Repository: mitmproxy/mitmproxy

Issue:
Mitmproxy crashes, when viewing response body in url-encoded mode
##### Steps to reproduce the problem:

1. Run mitmproxy.
2. `n` -> `Enter` -> `Enter` -> `m` -> `url-encoded`

_I am seeing:_
```
Traceback (most recent call last):
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py", line 216, in run
    self.loop.run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 278, in run
    self._run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 376, in _run
    self.event_loop.run()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 682, in run
    self._loop()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 719, in _loop
    self._watch_files[fd]()
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 393, in <lambda>
    event_loop, callback, self.get_available_raw_input())
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 493, in parse_input
    callback(processed, processed_codes)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 403, in _update
    self.process_input(keys)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 308, in keypress
    k = super().keypress(size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 44, in keypress
    ret = super().keypress(size, key)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/overlay.py", line 130, in keypress
    self.master.keymap.handle("global", key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/keymap.py", line 130, in handle
    return self.executor(b.command)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/commandexecutor.py", line 17, in __call__
    ret = self.master.commands.call(cmd)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 221, in call
    return self.call_args(parts[0], parts[1:])
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 212, in call_args
    return self.commands[path].call(args)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 101, in call
    ret = self.func(*pargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/command.py", line 251, in wrapper
    return function(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 174, in nav_select
    self.master.inject_key("m_select")
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py", line 181, in inject_key
    self.loop.process_input([key])
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 308, in keypress
    k = super().keypress(size, k)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 44, in keypress
    ret = super().keypress(size, key)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/overlay.py", line 121, in keypress
    signals.pop_view_state.send(self)
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/blinker/base.py", line 267, in send
    for receiver in self.receivers_for(sender)]
  File "/home/kajoj/Mitmproxy/mitmproxy/venv/lib/python3.5/site-packages/blinker/base.py", line 267, in <listcomp>
    for receiver in self.receivers_for(sender)]
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 244, in pop
    self.focus_changed()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 209, in focus_changed
    i.call("focus_changed")
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 118, in call
    getattr(self.top_window(), name)(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 233, in focus_changed
    self.body.focus_changed()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 58, in focus_changed
    self.show()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/tabs.py", line 73, in show
    body = self.tabs[self.tab_offset][1](),
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 86, in view_request
    return self.conn_text(self.flow.request)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 185, in conn_text
    msg, body = self.content_view(viewmode, conn)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 112, in content_view
    return self._get_content_view(viewmode, limit, flow_modify_cache_invalidation)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/flowview.py", line 132, in _get_content_view
    for line in lines:
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/contentviews/__init__.py", line 85, in safe_to_print
    for line in lines:
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/contentviews/base.py", line 52, in format_dict
    max_key_len = max(len(k) for k in d.keys())
ValueError: max() arg is an empty sequence
```

##### Any other comments? What have you tried so far?

This issue is reproducible not only for request/response without body, but for `query` (maybe some others) content type as well. I continue digging.

##### System information

Mitmproxy: 3.0.0.dev53 (commit 3b5237c) 
Python:    3.5.2
OpenSSL:   OpenSSL 1.1.0g  2 Nov 2017
Platform:  Linux-4.4.0-109-generic-x86_64-with-Ubuntu-16.04-xenial



Discussion:

Resolving patch:
diff --git a/mitmproxy/contentviews/base.py b/mitmproxy/contentviews/base.py
--- a/mitmproxy/contentviews/base.py
+++ b/mitmproxy/contentviews/base.py
@@ -49,8 +49,9 @@ def format_dict(
     ]
     entries, where key is padded to a uniform width.
     """
-    max_key_len = max(len(k) for k in d.keys())
-    max_key_len = min(max_key_len, KEY_MAX)
+
+    max_key_len = max((len(k) for k in d.keys()), default=0)
+    max_key_len = min((max_key_len, KEY_MAX), default=0)
     for key, value in d.items():
         if isinstance(key, bytes):
             key += b":"


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
@@ -1 +1,17 @@
-# TODO: write tests
+import pytest
+from mitmproxy.contentviews import base
+
+
+def test_format_dict():
+    d = {"one": "two", "three": "four"}
+    f_d = base.format_dict(d)
+    assert next(f_d)
+
+    d = {"adsfa": ""}
+    f_d = base.format_dict(d)
+    assert next(f_d)
+
+    d = {}
+    f_d = base.format_dict(d)
+    with pytest.raises(StopIteration):
+        next(f_d)