Repository: mitmproxy/mitmproxy

Issue:
har_dump throwing error when body content has weird characters 
##### Steps to reproduce the problem:
1. Use har_dump.py as script
2. Browser lot of things
3. Eventually you will errors like this

```
Error: Script error: Traceback (most recent call last):
  File "/Users/manish/Work/mitmproxy/mitmproxy/builtins/script.py", line 146, in run
    return func(*args, **kwargs)
  File "har_dump.py", line 96, in response
    response_body_decoded_size = len(flow.response.content)
  File "/Users/manish/Work/mitmproxy/netlib/http/message.py", line 115, in get_content
    return encoding.decode(self.raw_content, ce)
  File "/Users/manish/Work/mitmproxy/netlib/encoding.py", line 56, in decode
    repr(encoding),
ValueError: AssertionError when decoding "\x87-\x00 with 'br'
```
##### What is the expected behavior?

Not throw error
##### What went wrong?

These characters are weird, i hate them
##### Any other comments? What have you tried so far?

none

---

Mitmproxy Version: Head (1.8 i guess)
Operating System: mac 

<!-- Please use the mitmproxy forums (https://discourse.mitmproxy.org/) for support/how-to questions. Thanks! :) -->



Discussion:

Resolving patch:
diff --git a/netlib/encoding.py b/netlib/encoding.py
--- a/netlib/encoding.py
+++ b/netlib/encoding.py
@@ -5,7 +5,9 @@
 
 import codecs
 import collections
+import six
 from io import BytesIO
+
 import gzip
 import zlib
 import brotli
@@ -32,6 +34,9 @@ def decode(encoded, encoding, errors='strict'):
     Raises:
         ValueError, if decoding fails.
     """
+    if len(encoded) == 0:
+        return encoded
+
     global _cache
     cached = (
         isinstance(encoded, bytes) and
@@ -49,11 +54,14 @@ def decode(encoded, encoding, errors='strict'):
         if encoding in ("gzip", "deflate", "br"):
             _cache = CachedDecode(encoded, encoding, errors, decoded)
         return decoded
+    except TypeError:
+        raise
     except Exception as e:
-        raise ValueError("{} when decoding {} with {}".format(
+        raise ValueError("{} when decoding {} with {}: {}".format(
             type(e).__name__,
             repr(encoded)[:10],
             repr(encoding),
+            repr(e),
         ))
 
 
@@ -68,6 +76,9 @@ def encode(decoded, encoding, errors='strict'):
     Raises:
         ValueError, if encoding fails.
     """
+    if len(decoded) == 0:
+        return decoded
+
     global _cache
     cached = (
         isinstance(decoded, bytes) and
@@ -79,17 +90,23 @@ def encode(decoded, encoding, errors='strict'):
         return _cache.encoded
     try:
         try:
-            encoded = custom_encode[encoding](decoded)
+            value = decoded
+            if not six.PY2 and isinstance(value, six.string_types):
+                value = decoded.encode()
+            encoded = custom_encode[encoding](value)
         except KeyError:
             encoded = codecs.encode(decoded, encoding, errors)
         if encoding in ("gzip", "deflate", "br"):
             _cache = CachedDecode(encoded, encoding, errors, decoded)
         return encoded
+    except TypeError:
+        raise
     except Exception as e:
-        raise ValueError("{} when encoding {} with {}".format(
+        raise ValueError("{} when encoding {} with {}: {}".format(
             type(e).__name__,
             repr(decoded)[:10],
             repr(encoding),
+            repr(e),
         ))
 
 
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -85,7 +85,7 @@
         "tornado>=4.3, <4.5",
         "urwid>=1.3.1, <1.4",
         "watchdog>=0.8.3, <0.9",
-        "brotlipy>=0.3.0, <0.5",
+        "brotlipy>=0.5.1, <0.6",
     ],
     extras_require={
         ':sys_platform == "win32"': [


Test patch:
diff --git a/test/netlib/test_encoding.py b/test/netlib/test_encoding.py
--- a/test/netlib/test_encoding.py
+++ b/test/netlib/test_encoding.py
@@ -1,4 +1,6 @@
 import mock
+import pytest
+
 from netlib import encoding, tutils
 
 
@@ -9,47 +11,32 @@ def test_identity():
         encoding.encode(b"string", "nonexistent encoding")
 
 
-def test_gzip():
-    assert b"string" == encoding.decode(
-        encoding.encode(
-            b"string",
-            "gzip"
-        ),
-        "gzip"
-    )
-    with tutils.raises(ValueError):
-        encoding.decode(b"bogus", "gzip")
+@pytest.mark.parametrize("encoder", [
+    'gzip',
+    'br',
+    'deflate',
+])
+def test_encoders(encoder):
+    assert "" == encoding.decode("", encoder)
+    assert b"" == encoding.decode(b"", encoder)
 
-
-def test_brotli():
-    assert b"string" == encoding.decode(
+    assert "string" == encoding.decode(
         encoding.encode(
-            b"string",
-            "br"
+            "string",
+            encoder
         ),
-        "br"
+        encoder
     )
-    with tutils.raises(ValueError):
-        encoding.decode(b"bogus", "br")
-
-
-def test_deflate():
     assert b"string" == encoding.decode(
         encoding.encode(
             b"string",
-            "deflate"
+            encoder
         ),
-        "deflate"
-    )
-    assert b"string" == encoding.decode(
-        encoding.encode(
-            b"string",
-            "deflate"
-        )[2:-4],
-        "deflate"
+        encoder
     )
+
     with tutils.raises(ValueError):
-        encoding.decode(b"bogus", "deflate")
+        encoding.decode(b"foobar", encoder)
 
 
 def test_cache():