diff options
40 files changed, 462 insertions, 137 deletions
diff --git a/examples/complex/har_dump.py b/examples/complex/har_dump.py index 62011903..f7c1e658 100644 --- a/examples/complex/har_dump.py +++ b/examples/complex/har_dump.py @@ -7,6 +7,7 @@ import json import sys import base64 import zlib +import os from datetime import datetime import pytz @@ -166,7 +167,7 @@ def done(): if dump_file.endswith('.zhar'): raw = zlib.compress(raw, 9) - with open(dump_file, "wb") as f: + with open(os.path.expanduser(dump_file), "wb") as f: f.write(raw) mitmproxy.ctx.log("HAR dump finished (wrote %s bytes to file)" % len(json_dump)) diff --git a/mitmproxy/contentviews/image/image_parser.py b/mitmproxy/contentviews/image/image_parser.py index 0af58a88..b104d105 100644 --- a/mitmproxy/contentviews/image/image_parser.py +++ b/mitmproxy/contentviews/image/image_parser.py @@ -4,6 +4,7 @@ import typing from kaitaistruct import KaitaiStream from mitmproxy.contrib.kaitaistruct import png +from mitmproxy.contrib.kaitaistruct import gif Metadata = typing.List[typing.Tuple[str, str]] @@ -28,3 +29,29 @@ def parse_png(data: bytes) -> Metadata: elif chunk.type == 'zTXt': parts.append((chunk.body.keyword, chunk.body.text_datastream.decode('iso8859-1'))) return parts + + +def parse_gif(data: bytes) -> Metadata: + img = gif.Gif(KaitaiStream(io.BytesIO(data))) + parts = [ + ('Format', 'Compuserve GIF') + ] + parts.append(('version', "GIF{0}".format(img.header.version.decode('ASCII')))) + descriptor = img.logical_screen_descriptor + parts.append(('Size', "{0} x {1} px".format(descriptor.screen_width, descriptor.screen_height))) + parts.append(('background', str(descriptor.bg_color_index))) + ext_blocks = [] + for block in img.blocks: + if block.block_type.name == 'extension': + ext_blocks.append(block) + comment_blocks = [] + for block in ext_blocks: + if block.body.label._name_ == 'comment': + comment_blocks.append(block) + for block in comment_blocks: + entries = block.body.body.entries + for entry in entries: + comment = entry.bytes + if comment is not b'': + parts.append(('comment', str(comment))) + return parts diff --git a/mitmproxy/contentviews/image/view.py b/mitmproxy/contentviews/image/view.py index 08a70795..9caf9a6c 100644 --- a/mitmproxy/contentviews/image/view.py +++ b/mitmproxy/contentviews/image/view.py @@ -22,11 +22,17 @@ class ViewImage(base.View): ] def __call__(self, data, **metadata): - if imghdr.what('', h=data) == 'png': + image_type = imghdr.what('', h=data) + if image_type == 'png': f = "PNG" parts = image_parser.parse_png(data) fmt = base.format_dict(multidict.MultiDict(parts)) return "%s image" % f, fmt + elif image_type == 'gif': + f = "GIF" + parts = image_parser.parse_gif(data) + fmt = base.format_dict(multidict.MultiDict(parts)) + return "%s image" % f, fmt try: img = Image.open(io.BytesIO(data)) except IOError: diff --git a/mitmproxy/contrib/kaitaistruct/gif.py b/mitmproxy/contrib/kaitaistruct/gif.py new file mode 100644 index 00000000..3a847d54 --- /dev/null +++ b/mitmproxy/contrib/kaitaistruct/gif.py @@ -0,0 +1,247 @@ +# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +# The source was png.ksy from here - https://github.com/kaitai-io/kaitai_struct_formats/blob/562154250bea0081fed4e232751b934bc270a0c7/image/gif.ksy + +import array +import struct +import zlib +from enum import Enum + +from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO + + +class Gif(KaitaiStruct): + + class BlockType(Enum): + extension = 33 + local_image_descriptor = 44 + end_of_file = 59 + + class ExtensionLabel(Enum): + graphic_control = 249 + comment = 254 + application = 255 + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.header = self._root.Header(self._io, self, self._root) + self.logical_screen_descriptor = self._root.LogicalScreenDescriptor(self._io, self, self._root) + if self.logical_screen_descriptor.has_color_table: + self._raw_global_color_table = self._io.read_bytes((self.logical_screen_descriptor.color_table_size * 3)) + io = KaitaiStream(BytesIO(self._raw_global_color_table)) + self.global_color_table = self._root.ColorTable(io, self, self._root) + + self.blocks = [] + while not self._io.is_eof(): + self.blocks.append(self._root.Block(self._io, self, self._root)) + + + class ImageData(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.lzw_min_code_size = self._io.read_u1() + self.subblocks = self._root.Subblocks(self._io, self, self._root) + + + class ColorTableEntry(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.red = self._io.read_u1() + self.green = self._io.read_u1() + self.blue = self._io.read_u1() + + + class LogicalScreenDescriptor(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.screen_width = self._io.read_u2le() + self.screen_height = self._io.read_u2le() + self.flags = self._io.read_u1() + self.bg_color_index = self._io.read_u1() + self.pixel_aspect_ratio = self._io.read_u1() + + @property + def has_color_table(self): + if hasattr(self, '_m_has_color_table'): + return self._m_has_color_table + + self._m_has_color_table = (self.flags & 128) != 0 + return self._m_has_color_table + + @property + def color_table_size(self): + if hasattr(self, '_m_color_table_size'): + return self._m_color_table_size + + self._m_color_table_size = (2 << (self.flags & 7)) + return self._m_color_table_size + + + class LocalImageDescriptor(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.left = self._io.read_u2le() + self.top = self._io.read_u2le() + self.width = self._io.read_u2le() + self.height = self._io.read_u2le() + self.flags = self._io.read_u1() + if self.has_color_table: + self._raw_local_color_table = self._io.read_bytes((self.color_table_size * 3)) + io = KaitaiStream(BytesIO(self._raw_local_color_table)) + self.local_color_table = self._root.ColorTable(io, self, self._root) + + self.image_data = self._root.ImageData(self._io, self, self._root) + + @property + def has_color_table(self): + if hasattr(self, '_m_has_color_table'): + return self._m_has_color_table + + self._m_has_color_table = (self.flags & 128) != 0 + return self._m_has_color_table + + @property + def has_interlace(self): + if hasattr(self, '_m_has_interlace'): + return self._m_has_interlace + + self._m_has_interlace = (self.flags & 64) != 0 + return self._m_has_interlace + + @property + def has_sorted_color_table(self): + if hasattr(self, '_m_has_sorted_color_table'): + return self._m_has_sorted_color_table + + self._m_has_sorted_color_table = (self.flags & 32) != 0 + return self._m_has_sorted_color_table + + @property + def color_table_size(self): + if hasattr(self, '_m_color_table_size'): + return self._m_color_table_size + + self._m_color_table_size = (2 << (self.flags & 7)) + return self._m_color_table_size + + + class Block(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.block_type = self._root.BlockType(self._io.read_u1()) + _on = self.block_type + if _on == self._root.BlockType.extension: + self.body = self._root.Extension(self._io, self, self._root) + elif _on == self._root.BlockType.local_image_descriptor: + self.body = self._root.LocalImageDescriptor(self._io, self, self._root) + + + class ColorTable(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.entries = [] + while not self._io.is_eof(): + self.entries.append(self._root.ColorTableEntry(self._io, self, self._root)) + + + + class Header(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.magic = self._io.ensure_fixed_contents(3, struct.pack('3b', 71, 73, 70)) + self.version = self._io.read_bytes(3) + + + class ExtGraphicControl(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.block_size = self._io.ensure_fixed_contents(1, struct.pack('1b', 4)) + self.flags = self._io.read_u1() + self.delay_time = self._io.read_u2le() + self.transparent_idx = self._io.read_u1() + self.terminator = self._io.ensure_fixed_contents(1, struct.pack('1b', 0)) + + @property + def transparent_color_flag(self): + if hasattr(self, '_m_transparent_color_flag'): + return self._m_transparent_color_flag + + self._m_transparent_color_flag = (self.flags & 1) != 0 + return self._m_transparent_color_flag + + @property + def user_input_flag(self): + if hasattr(self, '_m_user_input_flag'): + return self._m_user_input_flag + + self._m_user_input_flag = (self.flags & 2) != 0 + return self._m_user_input_flag + + + class Subblock(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.num_bytes = self._io.read_u1() + self.bytes = self._io.read_bytes(self.num_bytes) + + + class ExtApplication(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.application_id = self._root.Subblock(self._io, self, self._root) + self.subblocks = [] + while True: + _ = self._root.Subblock(self._io, self, self._root) + self.subblocks.append(_) + if _.num_bytes == 0: + break + + + class Subblocks(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.entries = [] + while True: + _ = self._root.Subblock(self._io, self, self._root) + self.entries.append(_) + if _.num_bytes == 0: + break + + + class Extension(KaitaiStruct): + def __init__(self, _io, _parent=None, _root=None): + self._io = _io + self._parent = _parent + self._root = _root if _root else self + self.label = self._root.ExtensionLabel(self._io.read_u1()) + _on = self.label + if _on == self._root.ExtensionLabel.application: + self.body = self._root.ExtApplication(self._io, self, self._root) + elif _on == self._root.ExtensionLabel.comment: + self.body = self._root.Subblocks(self._io, self, self._root) + elif _on == self._root.ExtensionLabel.graphic_control: + self.body = self._root.ExtGraphicControl(self._io, self, self._root) + else: + self.body = self._root.Subblocks(self._io, self, self._root) diff --git a/mitmproxy/net/http/encoding.py b/mitmproxy/net/http/encoding.py index e123a033..5da07099 100644 --- a/mitmproxy/net/http/encoding.py +++ b/mitmproxy/net/http/encoding.py @@ -31,8 +31,8 @@ def decode(encoded: Union[str, bytes], encoding: str, errors: str='strict') -> U Raises: ValueError, if decoding fails. """ - if len(encoded) == 0: - return encoded + if encoded is None: + return None global _cache cached = ( @@ -72,8 +72,8 @@ def encode(decoded: Union[str, bytes], encoding: str, errors: str='strict') -> U Raises: ValueError, if encoding fails. """ - if len(decoded) == 0: - return decoded + if decoded is None: + return None global _cache cached = ( @@ -86,10 +86,7 @@ def encode(decoded: Union[str, bytes], encoding: str, errors: str='strict') -> U return _cache.encoded try: try: - value = decoded - if isinstance(value, str): - value = decoded.encode() - encoded = custom_encode[encoding](value) + encoded = custom_encode[encoding](decoded) except KeyError: encoded = codecs.encode(decoded, encoding, errors) if encoding in ("gzip", "deflate", "br"): @@ -114,12 +111,14 @@ def identity(content): return content -def decode_gzip(content): +def decode_gzip(content: bytes) -> bytes: + if not content: + return b"" gfile = gzip.GzipFile(fileobj=BytesIO(content)) return gfile.read() -def encode_gzip(content): +def encode_gzip(content: bytes) -> bytes: s = BytesIO() gf = gzip.GzipFile(fileobj=s, mode='wb') gf.write(content) @@ -127,15 +126,17 @@ def encode_gzip(content): return s.getvalue() -def decode_brotli(content): +def decode_brotli(content: bytes) -> bytes: + if not content: + return b"" return brotli.decompress(content) -def encode_brotli(content): +def encode_brotli(content: bytes) -> bytes: return brotli.compress(content) -def decode_deflate(content): +def decode_deflate(content: bytes) -> bytes: """ Returns decompressed data for DEFLATE. Some servers may respond with compressed data without a zlib header or checksum. An undocumented @@ -144,13 +145,15 @@ def decode_deflate(content): http://bugs.python.org/issue5784 """ + if not content: + return b"" try: return zlib.decompress(content) except zlib.error: return zlib.decompress(content, -15) -def encode_deflate(content): +def encode_deflate(content: bytes) -> bytes: """ Returns compressed content, always including zlib header and checksum. """ diff --git a/test/conftest.py b/test/conftest.py index 4b05624b..50ec3421 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,6 +2,7 @@ import os import pytest import OpenSSL import functools +from contextlib import contextmanager import mitmproxy.net.tcp @@ -29,35 +30,19 @@ skip_appveyor = pytest.mark.skipif( original_pytest_raises = pytest.raises +# TODO: remove this wrapper when pytest 3.1.0 is released +@contextmanager @functools.wraps(original_pytest_raises) def raises(exc, *args, **kwargs): - if isinstance(exc, str): - return RaisesContext(exc) - else: - return original_pytest_raises(exc, *args, **kwargs) + with original_pytest_raises(exc, *args, **kwargs) as exc_info: + yield + if 'match' in kwargs: + assert exc_info.match(kwargs['match']) pytest.raises = raises -class RaisesContext: - def __init__(self, expected_exception): - self.expected_exception = expected_exception - - def __enter__(self): - return - - def __exit__(self, exc_type, exc_val, exc_tb): - if not exc_type: - raise AssertionError("No exception raised.") - else: - if self.expected_exception.lower() not in str(exc_val).lower(): - raise AssertionError( - "Expected %s, but caught %s" % (repr(self.expected_exception), repr(exc_val)) - ) - return True - - @pytest.fixture() def disable_alpn(monkeypatch): monkeypatch.setattr(mitmproxy.net.tcp, 'HAS_ALPN', False) diff --git a/test/mitmproxy/addons/test_replace.py b/test/mitmproxy/addons/test_replace.py index 5583b2c0..126c6e3d 100644 --- a/test/mitmproxy/addons/test_replace.py +++ b/test/mitmproxy/addons/test_replace.py @@ -16,16 +16,16 @@ class TestReplace: assert x == ("foo", "bar", "vo/ing/") x = replace.parse_hook("/bar/voing") assert x == (".*", "bar", "voing") - with pytest.raises("invalid replacement"): + with pytest.raises(Exception, match="Invalid replacement"): replace.parse_hook("/") def test_configure(self): r = replace.Replace() with taddons.context() as tctx: tctx.configure(r, replacements=[("one", "two", "three")]) - with pytest.raises("invalid filter pattern"): + with pytest.raises(Exception, match="Invalid filter pattern"): tctx.configure(r, replacements=[("~b", "two", "three")]) - with pytest.raises("invalid regular expression"): + with pytest.raises(Exception, match="Invalid regular expression"): tctx.configure(r, replacements=[("foo", "+", "three")]) tctx.configure(r, replacements=["/a/b/c/"]) diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py index ed7ec5f1..96d7bd9e 100644 --- a/test/mitmproxy/addons/test_script.py +++ b/test/mitmproxy/addons/test_script.py @@ -64,11 +64,11 @@ class TestParseCommand: script.parse_command(" ") def test_no_script_file(self): - with pytest.raises("not found"): + with pytest.raises(Exception, match="not found"): script.parse_command("notfound") with tutils.tmpdir() as dir: - with pytest.raises("not a file"): + with pytest.raises(Exception, match="Not a file"): script.parse_command(dir) def test_parse_args(self): @@ -204,7 +204,7 @@ class TestScriptLoader: f = tflow.tflow(resp=True) with m.handlecontext(): - with pytest.raises("file not found"): + with pytest.raises(Exception, match="file not found"): sl.run_once("nonexistent", [f]) def test_simple(self): diff --git a/test/mitmproxy/addons/test_setheaders.py b/test/mitmproxy/addons/test_setheaders.py index 0091fc96..6355f2be 100644 --- a/test/mitmproxy/addons/test_setheaders.py +++ b/test/mitmproxy/addons/test_setheaders.py @@ -14,13 +14,13 @@ class TestSetHeaders: assert x == ("foo", "bar", "vo/ing/") x = setheaders.parse_setheader("/bar/voing") assert x == (".*", "bar", "voing") - with pytest.raises("invalid replacement"): + with pytest.raises(Exception, match="Invalid replacement"): setheaders.parse_setheader("/") def test_configure(self): sh = setheaders.SetHeaders() with taddons.context() as tctx: - with pytest.raises("invalid setheader filter pattern"): + with pytest.raises(Exception, match="Invalid setheader filter pattern"): tctx.configure(sh, setheaders = [("~b", "one", "two")]) tctx.configure(sh, setheaders = ["/foo/bar/voing"]) diff --git a/test/mitmproxy/addons/test_stickycookie.py b/test/mitmproxy/addons/test_stickycookie.py index 2297fe2c..157f2959 100644 --- a/test/mitmproxy/addons/test_stickycookie.py +++ b/test/mitmproxy/addons/test_stickycookie.py @@ -16,7 +16,7 @@ class TestStickyCookie: def test_config(self): sc = stickycookie.StickyCookie() with taddons.context() as tctx: - with pytest.raises("invalid filter"): + with pytest.raises(Exception, match="invalid filter"): tctx.configure(sc, stickycookie="~b") tctx.configure(sc, stickycookie="foo") diff --git a/test/mitmproxy/addons/test_streamfile.py b/test/mitmproxy/addons/test_streamfile.py index 79e66c1e..4922fc0b 100644 --- a/test/mitmproxy/addons/test_streamfile.py +++ b/test/mitmproxy/addons/test_streamfile.py @@ -18,7 +18,7 @@ def test_configure(): p = os.path.join(tdir, "foo") with pytest.raises(exceptions.OptionsError): tctx.configure(sa, streamfile=tdir) - with pytest.raises("invalid filter"): + with pytest.raises(Exception, match="Invalid filter"): tctx.configure(sa, streamfile=p, filtstr="~~") tctx.configure(sa, filtstr="foo") assert sa.filt diff --git a/test/mitmproxy/addons/test_view.py b/test/mitmproxy/addons/test_view.py index 7145bfc8..a063416f 100644 --- a/test/mitmproxy/addons/test_view.py +++ b/test/mitmproxy/addons/test_view.py @@ -396,11 +396,11 @@ def test_configure(): v = view.View() with taddons.context(options=Options()) as tctx: tctx.configure(v, filter="~q") - with pytest.raises("invalid interception filter"): + with pytest.raises(Exception, match="Invalid interception filter"): tctx.configure(v, filter="~~") tctx.configure(v, console_order="method") - with pytest.raises("unknown flow order"): + with pytest.raises(Exception, match="Unknown flow order"): tctx.configure(v, console_order="no") tctx.configure(v, console_order_reversed=True) diff --git a/test/mitmproxy/contentviews/test_image_parser.py b/test/mitmproxy/contentviews/test_image_parser.py index 62a07f56..4241a1bb 100644 --- a/test/mitmproxy/contentviews/test_image_parser.py +++ b/test/mitmproxy/contentviews/test_image_parser.py @@ -6,13 +6,13 @@ from mitmproxy.test import tutils @pytest.mark.parametrize("filename, metadata", { # no textual data - "mitmproxy/data/png_parser/ct0n0g04.png": [ + "mitmproxy/data/image_parser/ct0n0g04.png": [ ('Format', 'Portable network graphics'), ('Size', '32 x 32 px'), ('gamma', '1.0') ], # with textual data - "mitmproxy/data/png_parser/ct1n0g04.png": [ + "mitmproxy/data/image_parser/ct1n0g04.png": [ ('Format', 'Portable network graphics'), ('Size', '32 x 32 px'), ('gamma', '1.0'), @@ -27,7 +27,7 @@ from mitmproxy.test import tutils ('Disclaimer', 'Freeware.') ], # with compressed textual data - "mitmproxy/data/png_parser/ctzn0g04.png": [ + "mitmproxy/data/image_parser/ctzn0g04.png": [ ('Format', 'Portable network graphics'), ('Size', '32 x 32 px'), ('gamma', '1.0'), @@ -42,7 +42,7 @@ from mitmproxy.test import tutils ('Disclaimer', 'Freeware.') ], # UTF-8 international text - english - "mitmproxy/data/png_parser/cten0g04.png": [ + "mitmproxy/data/image_parser/cten0g04.png": [ ('Format', 'Portable network graphics'), ('Size', '32 x 32 px'), ('gamma', '1.0'), @@ -57,13 +57,13 @@ from mitmproxy.test import tutils ('Disclaimer', 'Freeware.') ], # check gamma value - "mitmproxy/data/png_parser/g07n0g16.png": [ + "mitmproxy/data/image_parser/g07n0g16.png": [ ('Format', 'Portable network graphics'), ('Size', '32 x 32 px'), ('gamma', '0.7') ], # check aspect value - "mitmproxy/data/png_parser/aspect.png": [ + "mitmproxy/data/image_parser/aspect.png": [ ('Format', 'Portable network graphics'), ('Size', '1280 x 798 px'), ('aspect', '72 x 72'), @@ -74,3 +74,33 @@ from mitmproxy.test import tutils def test_parse_png(filename, metadata): with open(tutils.test_data.path(filename), "rb") as f: assert metadata == image_parser.parse_png(f.read()) + + +@pytest.mark.parametrize("filename, metadata", { + # check comment + "mitmproxy/data/image_parser/hopper.gif": [ + ('Format', 'Compuserve GIF'), + ('version', 'GIF89a'), + ('Size', '128 x 128 px'), + ('background', '0'), + ('comment', "b'File written by Adobe Photoshop\\xa8 4.0'") + ], + # check background + "mitmproxy/data/image_parser/chi.gif": [ + ('Format', 'Compuserve GIF'), + ('version', 'GIF89a'), + ('Size', '320 x 240 px'), + ('background', '248'), + ('comment', "b'Created with GIMP'") + ], + # check working with color table + "mitmproxy/data/image_parser/iss634.gif": [ + ('Format', 'Compuserve GIF'), + ('version', 'GIF89a'), + ('Size', '245 x 245 px'), + ('background', '0') + ], +}.items()) +def test_parse_gif(filename, metadata): + with open(tutils.test_data.path(filename), 'rb') as f: + assert metadata == image_parser.parse_gif(f.read()) diff --git a/test/mitmproxy/data/png_parser/aspect.png b/test/mitmproxy/data/image_parser/aspect.png Binary files differindex 17c01913..17c01913 100644 --- a/test/mitmproxy/data/png_parser/aspect.png +++ b/test/mitmproxy/data/image_parser/aspect.png diff --git a/test/mitmproxy/data/image_parser/chi.gif b/test/mitmproxy/data/image_parser/chi.gif Binary files differnew file mode 100644 index 00000000..d217f8b5 --- /dev/null +++ b/test/mitmproxy/data/image_parser/chi.gif diff --git a/test/mitmproxy/data/png_parser/ct0n0g04.png b/test/mitmproxy/data/image_parser/ct0n0g04.png Binary files differindex 40d1e062..40d1e062 100644 --- a/test/mitmproxy/data/png_parser/ct0n0g04.png +++ b/test/mitmproxy/data/image_parser/ct0n0g04.png diff --git a/test/mitmproxy/data/png_parser/ct1n0g04.png b/test/mitmproxy/data/image_parser/ct1n0g04.png Binary files differindex 3ba110aa..3ba110aa 100644 --- a/test/mitmproxy/data/png_parser/ct1n0g04.png +++ b/test/mitmproxy/data/image_parser/ct1n0g04.png diff --git a/test/mitmproxy/data/png_parser/cten0g04.png b/test/mitmproxy/data/image_parser/cten0g04.png Binary files differindex a6a56faf..a6a56faf 100644 --- a/test/mitmproxy/data/png_parser/cten0g04.png +++ b/test/mitmproxy/data/image_parser/cten0g04.png diff --git a/test/mitmproxy/data/png_parser/ctzn0g04.png b/test/mitmproxy/data/image_parser/ctzn0g04.png Binary files differindex b4401c9c..b4401c9c 100644 --- a/test/mitmproxy/data/png_parser/ctzn0g04.png +++ b/test/mitmproxy/data/image_parser/ctzn0g04.png diff --git a/test/mitmproxy/data/png_parser/g07n0g16.png b/test/mitmproxy/data/image_parser/g07n0g16.png Binary files differindex d6a47c2d..d6a47c2d 100644 --- a/test/mitmproxy/data/png_parser/g07n0g16.png +++ b/test/mitmproxy/data/image_parser/g07n0g16.png diff --git a/test/mitmproxy/data/image_parser/hopper.gif b/test/mitmproxy/data/image_parser/hopper.gif Binary files differnew file mode 100644 index 00000000..2e7f5ade --- /dev/null +++ b/test/mitmproxy/data/image_parser/hopper.gif diff --git a/test/mitmproxy/data/image_parser/iss634.gif b/test/mitmproxy/data/image_parser/iss634.gif Binary files differnew file mode 100644 index 00000000..ba4e4566 --- /dev/null +++ b/test/mitmproxy/data/image_parser/iss634.gif diff --git a/test/mitmproxy/net/http/http1/test_read.py b/test/mitmproxy/net/http/http1/test_read.py index 01d03e7c..642b91c0 100644 --- a/test/mitmproxy/net/http/http1/test_read.py +++ b/test/mitmproxy/net/http/http1/test_read.py @@ -356,11 +356,11 @@ def test_read_chunked(): assert b"".join(_read_chunked(BytesIO(data))) == b"ab" data = b"\r\n" - with pytest.raises("closed prematurely"): + with pytest.raises(Exception, match="closed prematurely"): b"".join(_read_chunked(BytesIO(data))) data = b"1\r\nfoo" - with pytest.raises("malformed chunked body"): + with pytest.raises(Exception, match="Malformed chunked body"): b"".join(_read_chunked(BytesIO(data))) data = b"foo\r\nfoo" @@ -368,5 +368,5 @@ def test_read_chunked(): b"".join(_read_chunked(BytesIO(data))) data = b"5\r\naaaaa\r\n0\r\n\r\n" - with pytest.raises("too large"): + with pytest.raises(Exception, match="too large"): b"".join(_read_chunked(BytesIO(data), limit=2)) diff --git a/test/mitmproxy/net/http/test_encoding.py b/test/mitmproxy/net/http/test_encoding.py index 11619c44..8dac12cb 100644 --- a/test/mitmproxy/net/http/test_encoding.py +++ b/test/mitmproxy/net/http/test_encoding.py @@ -21,26 +21,55 @@ def test_identity(encoder): 'deflate', ]) def test_encoders(encoder): - assert "" == encoding.decode("", encoder) + """ + This test is for testing byte->byte encoding/decoding + """ + assert encoding.decode(None, encoder) is None + assert encoding.encode(None, encoder) is None + assert b"" == encoding.decode(b"", encoder) - assert "string" == encoding.decode( + assert b"string" == encoding.decode( encoding.encode( - "string", + b"string", encoder ), encoder ) - assert b"string" == encoding.decode( + + with pytest.raises(TypeError): + encoding.encode("string", encoder) + + with pytest.raises(TypeError): + encoding.decode("string", encoder) + with pytest.raises(ValueError): + encoding.decode(b"foobar", encoder) + + +@pytest.mark.parametrize("encoder", [ + 'utf8', + 'latin-1' +]) +def test_encoders_strings(encoder): + """ + This test is for testing byte->str decoding + and str->byte encoding + """ + assert "" == encoding.decode(b"", encoder) + + assert "string" == encoding.decode( encoding.encode( - b"string", + "string", encoder ), encoder ) - with pytest.raises(ValueError): - encoding.decode(b"foobar", encoder) + with pytest.raises(TypeError): + encoding.encode(b"string", encoder) + + with pytest.raises(TypeError): + encoding.decode("foobar", encoder) def test_cache(): diff --git a/test/mitmproxy/net/test_socks.py b/test/mitmproxy/net/test_socks.py index c1bd0603..e00dd410 100644 --- a/test/mitmproxy/net/test_socks.py +++ b/test/mitmproxy/net/test_socks.py @@ -181,9 +181,8 @@ def test_message_ipv6(): def test_message_invalid_host(): raw = tutils.treader(b"\xEE\x01\x00\x03\x0bexample@com\xDE\xAD\xBE\xEF") - with pytest.raises(socks.SocksError) as exc_info: + with pytest.raises(socks.SocksError, match="Invalid hostname: b'example@com'"): socks.Message.from_file(raw) - assert exc_info.match("Invalid hostname: b'example@com'") def test_message_invalid_rsv(): diff --git a/test/mitmproxy/net/test_tcp.py b/test/mitmproxy/net/test_tcp.py index eb09f328..ff6362c8 100644 --- a/test/mitmproxy/net/test_tcp.py +++ b/test/mitmproxy/net/test_tcp.py @@ -430,7 +430,7 @@ class TestServerCipherListError(tservers.ServerTestBase): def test_echo(self): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): - with pytest.raises("handshake error"): + with pytest.raises(Exception, match="handshake error"): c.convert_to_ssl(sni="foo.com") @@ -443,7 +443,7 @@ class TestClientCipherListError(tservers.ServerTestBase): def test_echo(self): c = tcp.TCPClient(("127.0.0.1", self.port)) with c.connect(): - with pytest.raises("cipher specification"): + with pytest.raises(Exception, match="cipher specification"): c.convert_to_ssl(sni="foo.com", cipher_list="bogus") diff --git a/test/mitmproxy/net/websockets/test_frame.py b/test/mitmproxy/net/websockets/test_frame.py index 183c7caa..2a5bd556 100644 --- a/test/mitmproxy/net/websockets/test_frame.py +++ b/test/mitmproxy/net/websockets/test_frame.py @@ -108,9 +108,9 @@ class TestFrameHeader: assert not f2.mask def test_violations(self): - with pytest.raises("opcode"): + with pytest.raises(Exception, match="opcode"): websockets.FrameHeader(opcode=17) - with pytest.raises("masking key"): + with pytest.raises(Exception, match="Masking key"): websockets.FrameHeader(masking_key=b"x") def test_automask(self): diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index f546d61b..3b5d0ac1 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -251,9 +251,8 @@ class TestSerialize: sio.write(b"bogus") sio.seek(0) r = mitmproxy.io.FlowReader(sio) - with pytest.raises(FlowReadException) as exc_info: + with pytest.raises(FlowReadException, match='Invalid data format'): list(r.stream()) - assert exc_info.match('Invalid data format') sio = io.BytesIO() f = tflow.tdummyflow() @@ -261,9 +260,8 @@ class TestSerialize: w.add(f) sio.seek(0) r = mitmproxy.io.FlowReader(sio) - with pytest.raises(FlowReadException) as exc_info: + with pytest.raises(FlowReadException, match='Unknown flow type'): list(r.stream()) - assert exc_info.match('Unknown flow type') f = FlowReadException("foo") assert str(f) == "foo" @@ -277,7 +275,7 @@ class TestSerialize: sio.seek(0) r = mitmproxy.io.FlowReader(sio) - with pytest.raises("version"): + with pytest.raises(Exception, match="version"): list(r.stream()) @@ -287,15 +285,15 @@ class TestFlowMaster: fm = master.Master(None, DummyServer()) f = tflow.tflow(resp=True) f.request.content = None - with pytest.raises("missing"): + with pytest.raises(Exception, match="missing"): fm.replay_request(f) f.intercepted = True - with pytest.raises("intercepted"): + with pytest.raises(Exception, match="intercepted"): fm.replay_request(f) f.live = True - with pytest.raises("live"): + with pytest.raises(Exception, match="live"): fm.replay_request(f) def test_create_flow(self): diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index f177df7b..65691fdf 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -71,9 +71,9 @@ def test_options(): with pytest.raises(TypeError): TO(nonexistent = "value") - with pytest.raises("no such option"): + with pytest.raises(Exception, match="No such option"): o.nonexistent = "value" - with pytest.raises("no such option"): + with pytest.raises(Exception, match="No such option"): o.update(nonexistent = "value") rec = [] @@ -97,7 +97,7 @@ def test_setter(): f = o.setter("two") f("xxx") assert o.two == "xxx" - with pytest.raises("no such option"): + with pytest.raises(Exception, match="No such option"): o.setter("nonexistent") @@ -108,7 +108,7 @@ def test_toggler(): assert o.two is False f() assert o.two is True - with pytest.raises("no such option"): + with pytest.raises(Exception, match="No such option"): o.toggler("nonexistent") @@ -193,11 +193,11 @@ def test_serialize(): assert o2 == o t = "invalid: foo\ninvalid" - with pytest.raises("config error"): + with pytest.raises(Exception, match="Config error"): o2.load(t) t = "invalid" - with pytest.raises("config error"): + with pytest.raises(Exception, match="Config error"): o2.load(t) t = "" diff --git a/test/mitmproxy/test_platform_pf.py b/test/mitmproxy/test_platform_pf.py index ebb011fe..f644bcc5 100644 --- a/test/mitmproxy/test_platform_pf.py +++ b/test/mitmproxy/test_platform_pf.py @@ -14,7 +14,7 @@ class TestLookup: p = tutils.test_data.path("mitmproxy/data/pf01") d = open(p, "rb").read() assert pf.lookup("192.168.1.111", 40000, d) == ("5.5.5.5", 80) - with pytest.raises("Could not resolve original destination"): + with pytest.raises(Exception, match="Could not resolve original destination"): pf.lookup("192.168.1.112", 40000, d) - with pytest.raises("Could not resolve original destination"): + with pytest.raises(Exception, match="Could not resolve original destination"): pf.lookup("192.168.1.111", 40001, d) diff --git a/test/mitmproxy/test_proxy.py b/test/mitmproxy/test_proxy.py index 9d793572..a14c851e 100644 --- a/test/mitmproxy/test_proxy.py +++ b/test/mitmproxy/test_proxy.py @@ -96,27 +96,27 @@ class TestProcessProxyOptions: @mock.patch("mitmproxy.platform.original_addr", None) def test_no_transparent(self): - with pytest.raises("transparent mode not supported"): + with pytest.raises(Exception, match="Transparent mode not supported"): self.p("-T") @mock.patch("mitmproxy.platform.original_addr") def test_modes(self, _): self.assert_noerr("-R", "http://localhost") - with pytest.raises("expected one argument"): + with pytest.raises(Exception, match="expected one argument"): self.p("-R") - with pytest.raises("Invalid server specification"): + with pytest.raises(Exception, match="Invalid server specification"): self.p("-R", "reverse") self.assert_noerr("-T") self.assert_noerr("-U", "http://localhost") - with pytest.raises("Invalid server specification"): + with pytest.raises(Exception, match="Invalid server specification"): self.p("-U", "upstream") self.assert_noerr("--upstream-auth", "test:test") - with pytest.raises("expected one argument"): + with pytest.raises(Exception, match="expected one argument"): self.p("--upstream-auth") - with pytest.raises("mutually exclusive"): + with pytest.raises(Exception, match="mutually exclusive"): self.p("-R", "http://localhost", "-T") def test_client_certs(self): @@ -125,14 +125,14 @@ class TestProcessProxyOptions: self.assert_noerr( "--client-certs", os.path.join(tutils.test_data.path("mitmproxy/data/clientcert"), "client.pem")) - with pytest.raises("path does not exist"): + with pytest.raises(Exception, match="path does not exist"): self.p("--client-certs", "nonexistent") def test_certs(self): self.assert_noerr( "--cert", tutils.test_data.path("mitmproxy/data/testkey.pem")) - with pytest.raises("does not exist"): + with pytest.raises(Exception, match="does not exist"): self.p("--cert", "nonexistent") def test_insecure(self): @@ -156,12 +156,12 @@ class TestProxyServer: def test_err(self): # binding to 0.0.0.0:1 works without special permissions on Windows conf = ProxyConfig(options.Options(listen_port=1)) - with pytest.raises("error starting proxy server"): + with pytest.raises(Exception, match="Error starting proxy server"): ProxyServer(conf) def test_err_2(self): conf = ProxyConfig(options.Options(listen_host="invalidhost")) - with pytest.raises("error starting proxy server"): + with pytest.raises(Exception, match="Error starting proxy server"): ProxyServer(conf) diff --git a/test/mitmproxy/test_proxy_config.py b/test/mitmproxy/test_proxy_config.py index 27563e3a..4272d952 100644 --- a/test/mitmproxy/test_proxy_config.py +++ b/test/mitmproxy/test_proxy_config.py @@ -3,7 +3,7 @@ from mitmproxy.proxy import config def test_parse_server_spec(): - with pytest.raises("Invalid server specification"): + with pytest.raises(Exception, match="Invalid server specification"): config.parse_server_spec("") assert config.parse_server_spec("http://foo.com:88") == ( "http", ("foo.com", 88) @@ -14,7 +14,7 @@ def test_parse_server_spec(): assert config.parse_server_spec("https://foo.com") == ( "https", ("foo.com", 443) ) - with pytest.raises("Invalid server specification"): + with pytest.raises(Exception, match="Invalid server specification"): config.parse_server_spec("foo.com") - with pytest.raises("Invalid server specification"): + with pytest.raises(Exception, match="Invalid server specification"): config.parse_server_spec("http://") diff --git a/test/mitmproxy/test_stateobject.py b/test/mitmproxy/test_stateobject.py index b9ffe7ae..edec92c2 100644 --- a/test/mitmproxy/test_stateobject.py +++ b/test/mitmproxy/test_stateobject.py @@ -17,6 +17,9 @@ class Child(StateObject): obj.set_state(state) return obj + def __eq__(self, other): + return isinstance(other, Child) and self.x == other.x + class Container(StateObject): def __init__(self): @@ -60,4 +63,7 @@ def test_container_list(): "child": None, "children": [{"x": 42}, {"x": 44}] } - assert len(a.copy().children) == 2 + copy = a.copy() + assert len(copy.children) == 2 + assert copy.children is not a.children + assert copy.children[0] is not a.children[0] diff --git a/test/pathod/test_language_base.py b/test/pathod/test_language_base.py index 190c39b3..85e9e53b 100644 --- a/test/pathod/test_language_base.py +++ b/test/pathod/test_language_base.py @@ -149,11 +149,11 @@ class TestTokValueFile: v = base.TokValue.parseString("<path2")[0] with pytest.raises(exceptions.FileAccessDenied): v.get_generator(language.Settings(staticdir=t)) - with pytest.raises("access disabled"): + with pytest.raises(Exception, match="access disabled"): v.get_generator(language.Settings()) v = base.TokValue.parseString("</outside")[0] - with pytest.raises("outside"): + with pytest.raises(Exception, match="outside"): v.get_generator(language.Settings(staticdir=t)) def test_spec(self): @@ -194,32 +194,27 @@ class TestMisc: v3 = v2.freeze({}) assert v2.value.val == v3.value.val - def test_fixedlengthvalue(self): + def test_fixedlengthvalue(self, tmpdir): class TT(base.FixedLengthValue): preamble = "m" length = 4 e = TT.expr() assert e.parseString("m@4") - with pytest.raises("invalid value length"): + with pytest.raises(Exception, match="Invalid value length"): e.parseString("m@100") - with pytest.raises("invalid value length"): + with pytest.raises(Exception, match="Invalid value length"): e.parseString("m@1") - with tutils.tmpdir() as t: - p = os.path.join(t, "path") - s = base.Settings(staticdir=t) - with open(p, "wb") as f: - f.write(b"a" * 20) - v = e.parseString("m<path")[0] - with pytest.raises("invalid value length"): - v.values(s) + s = base.Settings(staticdir=str(tmpdir)) + tmpdir.join("path").write_binary(b"a" * 20, ensure=True) + v = e.parseString("m<path")[0] + with pytest.raises(Exception, match="Invalid value length"): + v.values(s) - p = os.path.join(t, "path") - with open(p, "wb") as f: - f.write(b"a" * 4) - v = e.parseString("m<path")[0] - assert v.values(s) + tmpdir.join("path2").write_binary(b"a" * 4, ensure=True) + v = e.parseString("m<path2")[0] + assert v.values(s) class TKeyValue(base.KeyValue): @@ -282,7 +277,7 @@ def test_intfield(): assert v.value == 4 assert v.spec() == "t4" - with pytest.raises("can't exceed"): + with pytest.raises(Exception, match="can't exceed"): e.parseString("t5") @@ -324,9 +319,9 @@ def test_integer(): class BInt(base.Integer): bounds = (1, 5) - with pytest.raises("must be between"): + with pytest.raises(Exception, match="must be between"): BInt(0) - with pytest.raises("must be between"): + with pytest.raises(Exception, match="must be between"): BInt(6) assert BInt(5) assert BInt(1) diff --git a/test/pathod/test_language_http.py b/test/pathod/test_language_http.py index 199fdf64..6ab43fe0 100644 --- a/test/pathod/test_language_http.py +++ b/test/pathod/test_language_http.py @@ -20,7 +20,7 @@ def test_make_error_response(): class TestRequest: def test_nonascii(self): - with pytest.raises("ascii"): + with pytest.raises(Exception, match="ASCII"): parse_request("get:\xf0") def test_err(self): @@ -226,7 +226,7 @@ class TestResponse: assert str(v) def test_nonascii(self): - with pytest.raises("ascii"): + with pytest.raises(Exception, match="ASCII"): language.parse_pathod("foo:b\xf0") def test_parse_header(self): @@ -263,7 +263,7 @@ class TestResponse: def test_websockets(self): r = next(language.parse_pathod("ws")) - with pytest.raises("no websocket key"): + with pytest.raises(Exception, match="No websocket key"): r.resolve(language.Settings()) res = r.resolve(language.Settings(websocket_key=b"foo")) assert res.status_code.string() == b"101" @@ -351,5 +351,5 @@ def test_nested_response_freeze(): def test_unique_components(): - with pytest.raises("multiple body clauses"): + with pytest.raises(Exception, match="multiple body clauses"): language.parse_pathod("400:b@1:b@1") diff --git a/test/pathod/test_language_http2.py b/test/pathod/test_language_http2.py index fdb65a63..4f89adb8 100644 --- a/test/pathod/test_language_http2.py +++ b/test/pathod/test_language_http2.py @@ -39,7 +39,7 @@ class TestRequest: assert req.values(default_settings()) == req.values(default_settings()) def test_nonascii(self): - with pytest.raises("ascii"): + with pytest.raises(Exception, match="ASCII"): parse_request("get:\xf0") def test_err(self): @@ -168,7 +168,7 @@ class TestResponse: assert res.values(default_settings()) == res.values(default_settings()) def test_nonascii(self): - with pytest.raises("ascii"): + with pytest.raises(Exception, match="ASCII"): parse_response("200:\xf0") def test_err(self): diff --git a/test/pathod/test_language_websocket.py b/test/pathod/test_language_websocket.py index 20f6a3a6..e5046591 100644 --- a/test/pathod/test_language_websocket.py +++ b/test/pathod/test_language_websocket.py @@ -130,7 +130,7 @@ class TestWebsocketFrame: assert frm.payload == b"abc" def test_knone(self): - with pytest.raises("expected 4 bytes"): + with pytest.raises(Exception, match="Expected 4 bytes"): self.fr("wf:b'foo':mask:knone") def test_length(self): @@ -138,5 +138,5 @@ class TestWebsocketFrame: frm = self.fr("wf:l2:b'foo'") assert frm.header.payload_length == 2 assert frm.payload == b"fo" - with pytest.raises("expected 1024 bytes"): + with pytest.raises(Exception, match="Expected 1024 bytes"): self.fr("wf:l1024:b'foo'") diff --git a/test/pathod/test_pathoc.py b/test/pathod/test_pathoc.py index a8f79e67..2dd29e20 100644 --- a/test/pathod/test_pathoc.py +++ b/test/pathod/test_pathoc.py @@ -173,12 +173,12 @@ class TestDaemon(PathocTestDaemon): to = ("foobar", 80) c = pathoc.Pathoc(("127.0.0.1", self.d.port), fp=None) c.rfile, c.wfile = io.BytesIO(), io.BytesIO() - with pytest.raises("connect failed"): + with pytest.raises(Exception, match="CONNECT failed"): c.http_connect(to) c.rfile = io.BytesIO( b"HTTP/1.1 500 OK\r\n" ) - with pytest.raises("connect failed"): + with pytest.raises(Exception, match="CONNECT failed"): c.http_connect(to) c.rfile = io.BytesIO( b"HTTP/1.1 200 OK\r\n" @@ -195,14 +195,14 @@ class TestDaemon(PathocTestDaemon): c.rfile = tutils.treader( b"\x05\xEE" ) - with pytest.raises("SOCKS without authentication"): + with pytest.raises(Exception, match="SOCKS without authentication"): c.socks_connect(("example.com", 0xDEAD)) c.rfile = tutils.treader( b"\x05\x00" + b"\x05\xEE\x00\x03\x0bexample.com\xDE\xAD" ) - with pytest.raises("SOCKS server error"): + with pytest.raises(Exception, match="SOCKS server error"): c.socks_connect(("example.com", 0xDEAD)) c.rfile = tutils.treader( diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py index 60ac8072..88480a59 100644 --- a/test/pathod/test_pathod.py +++ b/test/pathod/test_pathod.py @@ -134,7 +134,7 @@ class CommonTests(tservers.DaemonTests): assert len(self.d.log()) == 0 def test_disconnect(self): - with pytest.raises("unexpected eof"): + with pytest.raises(Exception, match="Unexpected EOF"): self.get("202:b@100k:d200") def test_parserr(self): diff --git a/web/src/css/codemirror.less b/web/src/css/codemirror.less index f88ea8b1..4ac16051 100644 --- a/web/src/css/codemirror.less +++ b/web/src/css/codemirror.less @@ -1,7 +1,6 @@ .CodeMirror { border: 1px solid #ccc; height: auto !important; - max-height: 2048px !important; } @import (inline) "../../node_modules/codemirror/lib/codemirror.css"; |
