We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
MITM Proxy Crashes on when editing a "form"
Stack trace: 
```
Traceback (most recent call last):
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/master.py", line 202, in run
    self.loop.run()
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 278, in run
    self._run()
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 376, in _run
    self.event_loop.run()
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 682, in run
    self._loop()
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 719, in _loop
    self._watch_files[fd]()
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/raw_display.py", line 393, in <lambda>
    event_loop, callback, self.get_available_raw_input())
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/raw_display.py", line 493, in parse_input
    callback(processed, processed_codes)
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 403, in _update
    self.process_input(keys)
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py", line 274, in keypress
    k = fs.keypress(size, k)
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/overlay.py", line 117, in keypress
    key = self.master.keymap.handle("chooser", key)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/keymap.py", line 123, in handle
    return self.executor(b.command)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/commandeditor.py", line 24, in __call__
    ret = self.master.commands.call(cmd)
  File "/Users/ograff/mitmproxy/mitmproxy/command.py", line 144, in call
    return self.call_args(parts[0], parts[1:])
  File "/Users/ograff/mitmproxy/mitmproxy/command.py", line 135, in call_args
    return self.commands[path].call(args)
  File "/Users/ograff/mitmproxy/mitmproxy/command.py", line 106, in call
    ret = self.func(*pargs)
  File "/Users/ograff/mitmproxy/mitmproxy/command.py", line 197, in wrapper
    return function(*args, **kwargs)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/consoleaddons.py", line 125, in nav_select
    self.master.inject_key("m_select")
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/master.py", line 178, in inject_key
    self.loop.process_input([key])
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py", line 503, in process_input
    k = self._topmost_widget.keypress(self.screen_size, k)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py", line 274, in keypress
    k = fs.keypress(size, k)
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/container.py", line 592, in keypress
    *self.calculate_padding_filler(size, True)), key)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/overlay.py", line 120, in keypress
    signals.pop_view_state.send(self)
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/blinker/base.py", line 267, in send
    for receiver in self.receivers_for(sender)]
  File "/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/blinker/base.py", line 267, in <listcomp>
    for receiver in self.receivers_for(sender)]
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py", line 207, in pop
    if self.focus_stack().pop():
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py", line 81, in pop
    self.call("layout_popping")
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py", line 93, in call
    getattr(self.top_window(), name)(*args, **kwargs)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py", line 463, in layout_popping
    self.call(self._w, "layout_popping")
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py", line 441, in call
    f(*args, **kwargs)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py", line 313, in layout_popping
    self.callback(self.data_out(res), *self.cb_args, **self.cb_kwargs)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py", line 456, in set_data_update
    self.set_data(vals, flow)
  File "/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/editors.py", line 64, in set_data
    flow.request.urlencoded_form = vals
  File "/Users/ograff/mitmproxy/mitmproxy/net/http/request.py", line 462, in urlencoded_form
    self._set_urlencoded_form(value)
  File "/Users/ograff/mitmproxy/mitmproxy/net/http/request.py", line 444, in _set_urlencoded_form
    self.content = mitmproxy.net.http.url.encode(form_data, self.content.decode()).encode()
  File "/Users/ograff/mitmproxy/mitmproxy/net/http/url.py", line 81, in encode
    if encoded[-1] == '=':
IndexError: string index out of range
```

mitmproxy has crashed!
Please lodge a bug report at:
	https://github.com/mitmproxy/mitmproxy
Shutting down...

##### Steps to reproduce the problem:

1. Enter a flow
2. Press "e"
3. Select "form"


##### Any other comments? What have you tried so far?
`encoded` is an empty string but probably shouldn't be. 
Changing `mitmproxy/net/http/url.py` to just check for an empty string and not index into it if its empty results in an empty request body after exiting the editor.

##### System information


