]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Detect credentials in proxy URLs and create Proxy-authorization header (#780)
authorYeray Diaz Diaz <yeraydiazdiaz@gmail.com>
Mon, 20 Jan 2020 13:50:50 +0000 (13:50 +0000)
committerGitHub <noreply@github.com>
Mon, 20 Jan 2020 13:50:50 +0000 (13:50 +0000)
* Detect auth in proxy URLs and create Proxy-authorization header

* Add credentials and SOCKS details to proxy documentation

* Use URL.copy_with to remove credentials from URL

docs/advanced.md
httpx/config.py
tests/test_config.py

index 1d2e4ca0aff5cc74a91f799e5056eb4a04aaa8cc..809d84383cfef7307046171e7b9f5a838aff8eff 100644 (file)
@@ -176,7 +176,7 @@ client = httpx.Client(trust_env=False)
 
 ## HTTP Proxying
 
-HTTPX supports setting up proxies the same way that Requests does via the `proxies` parameter.
+HTTPX supports setting up HTTP proxies the same way that Requests does via the `proxies` parameter.
 For example to forward all HTTP traffic to `http://127.0.0.1:3080` and all HTTPS traffic
 to `http://127.0.0.1:3081` your `proxies` config would look like this:
 
@@ -189,6 +189,9 @@ to `http://127.0.0.1:3081` your `proxies` config would look like this:
 ...     ...
 ```
 
+Credentials may be passed in as part of the URL in the standard way, i.e.
+`http://username:password@127.0.0.1:3080`.
+
 Proxies can be configured for a specific scheme and host, all schemes of a host,
 all hosts for a scheme, or for all requests. When determining which proxy configuration
 to use for a given request this same order is used.
@@ -234,6 +237,8 @@ with httpx.Client(proxies=proxy) as client:
     To use proxies you must pass the proxy information at `Client` initialization,
     rather than on the `.get(...)` call or other request methods.
 
+SOCKS proxies are *not* supported yet.
+
 ## Timeout Configuration
 
 HTTPX is careful to enforce timeouts everywhere by default.
index d31086493c1f028b34eea8c95a9c87d90bacc18f..99f9538da87aaca1cee271f775667195956740bc 100644 (file)
@@ -1,6 +1,7 @@
 import os
 import ssl
 import typing
+from base64 import b64encode
 from pathlib import Path
 
 import certifi
@@ -325,10 +326,24 @@ class Proxy:
         if mode not in ("DEFAULT", "CONNECT_ONLY", "TUNNEL_ONLY"):
             raise ValueError(f"Unknown proxy mode {mode!r}")
 
+        if url.username or url.password:
+            headers.setdefault(
+                "Proxy-Authorization",
+                self.build_auth_header(url.username, url.password),
+            )
+            # Remove userinfo from the URL authority, e.g.:
+            # 'username:password@proxy_host:proxy_port' -> 'proxy_host:proxy_port'
+            url = url.copy_with(username=None, password=None)
+
         self.url = url
         self.headers = headers
         self.mode = mode
 
+    def build_auth_header(self, username: str, password: str) -> str:
+        userpass = (username.encode("utf-8"), password.encode("utf-8"))
+        token = b64encode(b":".join(userpass)).decode().strip()
+        return f"Basic {token}"
+
     def __repr__(self) -> str:
         return (
             f"Proxy(url={str(self.url)!r}, "
index e037076d20aad91ec603a9f551d366bf00ed39e8..8507c710de60b1cfcca28b42e00a6a5bf8e361af 100644 (file)
@@ -208,9 +208,27 @@ def test_ssl_config_support_for_keylog_file(tmpdir, monkeypatch):  # pragma: noc
         assert ssl_config.ssl_context.keylog_filename is None
 
 
-def test_proxy_from_url():
-    proxy = httpx.Proxy("https://example.com")
-    assert repr(proxy) == "Proxy(url='https://example.com', headers={}, mode='DEFAULT')"
+@pytest.mark.parametrize(
+    "url,expected_url,expected_headers,expected_mode",
+    [
+        ("https://example.com", "https://example.com", {}, "DEFAULT"),
+        (
+            "https://user:pass@example.com",
+            "https://example.com:443",
+            {"proxy-authorization": "Basic dXNlcjpwYXNz"},
+            "DEFAULT",
+        ),
+    ],
+)
+def test_proxy_from_url(url, expected_url, expected_headers, expected_mode):
+    proxy = httpx.Proxy(url)
+
+    assert str(proxy.url) == expected_url
+    assert dict(proxy.headers) == expected_headers
+    assert proxy.mode == expected_mode
+    assert repr(proxy) == "Proxy(url='{}', headers={}, mode='{}')".format(
+        expected_url, str(expected_headers), expected_mode
+    )
 
 
 def test_invalid_proxy_scheme():