diff --git a/mitmproxy/addons/dumper.py b/mitmproxy/addons/dumper.py index b13348bb2..da0842b88 100644 --- a/mitmproxy/addons/dumper.py +++ b/mitmproxy/addons/dumper.py @@ -137,7 +137,8 @@ class Dumper: human.format_address(flow.client_conn.peername) ) ) - else: + else: # pragma: no cover + # this should not happen, but we're defensive here. client = "" pushed = ' PUSH_PROMISE' if 'h2-pushed-stream' in flow.metadata else '' diff --git a/test/mitmproxy/addons/test_view.py b/test/mitmproxy/addons/test_view.py index 13429a6ab..6c1124d84 100644 --- a/test/mitmproxy/addons/test_view.py +++ b/test/mitmproxy/addons/test_view.py @@ -224,22 +224,26 @@ async def test_load(tmpdir): def test_resolve(): v = view.View() with taddons.context() as tctx: + f = tft(method="get") assert tctx.command(v.resolve, "@all") == [] assert tctx.command(v.resolve, "@focus") == [] assert tctx.command(v.resolve, "@shown") == [] assert tctx.command(v.resolve, "@hidden") == [] assert tctx.command(v.resolve, "@marked") == [] assert tctx.command(v.resolve, "@unmarked") == [] + assert tctx.command(v.resolve, f"@{f.id}") == [] assert tctx.command(v.resolve, "~m get") == [] - v.request(tft(method="get")) + v.request(f) assert len(tctx.command(v.resolve, "~m get")) == 1 assert len(tctx.command(v.resolve, "@focus")) == 1 assert len(tctx.command(v.resolve, "@all")) == 1 assert len(tctx.command(v.resolve, "@shown")) == 1 assert len(tctx.command(v.resolve, "@unmarked")) == 1 + assert len(tctx.command(v.resolve, f"@{f.id}")) == 1 assert tctx.command(v.resolve, "@hidden") == [] assert tctx.command(v.resolve, "@marked") == [] v.request(tft(method="put")) + assert len(tctx.command(v.resolve, f"@{f.id}")) == 1 assert len(tctx.command(v.resolve, "@focus")) == 1 assert len(tctx.command(v.resolve, "@shown")) == 2 assert len(tctx.command(v.resolve, "@all")) == 2 @@ -624,4 +628,4 @@ def test_configure(): [":not valid:", SYMBOL_MARK], [":weird", SYMBOL_MARK] ]) def test_marker(marker, expected): - assert render_marker(marker) == expected \ No newline at end of file + assert render_marker(marker) == expected diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py index d3f76c70e..44073ea82 100644 --- a/test/mitmproxy/tools/web/test_app.py +++ b/test/mitmproxy/tools/web/test_app.py @@ -18,7 +18,7 @@ import tornado.testing from tornado import httpclient from tornado import websocket -from mitmproxy import options, optmanager +from mitmproxy import certs, options, optmanager from mitmproxy.test import tflow from mitmproxy.tools.web import app from mitmproxy.tools.web import master as webmaster @@ -39,7 +39,81 @@ def get_json(resp: httpclient.HTTPResponse): return _json.loads(resp.body.decode()) -@pytest.mark.usefixtures("no_tornado_logging") +def test_generate_tflow_js(tdata): + tf = tflow.tflow(resp=True, err=True, ws=True) + tf.server_conn.certificate_list = [ + certs.Cert.from_pem( + Path(tdata.path("mitmproxy/net/data/verificationcerts/self-signed.pem")).read_bytes() + ) + ] + tf.request.trailers = Headers(trailer="qvalue") + tf.response.trailers = Headers(trailer="qvalue") + + _tflow = app.flow_to_json(tf) + # Set some value as constant, so that _tflow.js would not change every time. + _tflow['id'] = "d91165be-ca1f-4612-88a9-c0f8696f3e29" + _tflow['client_conn']['id'] = "4a18d1a0-50a1-48dd-9aa6-d45d74282939" + _tflow['server_conn']['id'] = "f087e7b2-6d0a-41a8-a8f0-e1a4761395f8" + tflow_json = _json.dumps(_tflow, indent=4, sort_keys=True) + + tflow_json = re.sub( + r'( {8}"(address|is_replay|alpn_proto_negotiated)":)', + r" //@ts-ignore\n\1", + tflow_json + ).replace(": null", ": undefined") + + content = ( + "/** Auto-generated by test_app.py:test_generate_tflow_js */\n" + "import {HTTPFlow} from '../../flow';\n" + "export default function(): Required {\n" + f" return {tflow_json}\n" + "}" + ) + (Path(__file__).parent / "../../../../web/src/js/__tests__/ducks/_tflow.ts").write_bytes( + content.encode() + ) + + +def test_generate_options_js(): + o = options.Options() + m = webmaster.WebMaster(o) + opt: optmanager._Option + + def ts_type(t): + if t == bool: + return "boolean" + if t == str: + return "string" + if t == int: + return "number" + if t == typing.Sequence[str]: + return "string[]" + if t == typing.Optional[str]: + return "string | undefined" + raise RuntimeError(t) + + with redirect_stdout(io.StringIO()) as s: + + print("/** Auto-generated by test_app.py:test_generate_options_js */") + + print("export interface OptionsState {") + for _, opt in sorted(m.options.items()): + print(f" {opt.name}: {ts_type(opt.typespec)}") + print("}") + print("") + print("export type Option = keyof OptionsState") + print("") + print("export const defaultState: OptionsState = {") + for _, opt in sorted(m.options.items()): + print(f" {opt.name}: {json.dumps(opt.default)},".replace(": null", ": undefined")) + print("}") + + (Path(__file__).parent / "../../../../web/src/js/ducks/_options_gen.ts").write_bytes( + s.getvalue().encode() + ) + + +@pytest.mark.usefixtures("no_tornado_logging", "tdata") class TestApp(tornado.testing.AsyncHTTPTestCase): def get_new_ioloop(self): io_loop = tornado.platform.asyncio.AsyncIOLoop() @@ -51,6 +125,7 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): m = webmaster.WebMaster(o, with_termlog=False) f = tflow.tflow(resp=True) f.id = "42" + f.request.content = b"foo\nbar" m.view.add([f]) m.view.add([tflow.tflow(err=True)]) m.log.info("test log") @@ -153,7 +228,8 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): "code": 404, "headers": [("bar", "baz")], "content": "resp", - } + }, + "marked": ":red_circle:", } assert self.put_json("/flows/42", upd).code == 200 assert f.request.method == "PATCH" @@ -252,10 +328,17 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): assert f.modified() f.revert() - def test_flow_content_view(self): + def test_flow_contentview(self): assert get_json(self.fetch("/flows/42/request/content/raw")) == { "lines": [ - [["text", "content"]] + [["text", "foo"]], + [["text", "bar"]] + ], + "description": "Raw" + } + assert get_json(self.fetch("/flows/42/request/content/raw?lines=1")) == { + "lines": [ + [["text", "foo"]] ], "description": "Raw" } @@ -322,70 +405,3 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): # trigger on_close by opening a second connection. ws_client2 = yield websocket.websocket_connect(ws_url) ws_client2.close() - - def test_generate_tflow_js(self): - tf = tflow.tflow(resp=True, err=True, ws=True) - tf.request.trailers = Headers(trailer="qvalue") - tf.response.trailers = Headers(trailer="qvalue") - - _tflow = app.flow_to_json(tf) - # Set some value as constant, so that _tflow.js would not change every time. - _tflow['id'] = "d91165be-ca1f-4612-88a9-c0f8696f3e29" - _tflow['client_conn']['id'] = "4a18d1a0-50a1-48dd-9aa6-d45d74282939" - _tflow['server_conn']['id'] = "f087e7b2-6d0a-41a8-a8f0-e1a4761395f8" - tflow_json = _json.dumps(_tflow, indent=4, sort_keys=True) - - tflow_json = re.sub( - r'( {8}"(address|is_replay|alpn_proto_negotiated)":)', - r" //@ts-ignore\n\1", - tflow_json - ).replace(": null", ": undefined") - - content = ( - "/** Auto-generated by test_app.py:TestApp.test_generate_tflow_js */\n" - "import {HTTPFlow} from '../../flow';\n" - "export default function(): Required {\n" - f" return {tflow_json}\n" - "}" - ) - (Path(__file__).parent / "../../../../web/src/js/__tests__/ducks/_tflow.ts").write_bytes( - content.encode() - ) - - def test_generate_options_js(self): - o = options.Options() - m = webmaster.WebMaster(o) - opt: optmanager._Option - - def ts_type(t): - if t == bool: - return "boolean" - if t == str: - return "string" - if t == int: - return "number" - if t == typing.Sequence[str]: - return "string[]" - if t == typing.Optional[str]: - return "string | undefined" - raise RuntimeError(t) - - with redirect_stdout(io.StringIO()) as s: - - print("/** Auto-generated by test_app.py:TestApp.test_generate_options_js */") - - print("export interface OptionsState {") - for _, opt in sorted(m.options.items()): - print(f" {opt.name}: {ts_type(opt.typespec)}") - print("}") - print("") - print("export type Option = keyof OptionsState") - print("") - print("export const defaultState: OptionsState = {") - for _, opt in sorted(m.options.items()): - print(f" {opt.name}: {json.dumps(opt.default)},".replace(": null", ": undefined")) - print("}") - - (Path(__file__).parent / "../../../../web/src/js/ducks/_options_gen.ts").write_bytes( - s.getvalue().encode() - ) diff --git a/web/jest.config.js b/web/jest.config.js index a5ab6f70c..a9261390c 100644 --- a/web/jest.config.js +++ b/web/jest.config.js @@ -13,7 +13,8 @@ module.exports = async () => { ], "coverageDirectory": "./coverage", "coveragePathIgnorePatterns": [ - "/src/js/filt/filt.js" + "/src/js/filt/filt.js", + "/src/js/filt/command.js" ], "collectCoverageFrom": [ "src/js/**/*.{js,jsx,ts,tsx}" diff --git a/web/package.json b/web/package.json index 29820afde..2aa7a76a0 100644 --- a/web/package.json +++ b/web/package.json @@ -1,9 +1,6 @@ { "name": "mitmproxy", "private": true, - "jest": { - "verbose": true - }, "scripts": { "test": "tsc --noEmit && jest --coverage", "build": "gulp prod", diff --git a/web/src/js/__tests__/components/__snapshots__/FlowViewSpec.tsx.snap b/web/src/js/__tests__/components/__snapshots__/FlowViewSpec.tsx.snap index d7db16248..62867f464 100644 --- a/web/src/js/__tests__/components/__snapshots__/FlowViewSpec.tsx.snap +++ b/web/src/js/__tests__/components/__snapshots__/FlowViewSpec.tsx.snap @@ -611,6 +611,121 @@ exports[`FlowView 4`] = ` +

