Repository: mitmproxy/mitmproxy

Issue:
Improper handling of Punycode
#### Problem Description
Can't open an address like `https://стопкоронавирус.рф/` through `mitmproxy` or other applications. My upstream proxy receives a CONNECT request to https://СЃС‚РѕРїРєРѕСЂРѕРЅР°РІРёСЂСѓСЃ.СЂС„:443 instead. As the current run of `mitmproxy` was supposed to be just a test, it was configured only to forward all requests as-is to the upstream proxy, so this rules out any and all issues that could arise from my tinkering. Note: the actual URL that the browser opens is `https://xn--80aesfpebagmfblc0a.xn--p1ai` in this case.

Did it fail to properly encode the resulting authority? My upstream proxy normally has no issues with opening Puny-encoded URLs. I can verify that by opening that URL bypassing `mitmproxy`. It looks like it uses the wrong encoding, as it reminds me of the time when Unicode was not widespread and so this is how text in Russian would display when the text encoding wasn't set correctly.

#### Steps to reproduce the behavior:
1. Configure `mitmproxy` to forward all requests as-is to the upstream proxy that optionally can report what requests it receives. This includes no HTTPS decryption.
2. Navigate your browser to `https://стопкоронавирус.рф/`.
3. Check what the authority part of the URL the upstream proxy gets, it should be mangled.

#### System Information
Paste the output of "mitmproxy --version" here.

```Mitmproxy: 8.0.0 binary
Python:    3.10.2
OpenSSL:   OpenSSL 1.1.1n  15 Mar 2022
Platform:  Windows-10-10.0.19043-SP0
```


Discussion:
Repro:
```
mitmdump
mitmdump --mode upstream:https://localhost:8080 -p 8081 --set ssl_insecure
curl -x localhost:8081 -k https://стопкоронавирус.рф/
```

Resolving patch:
diff --git a/mitmproxy/proxy/layers/http/_upstream_proxy.py b/mitmproxy/proxy/layers/http/_upstream_proxy.py
--- a/mitmproxy/proxy/layers/http/_upstream_proxy.py
+++ b/mitmproxy/proxy/layers/http/_upstream_proxy.py
@@ -46,12 +46,13 @@ def start_handshake(self) -> layer.CommandGenerator[None]:
             return (yield from super().start_handshake())
         assert self.conn.address
         flow = http.HTTPFlow(self.context.client, self.tunnel_connection)
