Repository: encode/httpx

Issue:
KeyError occurs when redirect to custom scheme(ex. market://)
# Information

OS platform : mac OS
Python version : 3.7.2
Installed dependencies and versions : `httpx==0.9.3`
Code snippet
```
    @property
    def port(self) -> int:
        port = self._uri_reference.port
        if port is None:
            return {"https": 443, "http": 80}[self.scheme]
        return int(port)
```

Error traceback
```
[2020-02-24 14:57:08 +0900] [82150] [ERROR] Exception
Traceback (most recent call last):
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/sanic/testing.py", line 120, in _collect_response
    method, url, *request_args, **request_kwargs
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/sanic/testing.py", line 41, in _local_request
    url, verify=False, *args, **kwargs
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/client.py", line 671, in get
    trust_env=trust_env,
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/client.py", line 268, in request
    trust_env=trust_env,
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/client.py", line 410, in send
    allow_redirects=allow_redirects,
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/client.py", line 478, in send_handling_redirects
    request = self.build_redirect_request(request, response)
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/client.py", line 500, in build_redirect_request
    headers = self.redirect_headers(request, url, method)
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/client.py", line 555, in redirect_headers
    if url.origin != request.url.origin:
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/models.py", line 215, in origin
    return Origin(self)
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/models.py", line 287, in __init__
    self.port = url.port
  File "/Users/sym/.pyenv/versions/airbridge-ads-was/lib/python3.7/site-packages/httpx/models.py", line 165, in port
    return {"https": 443, "http": 80}[self.scheme]
KeyError: 'market'
[2020-02-24 14:57:08 +0900] [82150] [INFO] Starting worker [82150]
[2020-02-24 14:57:08 +0900] [82150] [INFO] Stopping worker [82150]
[2020-02-24 14:57:08 +0900] [82150] [INFO] Server Stopped
```

# Description
i'm using sanic and sanic uses httpx to test web request.
when i make a redirect response which goes to "market://details?id=~~" (android market url)", KeyError occurred.
I think it is associated with `port` property method.
Is this the intended behavior?

Thank you.


Discussion:
Hi @holyachon, I believe this was fixed in a recent release — can’t dig up the issue right now but I remember we now use defaults for the port instead of direct key access.

Can you upgrade to the latest version and confirm this is now resolved?
hi @florimondmanca , thank you for your quick reply :)
I tested same case with `httpx==0.11.1`, but same error occurs.

i made a redirect response which goes to "market://test", and error trace is below.

```
  File "/Users/sym/.pyenv/versions/workbox/lib/python3.7/site-packages/httpx/models.py", line 148, in port
    return {"https": 443, "http": 80}[self.scheme]
KeyError: 'market'
```
Okay, so this *ought* to error by default since "market" isn't a valid HTTP scheme, but it could do so more gracefully, and we ought to allow it to be supported by explicitly mounting a dispatcher class for `market`. (Eg. mentioned in passing at https://github.com/encode/httpx/issues/769#issuecomment-576267046)

For example `curl` will fail with `curl: (1) Protocol "market" not supported or disabled in libcurl`, and `requests` will fail with `requests.exceptions.InvalidSchema: No connection adapters were found for 'market://details?id=~~'`

Resolving patch:
diff --git a/httpx/_client.py b/httpx/_client.py
--- a/httpx/_client.py
+++ b/httpx/_client.py
@@ -322,6 +322,10 @@ def redirect_url(self, request: Request, response: Response) -> URL:
 
         url = URL(location, allow_relative=True)
 
+        # Check that we can handle the scheme
+        if url.scheme and url.scheme not in ("http", "https"):
+            raise InvalidURL(f'Scheme "{url.scheme}" not supported.')
+
         # Handle malformed 'Location' headers that are "absolute" form, have no host.
         # See: https://github.com/encode/httpx/issues/771
         if url.scheme and not url.host:


Test patch:
diff --git a/tests/client/test_redirects.py b/tests/client/test_redirects.py
--- a/tests/client/test_redirects.py
+++ b/tests/client/test_redirects.py
@@ -8,6 +8,7 @@
 from httpx import (
     URL,
     AsyncClient,
+    InvalidURL,
     NotRedirectResponse,
     RequestBodyUnavailable,
     TooManyRedirects,
@@ -140,6 +141,17 @@ async def body():
             else:
                 return b"HTTP/1.1", 200, b"OK", [], ByteStream(b"Hello, world!")
 
+        elif path == b"/redirect_custom_scheme":
+            status_code = codes.MOVED_PERMANENTLY
+            headers = [(b"location", b"market://details?id=42")]
+            return (
+                b"HTTP/1.1",
+                status_code,
+                b"Moved Permanently",
+                headers,
+                ByteStream(b""),
+            )
+
         return b"HTTP/1.1", 200, b"OK", [], ByteStream(b"Hello, world!")
 
 
@@ -431,3 +443,11 @@ async def test_redirect_cookie_behavior():
     response = await client.get("https://example.com/")
     assert response.url == "https://example.com/"
     assert response.text == "Not logged in"
+
+
+@pytest.mark.usefixtures("async_environment")
+async def test_redirect_custom_scheme():
+    client = AsyncClient(dispatch=MockDispatch())
+    with pytest.raises(InvalidURL) as e:
+        await client.post("https://example.org/redirect_custom_scheme")
+    assert str(e.value) == 'Scheme "market" not supported.'