+ Server Certificate +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Type + + RSA, 2048 bits +
+ SHA256 digest + + e5f62a1175031b6feb959bc8e6dd0f8e2546dbbf7c32da39534309d8aa92967c +
+ Valid from + + 2020-11-03 06:03:27 +
+ Valid to + + 2040-10-29 06:03:27 +
+ Subject Alternative Names + + example.mitmproxy.org +
+ Subject + +
+
+ C +
+
+ AU +
+
+ ST +
+
+ Some-State +
+
+ O +
+
+ Internet Widgits Pty Ltd +
+
+
+ Issuer + +
+
+ C +
+
+ AU +
+
+ ST +
+
+ Some-State +
+
+ O +
+
+ Internet Widgits Pty Ltd +
+
+
+ Serial + + 247170098335718583458667965517443538258472437317 +
diff --git a/web/src/js/__tests__/ducks/_tflow.ts b/web/src/js/__tests__/ducks/_tflow.ts index d26b95cc3..e71f2070e 100644 --- a/web/src/js/__tests__/ducks/_tflow.ts +++ b/web/src/js/__tests__/ducks/_tflow.ts @@ -1,4 +1,4 @@ -/** Auto-generated by test_app.py:TestApp.test_generate_tflow_js */ +/** Auto-generated by test_app.py:test_generate_tflow_js */ import {HTTPFlow} from '../../flow'; export default function(): Required { return { @@ -90,7 +90,47 @@ export default function(): Required { 22 ], "alpn": undefined, - "cert": undefined, + "cert": { + "altnames": [ + "example.mitmproxy.org" + ], + "issuer": [ + [ + "C", + "AU" + ], + [ + "ST", + "Some-State" + ], + [ + "O", + "Internet Widgits Pty Ltd" + ] + ], + "keyinfo": [ + "RSA", + 2048 + ], + "notafter": 2235103407, + "notbefore": 1604383407, + "serial": "247170098335718583458667965517443538258472437317", + "sha256": "e5f62a1175031b6feb959bc8e6dd0f8e2546dbbf7c32da39534309d8aa92967c", + "subject": [ + [ + "C", + "AU" + ], + [ + "ST", + "Some-State" + ], + [ + "O", + "Internet Widgits Pty Ltd" + ] + ] + }, "cipher": undefined, "id": "f087e7b2-6d0a-41a8-a8f0-e1a4761395f8", "peername": [ diff --git a/web/src/js/ducks/_options_gen.ts b/web/src/js/ducks/_options_gen.ts index 075e3ffe4..a8bf9fce7 100644 --- a/web/src/js/ducks/_options_gen.ts +++ b/web/src/js/ducks/_options_gen.ts @@ -1,4 +1,4 @@ -/** Auto-generated by test_app.py:TestApp.test_generate_options_js */ +/** Auto-generated by test_app.py:test_generate_options_js */ export interface OptionsState { add_upstream_certs_to_client_chain: boolean allow_hosts: string[]