Repository: mitmproxy/mitmproxy

Issue:
query string arrays are not fully displayed
##### Steps to reproduce the problem:

1. visit through mitmproxy/mitmdump e.g. http://example.com/?first=value&arr[]=foo+bar&arr[]=baz
2. Check the query parameters in the request
3. Notice that they contain more data than mitmproxy/mitmdump shows

##### Any other comments? What have you tried so far?

The following script shows all the data:

```
#!/usr/bin/env python3

from urllib.parse import urlparse, parse_qs

url = "http://example.com/?first=value&arr[]=foo+bar&arr[]=baz"
parts = urlparse(url)
print(parse_qs(parts.query))
```

Output:
`{'first': ['value'], 'arr[]': ['foo bar', 'baz']}`

But mitmproxy/mitmdump only shows:
```
    first: value
    arr[]: foo bar
```

##### System information

<!-- Paste the output of "mitmproxy --version" here. -->

Mitmproxy: 3.0.4
Python:    3.5.2
OpenSSL:   OpenSSL 1.0.2g  1 Mar 2016
Platform:  Linux-4.10.0-42-generic-x86_64-with-Ubuntu-16.04-xenial

<!-- Please use the mitmproxy forums (https://discourse.mitmproxy.org/) for support/how-to questions. Thanks! :) -->



Discussion:
Repro with curl:
```
curl -x localhost:8080 -k -g "http://example.com/?first=value&arr[]=foo+bar&arr[]=baz"
```
(Note the `-g`)
I will to work on this.

Resolving patch:
diff --git a/mitmproxy/contentviews/base.py b/mitmproxy/contentviews/base.py
--- a/mitmproxy/contentviews/base.py
+++ b/mitmproxy/contentviews/base.py
@@ -35,32 +35,52 @@ def __call__(self, data: bytes, **metadata) -> TViewResult:
         raise NotImplementedError()  # pragma: no cover
 
 
-def format_dict(
-        d: typing.Mapping[TTextType, TTextType]
-) -> typing.Iterator[TViewLine]:
+def format_pairs(
+        items: typing.Iterable[typing.Tuple[TTextType, TTextType]]
+)-> typing.Iterator[TViewLine]:
+
     """
-    Helper function that transforms the given dictionary into a list of
+    Helper function that accepts a list of (k,v) pairs into a list of
     [
-        ("key",   key  )
+        ("key", key    )
         ("value", value)
     ]
-    entries, where key is padded to a uniform width.
+    where key is padded to a uniform width
     """
 
-    max_key_len = max((len(k) for k in d.keys()), default=0)
+    max_key_len = max((len(k[0]) for k in items), default=0)
     max_key_len = min((max_key_len, KEY_MAX), default=0)
-    for key, value in d.items():
+
+    for key, value in items:
         if isinstance(key, bytes):
+
             key += b":"
         else:
             key += ":"
+
         key = key.ljust(max_key_len + 2)
+
         yield [
             ("header", key),
             ("text", value)
         ]
 
 
+def format_dict(
+        d: typing.Mapping[TTextType, TTextType]
+) -> typing.Iterator[TViewLine]:
+    """
+    Helper function that transforms the given dictionary into a list of
+    [
+        ("key",   key  )
+        ("value", value)
+    ]
+    entries, where key is padded to a uniform width.
+    """
+
+    return format_pairs(d.items())
+
+
 def format_text(text: TTextType) -> typing.Iterator[TViewLine]:
     """
     Helper function that transforms bytes into the view output format.
diff --git a/mitmproxy/contentviews/query.py b/mitmproxy/contentviews/query.py
--- a/mitmproxy/contentviews/query.py
+++ b/mitmproxy/contentviews/query.py
@@ -9,6 +9,6 @@ class ViewQuery(base.View):
     def __call__(self, data, **metadata):
         query = metadata.get("query")
         if query:
-            return "Query", base.format_dict(query)
+            return "Query", base.format_pairs(query.items(multi=True))
         else:
             return "Query", base.format_text("")
diff --git a/mitmproxy/contentviews/urlencoded.py b/mitmproxy/contentviews/urlencoded.py
--- a/mitmproxy/contentviews/urlencoded.py
+++ b/mitmproxy/contentviews/urlencoded.py
@@ -1,5 +1,4 @@
 from mitmproxy.net.http import url
-from mitmproxy.coretypes import multidict
 from . import base
 
 
@@ -13,4 +12,4 @@ def __call__(self, data, **metadata):
         except ValueError:
             return None
         d = url.decode(data)
-        return "URLEncoded form", base.format_dict(multidict.MultiDict(d))
+        return "URLEncoded form", base.format_pairs(d)


Test patch:
diff --git a/test/mitmproxy/contentviews/test_base.py b/test/mitmproxy/contentviews/test_base.py
--- a/test/mitmproxy/contentviews/test_base.py
+++ b/test/mitmproxy/contentviews/test_base.py
@@ -15,3 +15,18 @@ def test_format_dict():
     f_d = base.format_dict(d)
     with pytest.raises(StopIteration):
         next(f_d)
+
+
+def test_format_pairs():
+    d = [("a", "c"), ("b", "d")]
+    f_d = base.format_pairs(d)
+    assert next(f_d)
+
+    d = [("abc", "")]
+    f_d = base.format_pairs(d)
+    assert next(f_d)
+
+    d = []
+    f_d = base.format_pairs(d)
+    with pytest.raises(StopIteration):
+        next(f_d)
diff --git a/test/mitmproxy/contentviews/test_query.py b/test/mitmproxy/contentviews/test_query.py
--- a/test/mitmproxy/contentviews/test_query.py
+++ b/test/mitmproxy/contentviews/test_query.py
@@ -6,8 +6,8 @@
 def test_view_query():
     d = ""
     v = full_eval(query.ViewQuery())
-    f = v(d, query=multidict.MultiDict([("foo", "bar")]))
+    f = v(d, query=multidict.MultiDict([("foo", "bar"), ("foo", "baz")]))
     assert f[0] == "Query"
-    assert f[1] == [[("header", "foo: "), ("text", "bar")]]
+    assert f[1] == [[("header", "foo: "), ("text", "bar")], [("header", "foo: "), ("text", "baz")]]
 
     assert v(d) == ("Query", [])