```
Mitmproxy version: 3.0.0 (2.0.0dev0631-0x30927468)
Python version: 3.6.0
Platform: Darwin-16.5.0-x86_64-i386-64bit
SSL version: OpenSSL 1.1.0f  25 May 2017
Mac version: 10.12.4 ('', '', '') x86_64
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mitmproxy/net/http/url.py`
Content:
```
1 import urllib.parse
2 from typing import Sequence
3 from typing import Tuple
4 
5 from mitmproxy.net import check
6 
7 
8 def parse(url):
9     """
10         URL-parsing function that checks that
11             - port is an integer 0-65535
12             - host is a valid IDNA-encoded hostname with no null-bytes
13             - path is valid ASCII
14 
15         Args:
16             A URL (as bytes or as unicode)
17 
18         Returns:
19             A (scheme, host, port, path) tuple
20 
21         Raises:
22             ValueError, if the URL is not properly formatted.
23     """
24     parsed = urllib.parse.urlparse(url)
25 
26     if not parsed.hostname:
27         raise ValueError("No hostname given")
28 
29     if isinstance(url, bytes):
30         host = parsed.hostname
31 
32         # this should not raise a ValueError,
33         # but we try to be very forgiving here and accept just everything.
34     else:
35         host = parsed.hostname.encode("idna")
36         if isinstance(parsed, urllib.parse.ParseResult):
37             parsed = parsed.encode("ascii")
38 
39     port = parsed.port  # Returns None if port number invalid in Py3.5. Will throw ValueError in Py3.6
40     if not port:
41         port = 443 if parsed.scheme == b"https" else 80
42 
43     full_path = urllib.parse.urlunparse(
44         (b"", b"", parsed.path, parsed.params, parsed.query, parsed.fragment)
45     )
46     if not full_path.startswith(b"/"):
47         full_path = b"/" + full_path
48 
49     if not check.is_valid_host(host):
50         raise ValueError("Invalid Host")
51 
52     return parsed.scheme, host, port, full_path
53 
54 
55 def unparse(scheme, host, port, path=""):
56     """
57     Returns a URL string, constructed from the specified components.
58 
59     Args:
60         All args must be str.
61     """
62     if path == "*":
63         path = ""
64     return "%s://%s%s" % (scheme, hostport(scheme, host, port), path)
65 
66 
67 def encode(s: Sequence[Tuple[str, str]], similar_to: str=None) -> str:
68     """
69         Takes a list of (key, value) tuples and returns a urlencoded string.
70         If similar_to is passed, the output is formatted similar to the provided urlencoded string.
71     """
72 
73     remove_trailing_equal = False
74     if similar_to:
75         remove_trailing_equal = any("=" not in param for param in similar_to.split("&"))
76 
77     encoded = urllib.parse.urlencode(s, False, errors="surrogateescape")
78 
79     if remove_trailing_equal:
80         encoded = encoded.replace("=&", "&")
81         if encoded[-1] == '=':
82             encoded = encoded[:-1]
83 
84     return encoded
85 
86 
87 def decode(s):
88     """
89         Takes a urlencoded string and returns a list of surrogate-escaped (key, value) tuples.
90     """
91     return urllib.parse.parse_qsl(s, keep_blank_values=True, errors='surrogateescape')
92 
93 
94 def quote(b: str, safe: str="/") -> str:
95     """
96     Returns:
97         An ascii-encodable str.
98     """
99     return urllib.parse.quote(b, safe=safe, errors="surrogateescape")
100 
101 
102 def unquote(s: str) -> str:
103     """
104     Args:
105         s: A surrogate-escaped str
106     Returns:
107         A surrogate-escaped str
108     """
109     return urllib.parse.unquote(s, errors="surrogateescape")
110 
111 
112 def hostport(scheme, host, port):
113     """
114         Returns the host component, with a port specifcation if needed.
115     """
116     if (port, scheme) in [(80, "http"), (443, "https"), (80, b"http"), (443, b"https")]:
117         return host
118     else:
119         if isinstance(host, bytes):
120             return b"%s:%d" % (host, port)
121         else:
122             return "%s:%d" % (host, port)
123
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mitmproxy/net/http/url.py b/mitmproxy/net/http/url.py
--- a/mitmproxy/net/http/url.py
+++ b/mitmproxy/net/http/url.py
@@ -76,7 +76,7 @@
 
     encoded = urllib.parse.urlencode(s, False, errors="surrogateescape")
 
-    if remove_trailing_equal:
+    if encoded and remove_trailing_equal:
         encoded = encoded.replace("=&", "&")
         if encoded[-1] == '=':
             encoded = encoded[:-1]

{"golden_diff": "diff --git a/mitmproxy/net/http/url.py b/mitmproxy/net/http/url.py\n--- a/mitmproxy/net/http/url.py\n+++ b/mitmproxy/net/http/url.py\n@@ -76,7 +76,7 @@\n \n     encoded = urllib.parse.urlencode(s, False, errors=\"surrogateescape\")\n \n-    if remove_trailing_equal:\n+    if encoded and remove_trailing_equal:\n         encoded = encoded.replace(\"=&\", \"&\")\n         if encoded[-1] == '=':\n             encoded = encoded[:-1]\n", "issue": "MITM Proxy Crashes on when editing a \"form\"\nStack trace: \r\n```\r\nTraceback (most recent call last):\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/master.py\", line 202, in run\r\n    self.loop.run()\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 278, in run\r\n    self._run()\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 376, in _run\r\n    self.event_loop.run()\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 682, in run\r\n    self._loop()\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 719, in _loop\r\n    self._watch_files[fd]()\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/raw_display.py\", line 393, in <lambda>\r\n    event_loop, callback, self.get_available_raw_input())\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/raw_display.py\", line 493, in parse_input\r\n    callback(processed, processed_codes)\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 403, in _update\r\n    self.process_input(keys)\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 503, in process_input\r\n    k = self._topmost_widget.keypress(self.screen_size, k)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py\", line 274, in keypress\r\n    k = fs.keypress(size, k)\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/container.py\", line 592, in keypress\r\n    *self.calculate_padding_filler(size, True)), key)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/overlay.py\", line 117, in keypress\r\n    key = self.master.keymap.handle(\"chooser\", key)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/keymap.py\", line 123, in handle\r\n    return self.executor(b.command)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/commandeditor.py\", line 24, in __call__\r\n    ret = self.master.commands.call(cmd)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/command.py\", line 144, in call\r\n    return self.call_args(parts[0], parts[1:])\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/command.py\", line 135, in call_args\r\n    return self.commands[path].call(args)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/command.py\", line 106, in call\r\n    ret = self.func(*pargs)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/command.py\", line 197, in wrapper\r\n    return function(*args, **kwargs)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/consoleaddons.py\", line 125, in nav_select\r\n    self.master.inject_key(\"m_select\")\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/master.py\", line 178, in inject_key\r\n    self.loop.process_input([key])\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/main_loop.py\", line 503, in process_input\r\n    k = self._topmost_widget.keypress(self.screen_size, k)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py\", line 274, in keypress\r\n    k = fs.keypress(size, k)\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/urwid/container.py\", line 592, in keypress\r\n    *self.calculate_padding_filler(size, True)), key)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/overlay.py\", line 120, in keypress\r\n    signals.pop_view_state.send(self)\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/blinker/base.py\", line 267, in send\r\n    for receiver in self.receivers_for(sender)]\r\n  File \"/Users/ograff/mitmproxy/venv/lib/python3.6/site-packages/blinker/base.py\", line 267, in <listcomp>\r\n    for receiver in self.receivers_for(sender)]\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py\", line 207, in pop\r\n    if self.focus_stack().pop():\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py\", line 81, in pop\r\n    self.call(\"layout_popping\")\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/window.py\", line 93, in call\r\n    getattr(self.top_window(), name)(*args, **kwargs)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py\", line 463, in layout_popping\r\n    self.call(self._w, \"layout_popping\")\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py\", line 441, in call\r\n    f(*args, **kwargs)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py\", line 313, in layout_popping\r\n    self.callback(self.data_out(res), *self.cb_args, **self.cb_kwargs)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/base.py\", line 456, in set_data_update\r\n    self.set_data(vals, flow)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/tools/console/grideditor/editors.py\", line 64, in set_data\r\n    flow.request.urlencoded_form = vals\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/net/http/request.py\", line 462, in urlencoded_form\r\n    self._set_urlencoded_form(value)\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/net/http/request.py\", line 444, in _set_urlencoded_form\r\n    self.content = mitmproxy.net.http.url.encode(form_data, self.content.decode()).encode()\r\n  File \"/Users/ograff/mitmproxy/mitmproxy/net/http/url.py\", line 81, in encode\r\n    if encoded[-1] == '=':\r\nIndexError: string index out of range\r\n```\r\n\r\nmitmproxy has crashed!\r\nPlease lodge a bug report at:\r\n\thttps://github.com/mitmproxy/mitmproxy\r\nShutting down...\r\n\r\n##### Steps to reproduce the problem:\r\n\r\n1. Enter a flow\r\n2. Press \"e\"\r\n3. Select \"form\"\r\n\r\n\r\n##### Any other comments? What have you tried so far?\r\n`encoded` is an empty string but probably shouldn't be. \r\nChanging `mitmproxy/net/http/url.py` to just check for an empty string and not index into it if its empty results in an empty request body after exiting the editor.\r\n\r\n##### System information\r\n\r\n\r\n```\r\nMitmproxy version: 3.0.0 (2.0.0dev0631-0x30927468)\r\nPython version: 3.6.0\r\nPlatform: Darwin-16.5.0-x86_64-i386-64bit\r\nSSL version: OpenSSL 1.1.0f  25 May 2017\r\nMac version: 10.12.4 ('', '', '') x86_64\r\n```\n", "before_files": [{"content": "import urllib.parse\nfrom typing import Sequence\nfrom typing import Tuple\n\nfrom mitmproxy.net import check\n\n\ndef parse(url):\n    \"\"\"\n        URL-parsing function that checks that\n            - port is an integer 0-65535\n            - host is a valid IDNA-encoded hostname with no null-bytes\n            - path is valid ASCII\n\n        Args:\n            A URL (as bytes or as unicode)\n\n        Returns:\n            A (scheme, host, port, path) tuple\n\n        Raises:\n            ValueError, if the URL is not properly formatted.\n    \"\"\"\n    parsed = urllib.parse.urlparse(url)\n\n    if not parsed.hostname:\n        raise ValueError(\"No hostname given\")\n\n    if isinstance(url, bytes):\n        host = parsed.hostname\n\n        # this should not raise a ValueError,\n        # but we try to be very forgiving here and accept just everything.\n    else:\n        host = parsed.hostname.encode(\"idna\")\n        if isinstance(parsed, urllib.parse.ParseResult):\n            parsed = parsed.encode(\"ascii\")\n\n    port = parsed.port  # Returns None if port number invalid in Py3.5. Will throw ValueError in Py3.6\n    if not port:\n        port = 443 if parsed.scheme == b\"https\" else 80\n\n    full_path = urllib.parse.urlunparse(\n        (b\"\", b\"\", parsed.path, parsed.params, parsed.query, parsed.fragment)\n    )\n    if not full_path.startswith(b\"/\"):\n        full_path = b\"/\" + full_path\n\n    if not check.is_valid_host(host):\n        raise ValueError(\"Invalid Host\")\n\n    return parsed.scheme, host, port, full_path\n\n\ndef unparse(scheme, host, port, path=\"\"):\n    \"\"\"\n    Returns a URL string, constructed from the specified components.\n\n    Args:\n        All args must be str.\n    \"\"\"\n    if path == \"*\":\n        path = \"\"\n    return \"%s://%s%s\" % (scheme, hostport(scheme, host, port), path)\n\n\ndef encode(s: Sequence[Tuple[str, str]], similar_to: str=None) -> str:\n    \"\"\"\n        Takes a list of (key, value) tuples and returns a urlencoded string.\n        If similar_to is passed, the output is formatted similar to the provided urlencoded string.\n    \"\"\"\n\n    remove_trailing_equal = False\n    if similar_to:\n        remove_trailing_equal = any(\"=\" not in param for param in similar_to.split(\"&\"))\n\n    encoded = urllib.parse.urlencode(s, False, errors=\"surrogateescape\")\n\n    if remove_trailing_equal:\n        encoded = encoded.replace(\"=&\", \"&\")\n        if encoded[-1] == '=':\n            encoded = encoded[:-1]\n\n    return encoded\n\n\ndef decode(s):\n    \"\"\"\n        Takes a urlencoded string and returns a list of surrogate-escaped (key, value) tuples.\n    \"\"\"\n    return urllib.parse.parse_qsl(s, keep_blank_values=True, errors='surrogateescape')\n\n\ndef quote(b: str, safe: str=\"/\") -> str:\n    \"\"\"\n    Returns:\n        An ascii-encodable str.\n    \"\"\"\n    return urllib.parse.quote(b, safe=safe, errors=\"surrogateescape\")\n\n\ndef unquote(s: str) -> str:\n    \"\"\"\n    Args:\n        s: A surrogate-escaped str\n    Returns:\n        A surrogate-escaped str\n    \"\"\"\n    return urllib.parse.unquote(s, errors=\"surrogateescape\")\n\n\ndef hostport(scheme, host, port):\n    \"\"\"\n        Returns the host component, with a port specifcation if needed.\n    \"\"\"\n    if (port, scheme) in [(80, \"http\"), (443, \"https\"), (80, b\"http\"), (443, b\"https\")]:\n        return host\n    else:\n        if isinstance(host, bytes):\n            return b\"%s:%d\" % (host, port)\n        else:\n            return \"%s:%d\" % (host, port)\n", "path": "mitmproxy/net/http/url.py"}], "after_files": [{"content": "import urllib.parse\nfrom typing import Sequence\nfrom typing import Tuple\n\nfrom mitmproxy.net import check\n\n\ndef parse(url):\n    \"\"\"\n        URL-parsing function that checks that\n            - port is an integer 0-65535\n            - host is a valid IDNA-encoded hostname with no null-bytes\n            - path is valid ASCII\n\n        Args:\n            A URL (as bytes or as unicode)\n\n        Returns:\n            A (scheme, host, port, path) tuple\n\n        Raises:\n            ValueError, if the URL is not properly formatted.\n    \"\"\"\n    parsed = urllib.parse.urlparse(url)\n\n    if not parsed.hostname:\n        raise ValueError(\"No hostname given\")\n\n    if isinstance(url, bytes):\n        host = parsed.hostname\n\n        # this should not raise a ValueError,\n        # but we try to be very forgiving here and accept just everything.\n    else:\n        host = parsed.hostname.encode(\"idna\")\n        if isinstance(parsed, urllib.parse.ParseResult):\n            parsed = parsed.encode(\"ascii\")\n\n    port = parsed.port  # Returns None if port number invalid in Py3.5. Will throw ValueError in Py3.6\n    if not port:\n        port = 443 if parsed.scheme == b\"https\" else 80\n\n    full_path = urllib.parse.urlunparse(\n        (b\"\", b\"\", parsed.path, parsed.params, parsed.query, parsed.fragment)\n    )\n    if not full_path.startswith(b\"/\"):\n        full_path = b\"/\" + full_path\n\n    if not check.is_valid_host(host):\n        raise ValueError(\"Invalid Host\")\n\n    return parsed.scheme, host, port, full_path\n\n\ndef unparse(scheme, host, port, path=\"\"):\n    \"\"\"\n    Returns a URL string, constructed from the specified components.\n\n    Args:\n        All args must be str.\n    \"\"\"\n    if path == \"*\":\n        path = \"\"\n    return \"%s://%s%s\" % (scheme, hostport(scheme, host, port), path)\n\n\ndef encode(s: Sequence[Tuple[str, str]], similar_to: str=None) -> str:\n    \"\"\"\n        Takes a list of (key, value) tuples and returns a urlencoded string.\n        If similar_to is passed, the output is formatted similar to the provided urlencoded string.\n    \"\"\"\n\n    remove_trailing_equal = False\n    if similar_to:\n        remove_trailing_equal = any(\"=\" not in param for param in similar_to.split(\"&\"))\n\n    encoded = urllib.parse.urlencode(s, False, errors=\"surrogateescape\")\n\n    if encoded and remove_trailing_equal:\n        encoded = encoded.replace(\"=&\", \"&\")\n        if encoded[-1] == '=':\n            encoded = encoded[:-1]\n\n    return encoded\n\n\ndef decode(s):\n    \"\"\"\n        Takes a urlencoded string and returns a list of surrogate-escaped (key, value) tuples.\n    \"\"\"\n    return urllib.parse.parse_qsl(s, keep_blank_values=True, errors='surrogateescape')\n\n\ndef quote(b: str, safe: str=\"/\") -> str:\n    \"\"\"\n    Returns:\n        An ascii-encodable str.\n    \"\"\"\n    return urllib.parse.quote(b, safe=safe, errors=\"surrogateescape\")\n\n\ndef unquote(s: str) -> str:\n    \"\"\"\n    Args:\n        s: A surrogate-escaped str\n    Returns:\n        A surrogate-escaped str\n    \"\"\"\n    return urllib.parse.unquote(s, errors=\"surrogateescape\")\n\n\ndef hostport(scheme, host, port):\n    \"\"\"\n        Returns the host component, with a port specifcation if needed.\n    \"\"\"\n    if (port, scheme) in [(80, \"http\"), (443, \"https\"), (80, b\"http\"), (443, b\"https\")]:\n        return host\n    else:\n        if isinstance(host, bytes):\n            return b\"%s:%d\" % (host, port)\n        else:\n            return \"%s:%d\" % (host, port)\n", "path": "mitmproxy/net/http/url.py"}]}