Repository: mitmproxy/mitmproxy

Issue:
Editing set-cookies while no response is present crashes mitmproxy
##### Steps to reproduce the problem:

1. Create a new flow by pressing `n`.
2. Enter, e -> set-cookies

```
Traceback (most recent call last):
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/master.py", line 216, in run
    self.loop.run()
  File "/home/user/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 278, in run
    self._run()
  File "/home/user/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 376, in _run
    self.event_loop.run()
  File "/home/user/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 682, in run
    self._loop()
  File "/home/user/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 719, in _loop
    self._watch_files[fd]()
  File "/home/user/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 393, in <lambda>
    event_loop, callback, self.get_available_raw_input())
  File "/home/user/venv/lib/python3.5/site-packages/urwid/raw_display.py", line 493, in parse_input
    callback(processed, processed_codes)
  File "/home/user/venv/lib/python3.5/site-packages/urwid/main_loop.py", line 403, in _update
    self.process_input(keys)
  File "/home/user/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 "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 299, in keypress
    k = super().keypress(size, k)
  File "/home/user/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 36, in keypress
    ret = super().keypress(size, key)
  File "/home/user/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/user/venv/lib/python3.5/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/overlay.py", line 130, in keypress
    self.master.keymap.handle("global", key)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/keymap.py", line 130, in handle
    return self.executor(b.command)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/commandexecutor.py", line 17, in __call__
    ret = self.master.commands.call(cmd)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 221, in call
    return self.call_args(parts[0], parts[1:])
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 212, in call_args
    return self.commands[path].call(args)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 101, in call
    ret = self.func(*pargs)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 251, in wrapper
    return function(*args, **kwargs)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 174, in nav_select
    self.master.inject_key("m_select")
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/master.py", line 181, in inject_key
    self.loop.process_input([key])
  File "/home/user/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 "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 299, in keypress
    k = super().keypress(size, k)
  File "/home/user/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 36, in keypress
    ret = super().keypress(size, key)
  File "/home/user/venv/lib/python3.5/site-packages/urwid/container.py", line 1128, in keypress
    return self.body.keypress( (maxcol, remaining), key )
  File "/home/user/venv/lib/python3.5/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/overlay.py", line 120, in keypress
    self.callback(self.choices[self.walker.index])
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 264, in callback
    self.master.commands.call(subcmd + " " + repl)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 221, in call
    return self.call_args(parts[0], parts[1:])
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 212, in call_args
    return self.commands[path].call(args)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 101, in call
    ret = self.func(*pargs)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/command.py", line 251, in wrapper
    return function(*args, **kwargs)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 409, in edit_focus
    self.master.switch_view("edit_focus_setcookies")
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/master.py", line 236, in switch_view
    self.window.push(name)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 223, in push
    self.focus_changed()
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 200, in focus_changed
    i.call("focus_changed")
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/window.py", line 110, in call
    getattr(self.top_window(), name)(*args, **kwargs)
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/grideditor/base.py", line 471, in focus_changed
    self.get_data(self.master.view.focus.flow),
  File "/mnt/c/Users/user/git/mitmproxy/mitmproxy/tools/console/grideditor/editors.py", line 149, in get_data
    return self.data_in(flow.response.cookies.items(multi=True))
AttributeError: 'NoneType' object has no attribute 'cookies'
```

##### System information

Mitmproxy: 3.0.0.dev0002 (commit 299df48)
Python: 3.5.2
OpenSSL: OpenSSL 1.1.0g 2 Nov 2017
Platform: Linux-4.4.0-43-Microsoft-x86_64-with-Ubuntu-16.04-xenial


Discussion:
The same situation with `response-headers`:
```
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 305, 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 47, 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 305, 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 47, 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 120, in keypress
    self.callback(self.choices[self.walker.index])
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 264, in callback
    self.master.commands.call(subcmd + " " + repl)
  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 392, in edit_focus
    self.master.switch_view("edit_focus_response_headers")
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/master.py", line 236, in switch_view
    self.window.push(name)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 235, in push
    self.focus_changed()
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 212, in focus_changed
    i.call("focus_changed")
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/window.py", line 121, in call
    getattr(self.top_window(), name)(*args, **kwargs)
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/grideditor/base.py", line 471, in focus_changed
    self.get_data(self.master.view.focus.flow),
  File "/home/kajoj/Mitmproxy/mitmproxy/mitmproxy/tools/console/grideditor/editors.py", line 48, in get_data
    return flow.response.headers.fields
AttributeError: 'NoneType' object has no attribute 'headers'
```


Resolving patch:
diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py
--- a/mitmproxy/tools/console/consoleaddons.py
+++ b/mitmproxy/tools/console/consoleaddons.py
@@ -5,6 +5,7 @@
 from mitmproxy import command
 from mitmproxy import exceptions
 from mitmproxy import flow
+from mitmproxy import http
 from mitmproxy import contentviews
 from mitmproxy.utils import strutils
 import mitmproxy.types
@@ -378,6 +379,12 @@ def edit_focus(self, part: str) -> None:
         # but for now it is.
         if not flow:
             raise exceptions.CommandError("No flow selected.")
+        require_dummy_response = (
+            part in ("response-headers", "response-body", "set-cookies") and
+            flow.response is None
+        )
+        if require_dummy_response:
+            flow.response = http.HTTPResponse.make()
         if part == "cookies":
             self.master.switch_view("edit_focus_cookies")
         elif part == "form":
@@ -395,8 +402,6 @@ def edit_focus(self, part: str) -> None:
                 message = flow.request
             else:
                 message = flow.response
-            if not message:
-                raise exceptions.CommandError("Flow has no {}.".format(part.split("-")[0]))
             c = self.master.spawn_editor(message.get_content(strict=False) or b"")
             # Fix an issue caused by some editors when editing a
             # request/response body. Many editors make it hard to save a


Test patch: