diff --git a/addon/web/client/package.json b/addon/web/client/package.json index 6f39ebf..5700cef 100644 --- a/addon/web/client/package.json +++ b/addon/web/client/package.json @@ -7,6 +7,7 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "bootstrap": "^4.6.0", + "js-base64": "^3.6.0", "react": "^17.0.1", "react-bootstrap": "^1.4.3", "react-dom": "^17.0.1", diff --git a/addon/web/client/src/App.css b/addon/web/client/src/App.css index e0e7307..13df450 100644 --- a/addon/web/client/src/App.css +++ b/addon/web/client/src/App.css @@ -1,4 +1,46 @@ .main-table-wrap { font-family: Menlo,Monaco; - font-size: 0.9rem; + font-size: 0.8rem; +} + +.flow-detail { + position: fixed; + top: 0; + right: 0; + + height: 100vh; + background-color: #fff; + min-width: 500px; + width: 50%; + overflow-y: auto; + + word-break: break-all; +} + +.flow-detail .header-tabs span { + display: inline-block; + line-height: 1; + padding: 8px; + cursor: pointer; +} + +.flow-detail .header-tabs .selected { + border-bottom: 2px rgb(35, 118, 229) solid; +} + +/* .flow-detail .header-block { + margin: 20px; +} */ + +.flow-detail .header-block > p { + font-weight: bold; +} + +.flow-detail .header-block .header-block-content p { + margin: 5px 0; +} + +.flow-detail .header-block .header-block-content { + margin-left: 20px; + line-height: 1.5; } \ No newline at end of file diff --git a/addon/web/client/src/App.js b/addon/web/client/src/App.js index 40f6989..6e3bf66 100644 --- a/addon/web/client/src/App.js +++ b/addon/web/client/src/App.js @@ -1,7 +1,16 @@ import React from 'react' import Table from 'react-bootstrap/Table' +import { Base64 } from 'js-base64' import './App.css' +const isTextResponse = response => { + if (!response) return false + if (!response.header) return false + if (!response.header['Content-Type']) return false + + return /text|javascript|json/.test(response.header['Content-Type'].join('')) +} + class App extends React.Component { constructor(props) { @@ -9,6 +18,9 @@ class App extends React.Component { this.state = { flows: [], + flow: null, + + flowTab: 'Headers', // Headers, Preview, Response } this.ws = null } @@ -53,6 +65,79 @@ class App extends React.Component { // this.ws.send('msg') // this.ws.close() } + + renderFlow() { + const { flow, flowTab } = this.state + if (!flow) return null + + const request = flow.request + const response = flow.response || {} + + return ( +
+
+ { this.setState({ flow: null }) }}>x + { this.setState({ flowTab: 'Headers' }) }}>Headers + { this.setState({ flowTab: 'Preview' }) }}>Preview + { this.setState({ flowTab: 'Response' }) }}>Response +
+ +
+ { + !(flowTab === 'Headers') ? null : +
+
+

General

+
+

Request URL: {request.url}

+

Request Method: {request.method}

+

Status Code: {`${response.statusCode || '(pending)'}`}

+
+
+ +
+

Response Headers

+
+ { + !(response.header) ? null : + Object.keys(response.header).map(key => { + return ( +

{key}: {response.header[key].join(' ')}

+ ) + }) + } +
+
+ +
+

Request Headers

+
+ { + !(request.header) ? null : + Object.keys(request.header).map(key => { + return ( +

{key}: {request.header[key].join(' ')}

+ ) + }) + } +
+
+
+ } + + { + !(flowTab === 'Response') ? null : + !(response.body && response.body.length) ?
No response
: + !(isTextResponse(response)) ?
Not text response
: +
+ {Base64.decode(response.body)} +
+ } +
+ +
+ ) + } render() { const { flows } = this.state @@ -61,10 +146,10 @@ class App extends React.Component { - - + + @@ -78,17 +163,21 @@ class App extends React.Component { const request = f.request const response = f.response || {} return ( - - - + { + this.setState({ flow: f }) + }}> + + ) }) }
StatusMethod Host PathMethodStatus
{response.statusCode || '(pending)'}{request.method}
{u.host} {path}{request.method}{response.statusCode || '(pending)'}
+ + {this.renderFlow()} ) } diff --git a/addon/web/client/yarn.lock b/addon/web/client/yarn.lock index dd51f5b..7f654ce 100644 --- a/addon/web/client/yarn.lock +++ b/addon/web/client/yarn.lock @@ -6650,6 +6650,11 @@ jest@26.6.0: import-local "^3.0.2" jest-cli "^26.6.0" +js-base64@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.6.0.tgz#773e1de628f4f298d65a7e9842c50244751f5756" + integrity sha512-wVdUBYQeY2gY73RIlPrysvpYx+2vheGo8Y1SNQv/BzHToWpAZzJU7Z6uheKMAe+GLSBig5/Ps2nxg/8tRB73xg== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" diff --git a/cmd/mitmproxy/main.go b/cmd/mitmproxy/main.go index 5af9482..7081e31 100644 --- a/cmd/mitmproxy/main.go +++ b/cmd/mitmproxy/main.go @@ -52,6 +52,7 @@ func main() { p.AddAddon(dumper) } + p.AddAddon(&addon.Decoder{}) p.AddAddon(web.NewWebAddon()) log.Fatal(p.Start())