diff options
-rw-r--r-- | examples/remote_debug.py | 19 | ||||
-rw-r--r-- | mitmproxy/builtins/dumper.py | 79 | ||||
-rw-r--r-- | mitmproxy/console/flowlist.py | 6 | ||||
-rw-r--r-- | mitmproxy/console/flowview.py | 31 | ||||
-rw-r--r-- | mitmproxy/contentviews.py | 17 | ||||
-rw-r--r-- | mitmproxy/ctx.py | 2 | ||||
-rw-r--r-- | mitmproxy/filt.py | 7 | ||||
-rw-r--r-- | mitmproxy/protocol/http2.py | 2 | ||||
-rw-r--r-- | mitmproxy/web/app.py | 3 | ||||
-rw-r--r-- | netlib/http/url.py | 1 | ||||
-rw-r--r-- | netlib/strutils.py | 3 | ||||
-rw-r--r-- | test/mitmproxy/test_contentview.py | 4 | ||||
-rw-r--r-- | test/mitmproxy/test_protocol_http2.py | 75 |
13 files changed, 148 insertions, 101 deletions
diff --git a/examples/remote_debug.py b/examples/remote_debug.py new file mode 100644 index 00000000..fb864f78 --- /dev/null +++ b/examples/remote_debug.py @@ -0,0 +1,19 @@ +""" +This script enables remote debugging of the mitmproxy *UI* with PyCharm. +For general debugging purposes, it is easier to just debug mitmdump within PyCharm. + +Usage: + - pip install pydevd on the mitmproxy machine + - Open the Run/Debug Configuration dialog box in PyCharm, and select the Python Remote Debug configuration type. + - Debugging works in the way that mitmproxy connects to the debug server on startup. + Specify host and port that mitmproxy can use to reach your PyCharm instance on startup. + - Adjust this inline script accordingly. + - Start debug server in PyCharm + - Set breakpoints + - Start mitmproxy -s remote_debug.py +""" + + +def start(): + import pydevd + pydevd.settrace("localhost", port=5678, stdoutToServer=True, stderrToServer=True) diff --git a/mitmproxy/builtins/dumper.py b/mitmproxy/builtins/dumper.py index 34d3632e..74c2e6b2 100644 --- a/mitmproxy/builtins/dumper.py +++ b/mitmproxy/builtins/dumper.py @@ -5,6 +5,8 @@ import traceback import click +import typing # noqa + from mitmproxy import contentviews from mitmproxy import ctx from mitmproxy import exceptions @@ -19,12 +21,25 @@ def indent(n, text): return "\n".join(pad + i for i in l) -class Dumper(): +class Dumper(object): def __init__(self): - self.filter = None - self.flow_detail = None - self.outfp = None - self.showhost = None + self.filter = None # type: filt.TFilter + self.flow_detail = None # type: int + self.outfp = None # type: typing.io.TextIO + self.showhost = None # type: bool + + def configure(self, options, updated): + if options.filtstr: + self.filter = filt.parse(options.filtstr) + if not self.filter: + raise exceptions.OptionsError( + "Invalid filter expression: %s" % options.filtstr + ) + else: + self.filter = None + self.flow_detail = options.flow_detail + self.outfp = options.tfile + self.showhost = options.showhost def echo(self, text, ident=None, **style): if ident: @@ -59,7 +74,7 @@ class Dumper(): self.echo("") try: - type, lines = contentviews.get_content_view( + _, lines = contentviews.get_content_view( contentviews.get("Auto"), content, headers=getattr(message, "headers", None) @@ -67,7 +82,7 @@ class Dumper(): except exceptions.ContentViewException: s = "Content viewer failed: \n" + traceback.format_exc() ctx.log.debug(s) - type, lines = contentviews.get_content_view( + _, lines = contentviews.get_content_view( contentviews.get("Raw"), content, headers=getattr(message, "headers", None) @@ -114,9 +129,8 @@ class Dumper(): if flow.client_conn: client = click.style( strutils.escape_control_characters( - flow.client_conn.address.host - ), - bold=True + repr(flow.client_conn.address) + ) ) elif flow.request.is_replay: client = click.style("[replay]", fg="yellow", bold=True) @@ -139,17 +153,23 @@ class Dumper(): url = flow.request.url url = click.style(strutils.escape_control_characters(url), bold=True) - httpversion = "" + http_version = "" if flow.request.http_version not in ("HTTP/1.1", "HTTP/1.0"): # We hide "normal" HTTP 1. - httpversion = " " + flow.request.http_version + http_version = " " + flow.request.http_version - line = "{stickycookie}{client} {method} {url}{httpversion}".format( - stickycookie=stickycookie, + if self.flow_detail >= 2: + linebreak = "\n " + else: + linebreak = "" + + line = "{client}: {linebreak}{stickycookie}{method} {url}{http_version}".format( client=client, + stickycookie=stickycookie, + linebreak=linebreak, method=method, url=url, - httpversion=httpversion + http_version=http_version ) self.echo(line) @@ -185,9 +205,14 @@ class Dumper(): size = human.pretty_size(len(flow.response.raw_content)) size = click.style(size, bold=True) - arrows = click.style(" <<", bold=True) + arrows = click.style(" <<", bold=True) + if self.flow_detail == 1: + # This aligns the HTTP response code with the HTTP request method: + # 127.0.0.1:59519: GET http://example.com/ + # << 304 Not Modified 0b + arrows = " " * (len(repr(flow.client_conn.address)) - 2) + arrows - line = "{replay} {arrows} {code} {reason} {size}".format( + line = "{replay}{arrows} {code} {reason} {size}".format( replay=replay, arrows=arrows, code=code, @@ -211,25 +236,12 @@ class Dumper(): def match(self, f): if self.flow_detail == 0: return False - if not self.filt: + if not self.filter: return True - elif f.match(self.filt): + elif f.match(self.filter): return True return False - def configure(self, options, updated): - if options.filtstr: - self.filt = filt.parse(options.filtstr) - if not self.filt: - raise exceptions.OptionsError( - "Invalid filter expression: %s" % options.filtstr - ) - else: - self.filt = None - self.flow_detail = options.flow_detail - self.outfp = options.tfile - self.showhost = options.showhost - def response(self, f): if self.match(f): self.echo_flow(f) @@ -239,8 +251,7 @@ class Dumper(): self.echo_flow(f) def tcp_message(self, f): - # FIXME: Filter should be applied here - if self.options.flow_detail == 0: + if not self.match(f): return message = f.messages[-1] direction = "->" if message.from_client else "<-" diff --git a/mitmproxy/console/flowlist.py b/mitmproxy/console/flowlist.py index 8aea9ea2..43742083 100644 --- a/mitmproxy/console/flowlist.py +++ b/mitmproxy/console/flowlist.py @@ -182,7 +182,8 @@ class ConnectionItem(urwid.WidgetWrap): self.flow.accept_intercept(self.master) signals.flowlist_change.send(self) elif key == "d": - self.flow.kill(self.master) + if not self.flow.reply.acked: + self.flow.kill(self.master) self.state.delete_flow(self.flow) signals.flowlist_change.send(self) elif key == "D": @@ -245,7 +246,8 @@ class ConnectionItem(urwid.WidgetWrap): callback = self.save_flows_prompt, ) elif key == "X": - self.flow.kill(self.master) + if not self.flow.reply.acked: + self.flow.kill(self.master) elif key == "enter": if self.flow.request: self.master.view_flow(self.flow) diff --git a/mitmproxy/console/flowview.py b/mitmproxy/console/flowview.py index 789066fc..22481f69 100644 --- a/mitmproxy/console/flowview.py +++ b/mitmproxy/console/flowview.py @@ -6,6 +6,7 @@ import sys import traceback import urwid +from typing import Optional, Union # noqa from mitmproxy import contentviews from mitmproxy import controller @@ -105,7 +106,8 @@ footer = [ class FlowViewHeader(urwid.WidgetWrap): def __init__(self, master, f): - self.master, self.flow = master, f + self.master = master # type: "mitmproxy.console.master.ConsoleMaster" + self.flow = f # type: models.HTTPFlow self._w = common.format_flow( f, False, @@ -530,13 +532,6 @@ class FlowView(tabs.Tabs): ) signals.flow_change.send(self, flow = self.flow) - def delete_body(self, t): - if self.tab_offset == TAB_REQ: - self.flow.request.content = None - else: - self.flow.response.content = None - signals.flow_change.send(self, flow = self.flow) - def keypress(self, size, key): key = super(self.__class__, self).keypress(size, key) @@ -545,6 +540,8 @@ class FlowView(tabs.Tabs): return key = common.shortcuts(key) + + conn = None # type: Optional[Union[models.HTTPRequest, models.HTTPResponse]] if self.tab_offset == TAB_REQ: conn = self.flow.request elif self.tab_offset == TAB_RESP: @@ -569,7 +566,8 @@ class FlowView(tabs.Tabs): else: self.view_next_flow(self.flow) f = self.flow - f.kill(self.master) + if not f.reply.acked: + f.kill(self.master) self.state.delete_flow(f) elif key == "D": f = self.master.duplicate_flow(self.flow) @@ -691,15 +689,8 @@ class FlowView(tabs.Tabs): args = (scope, self.flow, common.copy_to_clipboard_or_prompt) ) elif key == "x": - signals.status_prompt_onekey.send( - prompt = "Delete body", - keys = ( - ("completely", "c"), - ("mark as missing", "m"), - ), - callback = self.delete_body - ) - key = None + conn.content = None + signals.flow_change.send(self, flow=self.flow) elif key == "v": if conn.raw_content: t = conn.headers.get("content-type") @@ -713,7 +704,9 @@ class FlowView(tabs.Tabs): self.flow.backup() e = conn.headers.get("content-encoding", "identity") if e != "identity": - if not conn.decode(): + try: + conn.decode() + except ValueError: signals.status_message.send( message = "Could not decode - invalid data?" ) diff --git a/mitmproxy/contentviews.py b/mitmproxy/contentviews.py index afdaad7f..e155bc01 100644 --- a/mitmproxy/contentviews.py +++ b/mitmproxy/contentviews.py @@ -20,6 +20,8 @@ import logging import subprocess import sys +from typing import Mapping # noqa + import html2text import lxml.etree import lxml.html @@ -76,6 +78,7 @@ def pretty_json(s): def format_dict(d): + # type: (Mapping[Union[str,bytes], Union[str,bytes]]) -> Generator[Tuple[Union[str,bytes], Union[str,bytes]]] """ Helper function that transforms the given dictionary into a list of ("key", key ) @@ -85,7 +88,7 @@ def format_dict(d): max_key_len = max(len(k) for k in d.keys()) max_key_len = min(max_key_len, KEY_MAX) for key, value in d.items(): - key += ":" + key += b":" if isinstance(key, bytes) else u":" key = key.ljust(max_key_len + 2) yield [ ("header", key), @@ -106,12 +109,16 @@ class View(object): prompt = () content_types = [] - def __call__(self, data, **metadata): + def __call__( + self, + data, # type: bytes + **metadata + ): """ Transform raw data into human-readable output. Args: - data: the data to decode/format as bytes. + data: the data to decode/format. metadata: optional keyword-only arguments for metadata. Implementations must not rely on a given argument being present. @@ -278,6 +285,10 @@ class ViewURLEncoded(View): content_types = ["application/x-www-form-urlencoded"] def __call__(self, data, **metadata): + try: + data = data.decode("ascii", "strict") + except ValueError: + return None d = url.decode(data) return "URLEncoded form", format_dict(multidict.MultiDict(d)) diff --git a/mitmproxy/ctx.py b/mitmproxy/ctx.py index fcfdfd0b..5d2905fa 100644 --- a/mitmproxy/ctx.py +++ b/mitmproxy/ctx.py @@ -1,4 +1,4 @@ from typing import Callable # noqa master = None # type: "mitmproxy.flow.FlowMaster" -log = None # type: Callable[[str], None] +log = None # type: "mitmproxy.controller.Log" diff --git a/mitmproxy/filt.py b/mitmproxy/filt.py index 6bdc1f03..67915e5b 100644 --- a/mitmproxy/filt.py +++ b/mitmproxy/filt.py @@ -39,9 +39,12 @@ import functools from mitmproxy.models.http import HTTPFlow from mitmproxy.models.tcp import TCPFlow +from mitmproxy.models.flow import Flow + from netlib import strutils import pyparsing as pp +from typing import Callable def only(*types): @@ -480,7 +483,11 @@ def _make(): bnf = _make() +TFilter = Callable[[Flow], bool] + + def parse(s): + # type: (str) -> TFilter try: filt = bnf.parseString(s, parseAll=True)[0] filt.pattern = s diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index 1285e10e..8308f44d 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -584,6 +584,8 @@ class Http2SingleStreamLayer(http._HttpTransmissionLayer, basethread.BaseThread) except exceptions.ProtocolException as e: # pragma: no cover self.log(repr(e), "info") self.log(traceback.format_exc(), "debug") + except exceptions.Kill: + self.log("Connection killed", "info") if not self.zombie: self.zombie = time.time() diff --git a/mitmproxy/web/app.py b/mitmproxy/web/app.py index eaef887a..e55df1f6 100644 --- a/mitmproxy/web/app.py +++ b/mitmproxy/web/app.py @@ -230,7 +230,8 @@ class AcceptFlow(RequestHandler): class FlowHandler(RequestHandler): def delete(self, flow_id): - self.flow.kill(self.master) + if not self.flow.reply.acked: + self.flow.kill(self.master) self.state.delete_flow(self.flow) def put(self, flow_id): diff --git a/netlib/http/url.py b/netlib/http/url.py index 2fc6e7ee..1c8c007a 100644 --- a/netlib/http/url.py +++ b/netlib/http/url.py @@ -82,6 +82,7 @@ def unparse(scheme, host, port, path=""): def encode(s): + # type: (six.text_type, bytes) -> str """ Takes a list of (key, value) tuples and returns a urlencoded string. """ diff --git a/netlib/strutils.py b/netlib/strutils.py index 32e77927..96c8b10f 100644 --- a/netlib/strutils.py +++ b/netlib/strutils.py @@ -51,8 +51,7 @@ else: def escape_control_characters(text, keep_spacing=True): """ - Replace all unicode C1 control characters from the given text with their respective control pictures. - For example, a null byte is replaced with the unicode character "\u2400". + Replace all unicode C1 control characters from the given text with a single "." Args: keep_spacing: If True, tabs and newlines will not be replaced. diff --git a/test/mitmproxy/test_contentview.py b/test/mitmproxy/test_contentview.py index 2db9ab40..aad53b37 100644 --- a/test/mitmproxy/test_contentview.py +++ b/test/mitmproxy/test_contentview.py @@ -59,10 +59,10 @@ class TestContentView: assert f[0] == "Query" def test_view_urlencoded(self): - d = url.encode([("one", "two"), ("three", "four")]) + d = url.encode([("one", "two"), ("three", "four")]).encode() v = cv.ViewURLEncoded() assert v(d) - d = url.encode([("adsfa", "")]) + d = url.encode([("adsfa", "")]).encode() v = cv.ViewURLEncoded() assert v(d) diff --git a/test/mitmproxy/test_protocol_http2.py b/test/mitmproxy/test_protocol_http2.py index afbffb67..aa096a72 100644 --- a/test/mitmproxy/test_protocol_http2.py +++ b/test/mitmproxy/test_protocol_http2.py @@ -30,7 +30,7 @@ logging.getLogger("PIL.PngImagePlugin").setLevel(logging.WARNING) requires_alpn = pytest.mark.skipif( not netlib.tcp.HAS_ALPN, - reason="requires OpenSSL with ALPN support") + reason='requires OpenSSL with ALPN support') class _Http2ServerBase(netlib_tservers.ServerTestBase): @@ -80,7 +80,7 @@ class _Http2ServerBase(netlib_tservers.ServerTestBase): print(traceback.format_exc()) break - def handle_server_event(self, h2_conn, rfile, wfile): + def handle_server_event(self, event, h2_conn, rfile, wfile): raise NotImplementedError() @@ -88,7 +88,6 @@ class _Http2TestBase(object): @classmethod def setup_class(cls): - cls.masteroptions = options.Options() opts = cls.get_options() cls.config = ProxyConfig(opts) @@ -145,12 +144,14 @@ class _Http2TestBase(object): wfile, h2_conn, stream_id=1, - headers=[], + headers=None, body=b'', end_stream=None, priority_exclusive=None, priority_depends_on=None, priority_weight=None): + if headers is None: + headers = [] if end_stream is None: end_stream = (len(body) == 0) @@ -172,12 +173,12 @@ class _Http2TestBase(object): class _Http2Test(_Http2TestBase, _Http2ServerBase): @classmethod - def setup_class(self): + def setup_class(cls): _Http2TestBase.setup_class() _Http2ServerBase.setup_class() @classmethod - def teardown_class(self): + def teardown_class(cls): _Http2TestBase.teardown_class() _Http2ServerBase.teardown_class() @@ -187,7 +188,7 @@ class TestSimple(_Http2Test): request_body_buffer = b'' @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.RequestReceived): @@ -214,7 +215,7 @@ class TestSimple(_Http2Test): wfile.write(h2_conn.data_to_send()) wfile.flush() elif isinstance(event, h2.events.DataReceived): - self.request_body_buffer += event.data + cls.request_body_buffer += event.data return True def test_simple(self): @@ -225,7 +226,7 @@ class TestSimple(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -269,7 +270,7 @@ class TestSimple(_Http2Test): class TestRequestWithPriority(_Http2Test): @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.RequestReceived): @@ -301,14 +302,14 @@ class TestRequestWithPriority(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), ], - priority_exclusive = True, - priority_depends_on = 42424242, - priority_weight = 42, + priority_exclusive=True, + priority_depends_on=42424242, + priority_weight=42, ) done = False @@ -343,7 +344,7 @@ class TestRequestWithPriority(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -381,11 +382,11 @@ class TestPriority(_Http2Test): priority_data = None @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.PriorityUpdated): - self.priority_data = (event.exclusive, event.depends_on, event.weight) + cls.priority_data = (event.exclusive, event.depends_on, event.weight) elif isinstance(event, h2.events.RequestReceived): import warnings with warnings.catch_warnings(): @@ -415,7 +416,7 @@ class TestPriority(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -451,11 +452,11 @@ class TestPriorityWithExistingStream(_Http2Test): priority_data = [] @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.PriorityUpdated): - self.priority_data.append((event.exclusive, event.depends_on, event.weight)) + cls.priority_data.append((event.exclusive, event.depends_on, event.weight)) elif isinstance(event, h2.events.RequestReceived): assert not event.priority_updated @@ -486,7 +487,7 @@ class TestPriorityWithExistingStream(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -527,7 +528,7 @@ class TestPriorityWithExistingStream(_Http2Test): class TestStreamResetFromServer(_Http2Test): @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.RequestReceived): @@ -543,7 +544,7 @@ class TestStreamResetFromServer(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -578,7 +579,7 @@ class TestStreamResetFromServer(_Http2Test): class TestBodySizeLimit(_Http2Test): @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False return True @@ -592,7 +593,7 @@ class TestBodySizeLimit(_Http2Test): client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -627,7 +628,7 @@ class TestBodySizeLimit(_Http2Test): class TestPushPromise(_Http2Test): @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.RequestReceived): @@ -637,14 +638,14 @@ class TestPushPromise(_Http2Test): h2_conn.send_headers(1, [(':status', '200')]) h2_conn.push_stream(1, 2, [ - (':authority', "127.0.0.1:%s" % self.port), + (':authority', "127.0.0.1:{}".format(cls.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/pushed_stream_foo'), ('foo', 'bar') ]) h2_conn.push_stream(1, 4, [ - (':authority', "127.0.0.1:%s" % self.port), + (':authority', "127.0.0.1:{}".format(cls.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/pushed_stream_bar'), @@ -675,7 +676,7 @@ class TestPushPromise(_Http2Test): client, h2_conn = self._setup_connection() self._send_request(client.wfile, h2_conn, stream_id=1, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -728,7 +729,7 @@ class TestPushPromise(_Http2Test): client, h2_conn = self._setup_connection() self._send_request(client.wfile, h2_conn, stream_id=1, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -780,7 +781,7 @@ class TestPushPromise(_Http2Test): class TestConnectionLost(_Http2Test): @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.RequestReceived): h2_conn.send_headers(1, [(':status', '200')]) wfile.write(h2_conn.data_to_send()) @@ -791,7 +792,7 @@ class TestConnectionLost(_Http2Test): client, h2_conn = self._setup_connection() self._send_request(client.wfile, h2_conn, stream_id=1, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -822,12 +823,12 @@ class TestConnectionLost(_Http2Test): class TestMaxConcurrentStreams(_Http2Test): @classmethod - def setup_class(self): + def setup_class(cls): _Http2TestBase.setup_class() _Http2ServerBase.setup_class(h2_server_settings={h2.settings.MAX_CONCURRENT_STREAMS: 2}) @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.ConnectionTerminated): return False elif isinstance(event, h2.events.RequestReceived): @@ -848,7 +849,7 @@ class TestMaxConcurrentStreams(_Http2Test): # this will exceed MAX_CONCURRENT_STREAMS on the server connection # and cause mitmproxy to throttle stream creation to the server self._send_request(client.wfile, h2_conn, stream_id=id, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), @@ -883,7 +884,7 @@ class TestMaxConcurrentStreams(_Http2Test): class TestConnectionTerminated(_Http2Test): @classmethod - def handle_server_event(self, event, h2_conn, rfile, wfile): + def handle_server_event(cls, event, h2_conn, rfile, wfile): if isinstance(event, h2.events.RequestReceived): h2_conn.close_connection(error_code=5, last_stream_id=42, additional_data=b'foobar') wfile.write(h2_conn.data_to_send()) @@ -894,7 +895,7 @@ class TestConnectionTerminated(_Http2Test): client, h2_conn = self._setup_connection() self._send_request(client.wfile, h2_conn, headers=[ - (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':authority', "127.0.0.1:{}".format(self.server.server.address.port)), (':method', 'GET'), (':scheme', 'https'), (':path', '/'), |