python-proxy-headers 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_proxy_headers-0.2.1/python_proxy_headers.egg-info → python_proxy_headers-0.2.3}/PKG-INFO +1 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/pyproject.toml +1 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/aiohttp_proxy.py +10 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/autoscraper_proxy.py +3 -2
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/cloudscraper_proxy.py +9 -3
- python_proxy_headers-0.2.3/python_proxy_headers/header_utils.py +165 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/httpx_proxy.py +17 -4
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/pycurl_proxy.py +4 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/requests_adapter.py +7 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/urllib3_proxy_manager.py +30 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3/python_proxy_headers.egg-info}/PKG-INFO +1 -1
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers.egg-info/SOURCES.txt +1 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/LICENSE +0 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/README.md +0 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/__init__.py +0 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers.egg-info/dependency_links.txt +0 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers.egg-info/top_level.txt +0 -0
- {python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/setup.cfg +0 -0
{python_proxy_headers-0.2.1/python_proxy_headers.egg-info → python_proxy_headers-0.2.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-proxy-headers
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Handle custom proxy headers for http & https requests in various python libraries
|
|
5
5
|
Author-email: ProxyMesh <support@proxymesh.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/proxymesh/python-proxy-headers
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/aiohttp_proxy.py
RENAMED
|
@@ -6,6 +6,7 @@ from aiohttp.client import ClientSession
|
|
|
6
6
|
from aiohttp.helpers import reify
|
|
7
7
|
from aiohttp import hdrs
|
|
8
8
|
from multidict import CIMultiDict, CIMultiDictProxy
|
|
9
|
+
from .header_utils import merge_proxy_response_headers
|
|
9
10
|
|
|
10
11
|
class ProxyTCPConnector(TCPConnector):
|
|
11
12
|
async def _create_proxy_connection(self, req: ClientRequest, traces, timeout):
|
|
@@ -143,13 +144,21 @@ class ProxyClientRequest(ClientRequest):
|
|
|
143
144
|
return resp
|
|
144
145
|
|
|
145
146
|
class ProxyClientResponse(ClientResponse):
|
|
147
|
+
@reify
|
|
148
|
+
def proxy_headers(self):
|
|
149
|
+
"""Headers from the proxy CONNECT response, not merged into origin headers."""
|
|
150
|
+
raw = getattr(self, "_proxy_headers", None)
|
|
151
|
+
if raw:
|
|
152
|
+
return CIMultiDictProxy(CIMultiDict(raw))
|
|
153
|
+
return CIMultiDictProxy(CIMultiDict())
|
|
154
|
+
|
|
146
155
|
@reify
|
|
147
156
|
def headers(self):
|
|
148
157
|
proxy_headers = getattr(self, '_proxy_headers', None)
|
|
149
158
|
|
|
150
159
|
if proxy_headers:
|
|
151
160
|
headers = CIMultiDict(self._headers)
|
|
152
|
-
headers
|
|
161
|
+
merge_proxy_response_headers(headers, proxy_headers)
|
|
153
162
|
return CIMultiDictProxy(headers)
|
|
154
163
|
else:
|
|
155
164
|
return self._headers
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/autoscraper_proxy.py
RENAMED
|
@@ -35,6 +35,7 @@ except ImportError:
|
|
|
35
35
|
"Install it with: pip install autoscraper"
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
from .header_utils import validate_headers
|
|
38
39
|
from .requests_adapter import ProxySession
|
|
39
40
|
|
|
40
41
|
|
|
@@ -71,7 +72,7 @@ class ProxyAutoScraper(AutoScraper):
|
|
|
71
72
|
stack_list: Optional[List] = None
|
|
72
73
|
):
|
|
73
74
|
super().__init__(stack_list=stack_list)
|
|
74
|
-
self._proxy_headers = proxy_headers
|
|
75
|
+
self._proxy_headers = validate_headers(proxy_headers)
|
|
75
76
|
self._session: Optional[ProxySession] = None
|
|
76
77
|
|
|
77
78
|
def _get_session(self) -> ProxySession:
|
|
@@ -90,7 +91,7 @@ class ProxyAutoScraper(AutoScraper):
|
|
|
90
91
|
Args:
|
|
91
92
|
proxy_headers: New proxy headers to use
|
|
92
93
|
"""
|
|
93
|
-
self._proxy_headers = proxy_headers
|
|
94
|
+
self._proxy_headers = validate_headers(proxy_headers)
|
|
94
95
|
if self._session is not None:
|
|
95
96
|
self._session.close()
|
|
96
97
|
self._session = None
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/cloudscraper_proxy.py
RENAMED
|
@@ -27,6 +27,7 @@ except ImportError:
|
|
|
27
27
|
"Install it with: pip install cloudscraper"
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
from .header_utils import validate_headers
|
|
30
31
|
from .urllib3_proxy_manager import proxy_from_url
|
|
31
32
|
|
|
32
33
|
|
|
@@ -41,8 +42,13 @@ class CipherSuiteProxyHeaderAdapter(CipherSuiteAdapter):
|
|
|
41
42
|
"""
|
|
42
43
|
|
|
43
44
|
def __init__(self, proxy_headers: Optional[Dict[str, str]] = None, **kwargs):
|
|
44
|
-
self._proxy_headers = proxy_headers
|
|
45
|
+
self._proxy_headers = validate_headers(proxy_headers)
|
|
45
46
|
super().__init__(**kwargs)
|
|
47
|
+
|
|
48
|
+
def build_response(self, req, resp):
|
|
49
|
+
response = super().build_response(req, resp)
|
|
50
|
+
response.proxy_headers = getattr(resp, "proxy_headers", {}) or {}
|
|
51
|
+
return response
|
|
46
52
|
|
|
47
53
|
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
|
48
54
|
"""
|
|
@@ -103,7 +109,7 @@ class ProxyCloudScraper(cloudscraper.CloudScraper):
|
|
|
103
109
|
"""
|
|
104
110
|
|
|
105
111
|
def __init__(self, proxy_headers: Optional[Dict[str, str]] = None, **kwargs):
|
|
106
|
-
self._proxy_headers = proxy_headers
|
|
112
|
+
self._proxy_headers = validate_headers(proxy_headers)
|
|
107
113
|
|
|
108
114
|
# Call parent init
|
|
109
115
|
super().__init__(**kwargs)
|
|
@@ -142,7 +148,7 @@ class ProxyCloudScraper(cloudscraper.CloudScraper):
|
|
|
142
148
|
Args:
|
|
143
149
|
proxy_headers: New proxy headers to use
|
|
144
150
|
"""
|
|
145
|
-
self._proxy_headers = proxy_headers
|
|
151
|
+
self._proxy_headers = validate_headers(proxy_headers)
|
|
146
152
|
|
|
147
153
|
# Remount adapters with new headers
|
|
148
154
|
self.mount(
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Validation and safe merging for proxy CONNECT headers.
|
|
2
|
+
|
|
3
|
+
CONNECT requests are raw HTTP and must not interpolate CR, LF, or NUL.
|
|
4
|
+
CONNECT response headers are not origin HTTPS headers: hop-by-hop and
|
|
5
|
+
security-sensitive names are not copied onto the origin response, and
|
|
6
|
+
existing origin headers are never overwritten.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Sequence, Tuple, Union
|
|
12
|
+
|
|
13
|
+
RawHeaders = Union[Mapping[Any, Any], Sequence[Tuple[Any, Any]], None]
|
|
14
|
+
|
|
15
|
+
# Never copy these from a CONNECT response onto the origin response.
|
|
16
|
+
BLOCKED_PROXY_RESPONSE_HEADERS = frozenset({
|
|
17
|
+
"age",
|
|
18
|
+
"authorization",
|
|
19
|
+
"cache-control",
|
|
20
|
+
"clear-site-data",
|
|
21
|
+
"connection",
|
|
22
|
+
"content-disposition",
|
|
23
|
+
"content-encoding",
|
|
24
|
+
"content-language",
|
|
25
|
+
"content-length",
|
|
26
|
+
"content-location",
|
|
27
|
+
"content-range",
|
|
28
|
+
"content-security-policy",
|
|
29
|
+
"content-security-policy-report-only",
|
|
30
|
+
"content-type",
|
|
31
|
+
"cookie",
|
|
32
|
+
"date",
|
|
33
|
+
"etag",
|
|
34
|
+
"expires",
|
|
35
|
+
"host",
|
|
36
|
+
"keep-alive",
|
|
37
|
+
"last-modified",
|
|
38
|
+
"link",
|
|
39
|
+
"location",
|
|
40
|
+
"pragma",
|
|
41
|
+
"proxy-agent",
|
|
42
|
+
"proxy-authenticate",
|
|
43
|
+
"proxy-authorization",
|
|
44
|
+
"proxy-connection",
|
|
45
|
+
"refresh",
|
|
46
|
+
"server",
|
|
47
|
+
"set-cookie",
|
|
48
|
+
"set-cookie2",
|
|
49
|
+
"strict-transport-security",
|
|
50
|
+
"te",
|
|
51
|
+
"trailer",
|
|
52
|
+
"transfer-encoding",
|
|
53
|
+
"upgrade",
|
|
54
|
+
"vary",
|
|
55
|
+
"via",
|
|
56
|
+
"warning",
|
|
57
|
+
"www-authenticate",
|
|
58
|
+
"x-content-type-options",
|
|
59
|
+
"x-frame-options",
|
|
60
|
+
"x-xss-protection",
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _as_str(value: Any) -> str:
|
|
65
|
+
if isinstance(value, bytes):
|
|
66
|
+
return value.decode("latin-1")
|
|
67
|
+
return str(value)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def validate_header_name(name: Any) -> str:
|
|
71
|
+
"""Return a header name, or raise ValueError if it is not a single token."""
|
|
72
|
+
name_s = _as_str(name)
|
|
73
|
+
if not name_s:
|
|
74
|
+
raise ValueError("Header name must not be empty")
|
|
75
|
+
for char in name_s:
|
|
76
|
+
code = ord(char)
|
|
77
|
+
if char in "()<>@,;:\\\"/[]?={} \t:" or code <= 32 or code == 127:
|
|
78
|
+
raise ValueError(f"Invalid header name {name_s!r}")
|
|
79
|
+
return name_s
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def validate_header_value(value: Any) -> str:
|
|
83
|
+
"""Return a header value, or raise ValueError if it contains CR, LF, or NUL."""
|
|
84
|
+
value_s = _as_str(value)
|
|
85
|
+
if any(char in value_s for char in "\r\n\x00"):
|
|
86
|
+
raise ValueError(f"Invalid header value {value_s!r}")
|
|
87
|
+
return value_s
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def validate_headers(headers: Optional[Mapping[Any, Any]]) -> Dict[str, str]:
|
|
91
|
+
"""Validate a mapping of CONNECT request headers.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: If any name or value contains CR, LF, NUL, or other
|
|
95
|
+
characters illegal in a single CONNECT header line.
|
|
96
|
+
"""
|
|
97
|
+
if not headers:
|
|
98
|
+
return {}
|
|
99
|
+
validated: Dict[str, str] = {}
|
|
100
|
+
for name, value in headers.items():
|
|
101
|
+
validated[validate_header_name(name)] = validate_header_value(value)
|
|
102
|
+
return validated
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _header_items(headers: RawHeaders) -> List[Tuple[Any, Any]]:
|
|
106
|
+
if not headers:
|
|
107
|
+
return []
|
|
108
|
+
if isinstance(headers, (list, tuple)):
|
|
109
|
+
return list(headers)
|
|
110
|
+
if hasattr(headers, "items"):
|
|
111
|
+
return list(headers.items())
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def origin_has_header(origin_headers: RawHeaders, name: str) -> bool:
|
|
116
|
+
"""Return True if origin_headers already contains ``name`` (case-insensitive)."""
|
|
117
|
+
lowered = _as_str(name).lower()
|
|
118
|
+
for key, _value in _header_items(origin_headers):
|
|
119
|
+
if _as_str(key).lower() == lowered:
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def is_mergeable_proxy_header(name: Any) -> bool:
|
|
125
|
+
"""Return True if a CONNECT response header may be copied onto origin headers."""
|
|
126
|
+
lowered = _as_str(name).lower()
|
|
127
|
+
if lowered in BLOCKED_PROXY_RESPONSE_HEADERS:
|
|
128
|
+
return False
|
|
129
|
+
if lowered.startswith("access-control-"):
|
|
130
|
+
return False
|
|
131
|
+
return True
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def snapshot_headers(headers: RawHeaders) -> Dict[str, str]:
|
|
135
|
+
"""Copy headers into a plain dict of strings."""
|
|
136
|
+
snapshot: Dict[str, str] = {}
|
|
137
|
+
for name, value in _header_items(headers):
|
|
138
|
+
snapshot[_as_str(name)] = _as_str(value)
|
|
139
|
+
return snapshot
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def merge_proxy_response_headers(
|
|
143
|
+
origin_headers: MutableMapping[Any, Any],
|
|
144
|
+
proxy_headers: RawHeaders,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Copy safe CONNECT headers onto origin headers without overwriting."""
|
|
147
|
+
for name, value in _header_items(proxy_headers):
|
|
148
|
+
name_s = _as_str(name)
|
|
149
|
+
if not is_mergeable_proxy_header(name_s):
|
|
150
|
+
continue
|
|
151
|
+
if origin_has_header(origin_headers, name_s):
|
|
152
|
+
continue
|
|
153
|
+
origin_headers[name_s] = _as_str(value)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def filter_connect_headers(
|
|
157
|
+
origin_headers: RawHeaders,
|
|
158
|
+
connect_headers: RawHeaders,
|
|
159
|
+
) -> List[Tuple[Any, Any]]:
|
|
160
|
+
"""Return CONNECT header pairs that are safe to merge into origin headers."""
|
|
161
|
+
extra: List[Tuple[Any, Any]] = []
|
|
162
|
+
for name, value in _header_items(connect_headers):
|
|
163
|
+
if is_mergeable_proxy_header(name) and not origin_has_header(origin_headers, name):
|
|
164
|
+
extra.append((name, value))
|
|
165
|
+
return extra
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/httpx_proxy.py
RENAMED
|
@@ -9,6 +9,7 @@ from httpcore._ssl import default_ssl_context
|
|
|
9
9
|
from httpcore._trace import Trace
|
|
10
10
|
from httpx import AsyncHTTPTransport, HTTPTransport, Client
|
|
11
11
|
from httpx._config import DEFAULT_LIMITS, DEFAULT_TIMEOUT_CONFIG, Proxy, create_ssl_context
|
|
12
|
+
from .header_utils import filter_connect_headers
|
|
12
13
|
|
|
13
14
|
class ProxyTunnelHTTPConnection(TunnelHTTPConnection):
|
|
14
15
|
# Unfortunately the only way to get connect_response.headers into the Response
|
|
@@ -90,10 +91,16 @@ class ProxyTunnelHTTPConnection(TunnelHTTPConnection):
|
|
|
90
91
|
keepalive_expiry=self._keepalive_expiry,
|
|
91
92
|
)
|
|
92
93
|
|
|
94
|
+
self._connect_response_headers = connect_response.headers
|
|
93
95
|
self._connected = True
|
|
94
|
-
#
|
|
96
|
+
# Merge safe CONNECT headers without overwriting origin values
|
|
95
97
|
response = self._connection.handle_request(request)
|
|
96
|
-
response.headers = merge_headers(
|
|
98
|
+
response.headers = merge_headers(
|
|
99
|
+
response.headers,
|
|
100
|
+
filter_connect_headers(
|
|
101
|
+
response.headers, getattr(self, "_connect_response_headers", ())
|
|
102
|
+
),
|
|
103
|
+
)
|
|
97
104
|
return response
|
|
98
105
|
|
|
99
106
|
class AsyncProxyTunnelHTTPConnection(AsyncTunnelHTTPConnection):
|
|
@@ -174,10 +181,16 @@ class AsyncProxyTunnelHTTPConnection(AsyncTunnelHTTPConnection):
|
|
|
174
181
|
keepalive_expiry=self._keepalive_expiry,
|
|
175
182
|
)
|
|
176
183
|
|
|
184
|
+
self._connect_response_headers = connect_response.headers
|
|
177
185
|
self._connected = True
|
|
178
|
-
#
|
|
186
|
+
# Merge safe CONNECT headers without overwriting origin values
|
|
179
187
|
response = await self._connection.handle_async_request(request)
|
|
180
|
-
response.headers = merge_headers(
|
|
188
|
+
response.headers = merge_headers(
|
|
189
|
+
response.headers,
|
|
190
|
+
filter_connect_headers(
|
|
191
|
+
response.headers, getattr(self, "_connect_response_headers", ())
|
|
192
|
+
),
|
|
193
|
+
)
|
|
181
194
|
return response
|
|
182
195
|
|
|
183
196
|
class HTTPProxyHeaders(HTTPProxy):
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/pycurl_proxy.py
RENAMED
|
@@ -38,6 +38,8 @@ from io import BytesIO
|
|
|
38
38
|
from dataclasses import dataclass, field
|
|
39
39
|
from typing import Dict, List, Optional, Tuple
|
|
40
40
|
|
|
41
|
+
from .header_utils import validate_headers
|
|
42
|
+
|
|
41
43
|
try:
|
|
42
44
|
import pycurl
|
|
43
45
|
except ImportError:
|
|
@@ -67,7 +69,8 @@ def set_proxy_headers(curl, headers: Dict[str, str]) -> None:
|
|
|
67
69
|
"""
|
|
68
70
|
if not headers:
|
|
69
71
|
return
|
|
70
|
-
|
|
72
|
+
|
|
73
|
+
headers = validate_headers(headers)
|
|
71
74
|
header_list = [f"{k}: {v}" for k, v in headers.items()]
|
|
72
75
|
|
|
73
76
|
# Set CURLOPT_PROXYHEADER
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers/requests_adapter.py
RENAMED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
from requests.adapters import HTTPAdapter
|
|
2
2
|
from requests.sessions import Session
|
|
3
|
+
from .header_utils import validate_headers
|
|
3
4
|
from .urllib3_proxy_manager import proxy_from_url
|
|
4
5
|
|
|
5
6
|
class HTTPProxyHeaderAdapter(HTTPAdapter):
|
|
6
7
|
def __init__(self, proxy_headers=None):
|
|
7
8
|
super().__init__()
|
|
8
|
-
self._proxy_headers = proxy_headers
|
|
9
|
+
self._proxy_headers = validate_headers(proxy_headers)
|
|
10
|
+
|
|
11
|
+
def build_response(self, req, resp):
|
|
12
|
+
response = super().build_response(req, resp)
|
|
13
|
+
response.proxy_headers = getattr(resp, "proxy_headers", {}) or {}
|
|
14
|
+
return response
|
|
9
15
|
|
|
10
16
|
def proxy_manager_for(self, proxy, **proxy_kwargs):
|
|
11
17
|
"""Return urllib3 ProxyManager for the given proxy.
|
|
@@ -3,6 +3,16 @@ from http.client import _read_headers
|
|
|
3
3
|
from urllib3.connection import HTTPSConnection
|
|
4
4
|
from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
|
5
5
|
from urllib3.poolmanager import ProxyManager
|
|
6
|
+
from urllib3.util.request import make_headers
|
|
7
|
+
from urllib3.util.url import parse_url
|
|
8
|
+
|
|
9
|
+
from .header_utils import (
|
|
10
|
+
merge_proxy_response_headers,
|
|
11
|
+
snapshot_headers,
|
|
12
|
+
validate_header_name,
|
|
13
|
+
validate_header_value,
|
|
14
|
+
validate_headers,
|
|
15
|
+
)
|
|
6
16
|
|
|
7
17
|
if sys.version_info < (3, 12, 0):
|
|
8
18
|
#####################################
|
|
@@ -75,6 +85,8 @@ class HTTPSProxyConnection(HTTPSConnection):
|
|
|
75
85
|
self._http_vsn_str.encode("ascii"))
|
|
76
86
|
headers = [connect]
|
|
77
87
|
for header, value in self._tunnel_headers.items():
|
|
88
|
+
validate_header_name(header)
|
|
89
|
+
validate_header_value(value)
|
|
78
90
|
headers.append(f"{header}: {value}\r\n".encode("latin-1"))
|
|
79
91
|
headers.append(b"\r\n")
|
|
80
92
|
# Making a single send() call instead of one per line encourages
|
|
@@ -123,11 +135,28 @@ class HTTPSProxyConnectionPool(HTTPSConnectionPool):
|
|
|
123
135
|
|
|
124
136
|
def urlopen(self, *args, **kwargs):
|
|
125
137
|
response = super().urlopen(*args, **kwargs)
|
|
126
|
-
|
|
138
|
+
proxy_headers = getattr(self, "_proxy_response_headers", None)
|
|
139
|
+
response.proxy_headers = snapshot_headers(proxy_headers)
|
|
140
|
+
merge_proxy_response_headers(response.headers, proxy_headers)
|
|
127
141
|
return response
|
|
128
142
|
|
|
129
143
|
class ProxyHeaderManager(ProxyManager):
|
|
130
144
|
def __init__(self, *args, **kwargs):
|
|
145
|
+
# urllib3.ProxyManager does not add Proxy-Authorization from user:pass in the
|
|
146
|
+
# proxy URL; requests does via HTTPAdapter.proxy_headers(). Merge URL auth here
|
|
147
|
+
# so direct proxy_from_url() matches requests and authenticates CONNECT.
|
|
148
|
+
proxy_url = kwargs.get("proxy_url")
|
|
149
|
+
if proxy_url is None and args:
|
|
150
|
+
proxy_url = args[0]
|
|
151
|
+
proxy_headers = kwargs.get("proxy_headers")
|
|
152
|
+
merged = validate_headers(dict(proxy_headers or {}))
|
|
153
|
+
if isinstance(proxy_url, str):
|
|
154
|
+
parsed = parse_url(proxy_url)
|
|
155
|
+
if parsed.auth and not any(
|
|
156
|
+
k.lower() == "proxy-authorization" for k in merged
|
|
157
|
+
):
|
|
158
|
+
merged.update(make_headers(proxy_basic_auth=parsed.auth))
|
|
159
|
+
kwargs["proxy_headers"] = merged
|
|
131
160
|
super().__init__(*args, **kwargs)
|
|
132
161
|
self.pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSProxyConnectionPool}
|
|
133
162
|
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3/python_proxy_headers.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-proxy-headers
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Handle custom proxy headers for http & https requests in various python libraries
|
|
5
5
|
Author-email: ProxyMesh <support@proxymesh.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/proxymesh/python-proxy-headers
|
{python_proxy_headers-0.2.1 → python_proxy_headers-0.2.3}/python_proxy_headers.egg-info/SOURCES.txt
RENAMED
|
@@ -5,6 +5,7 @@ python_proxy_headers/__init__.py
|
|
|
5
5
|
python_proxy_headers/aiohttp_proxy.py
|
|
6
6
|
python_proxy_headers/autoscraper_proxy.py
|
|
7
7
|
python_proxy_headers/cloudscraper_proxy.py
|
|
8
|
+
python_proxy_headers/header_utils.py
|
|
8
9
|
python_proxy_headers/httpx_proxy.py
|
|
9
10
|
python_proxy_headers/pycurl_proxy.py
|
|
10
11
|
python_proxy_headers/requests_adapter.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|