+        authority = self.conn.address[0].encode("idna") + f":{self.conn.address[1]}".encode()
         flow.request = http.Request(
             host=self.conn.address[0],
             port=self.conn.address[1],
             method=b"CONNECT",
             scheme=b"",
-            authority=f"{self.conn.address[0]}:{self.conn.address[1]}".encode(),
+            authority=authority,
             path=b"",
             http_version=b"HTTP/1.1",
             headers=http.Headers(),


Test patch:
diff --git a/test/mitmproxy/proxy/layers/http/test_http.py b/test/mitmproxy/proxy/layers/http/test_http.py
--- a/test/mitmproxy/proxy/layers/http/test_http.py
+++ b/test/mitmproxy/proxy/layers/http/test_http.py
@@ -722,8 +722,9 @@ def test_server_aborts(tctx, data):
 
 
 @pytest.mark.parametrize("redirect", ["", "change-destination", "change-proxy"])
+@pytest.mark.parametrize("domain", [b"example.com", b"xn--eckwd4c7c.xn--zckzah"])
 @pytest.mark.parametrize("scheme", ["http", "https"])
-def test_upstream_proxy(tctx, redirect, scheme):
+def test_upstream_proxy(tctx, redirect, domain, scheme):
     """Test that an upstream HTTP proxy is used."""
     server = Placeholder(Server)
     server2 = Placeholder(Server)
@@ -736,28 +737,28 @@ def test_upstream_proxy(tctx, redirect, scheme):
             playbook
             >> DataReceived(
                 tctx.client,
-                b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
+                b"GET http://%s/ HTTP/1.1\r\nHost: %s\r\n\r\n" % (domain, domain),
             )
             << OpenConnection(server)
             >> reply(None)
             << SendData(
-                server, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n"
+                server, b"GET http://%s/ HTTP/1.1\r\nHost: %s\r\n\r\n" % (domain, domain),
             )
         )
 
     else:
         assert (
             playbook
-            >> DataReceived(tctx.client, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n")
+            >> DataReceived(tctx.client, b"CONNECT %s:443 HTTP/1.1\r\n\r\n" % domain)
             << SendData(tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n")
-            >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+            >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: %s\r\n\r\n" % domain)
             << layer.NextLayerHook(Placeholder())
             >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent))
             << OpenConnection(server)
             >> reply(None)
-            << SendData(server, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n")
+            << SendData(server, b"CONNECT %s:443 HTTP/1.1\r\n\r\n" % domain)
             >> DataReceived(server, b"HTTP/1.1 200 Connection established\r\n\r\n")
-            << SendData(server, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+            << SendData(server, b"GET / HTTP/1.1\r\nHost: %s\r\n\r\n" % domain)
         )
 
     playbook >> DataReceived(server, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")
@@ -769,17 +770,17 @@ def test_upstream_proxy(tctx, redirect, scheme):
     if scheme == "http":
         playbook >> DataReceived(
             tctx.client,
-            b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n",
+            b"GET http://%s/two HTTP/1.1\r\nHost: %s\r\n\r\n" % (domain, domain),
         )
     else:
         playbook >> DataReceived(
-            tctx.client, b"GET /two HTTP/1.1\r\nHost: example.com\r\n\r\n"
+            tctx.client, b"GET /two HTTP/1.1\r\nHost: %s\r\n\r\n" % domain
         )
 
     assert playbook << http.HttpRequestHook(flow)
     if redirect == "change-destination":
-        flow().request.host = "other-server"
-        flow().request.host_header = "example.com"
+        flow().request.host = domain + b".test"
+        flow().request.host_header = domain
     elif redirect == "change-proxy":
         flow().server_conn.via = ServerSpec("http", address=("other-proxy", 1234))
     playbook >> reply()
@@ -796,25 +797,25 @@ def test_upstream_proxy(tctx, redirect, scheme):
         if redirect == "change-destination":
             playbook << SendData(
                 server2,
-                b"GET http://other-server/two HTTP/1.1\r\nHost: example.com\r\n\r\n",
+                b"GET http://%s.test/two HTTP/1.1\r\nHost: %s\r\n\r\n" % (domain, domain),
             )
         else:
             playbook << SendData(
                 server2,
-                b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n",
+                b"GET http://%s/two HTTP/1.1\r\nHost: %s\r\n\r\n" % (domain, domain),
             )
     else:
         if redirect == "change-destination":
-            playbook << SendData(server2, b"CONNECT other-server:443 HTTP/1.1\r\n\r\n")
+            playbook << SendData(server2, b"CONNECT %s.test:443 HTTP/1.1\r\n\r\n" % domain)
             playbook >> DataReceived(
                 server2, b"HTTP/1.1 200 Connection established\r\n\r\n"
             )
         elif redirect == "change-proxy":
-            playbook << SendData(server2, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n")
+            playbook << SendData(server2, b"CONNECT %s:443 HTTP/1.1\r\n\r\n" % domain)
             playbook >> DataReceived(
                 server2, b"HTTP/1.1 200 Connection established\r\n\r\n"
             )
-        playbook << SendData(server2, b"GET /two HTTP/1.1\r\nHost: example.com\r\n\r\n")
+        playbook << SendData(server2, b"GET /two HTTP/1.1\r\nHost: %s\r\n\r\n" % domain)
 
     playbook >> DataReceived(server2, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")
     playbook << SendData(tctx.client, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n")
@@ -822,9 +823,9 @@ def test_upstream_proxy(tctx, redirect, scheme):
     assert playbook
 
     if redirect == "change-destination":
-        assert flow().server_conn.address[0] == "other-server"
+        assert flow().server_conn.address[0] == (domain + b".test").decode("idna")
     else:
-        assert flow().server_conn.address[0] == "example.com"
+        assert flow().server_conn.address[0] == domain.decode("idna")
 
     if redirect == "change-proxy":
         assert (