-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
allow specifying https:// proxy #9119
Changes from all commits
8529878
c9cb0a8
f889cdf
9326de3
ef657bf
76698d8
bca0c04
728bbfb
9275a29
59e6f5a
35378a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add support for https connections to a proxy server. |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -22,6 +22,7 @@ | |||||||||
|
||||||||||
from twisted.internet import defer | ||||||||||
from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS | ||||||||||
from twisted.internet.interfaces import IReactorCore | ||||||||||
from twisted.python.failure import Failure | ||||||||||
from twisted.web.client import URI, BrowserLikePolicyForHTTPS, _AgentBase | ||||||||||
from twisted.web.error import SchemeNotSupported | ||||||||||
|
@@ -81,6 +82,10 @@ class ProxyAgent(_AgentBase): | |||||||||
|
||||||||||
use_proxy (bool): Whether proxy settings should be discovered and used | ||||||||||
from conventional environment variables. | ||||||||||
This currently supports http:// and https:// proxies. | ||||||||||
A hostname without scheme is assumed to be http. | ||||||||||
|
||||||||||
Raises: ValueError if given a proxy with a scheme we don't support. | ||||||||||
""" | ||||||||||
|
||||||||||
def __init__( | ||||||||||
|
@@ -121,11 +126,11 @@ def __init__( | |||||||||
self.https_proxy_creds, https_proxy = parse_username_password(https_proxy) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will break for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why only credentials were parsed for https proxy? Only security reason or anything else? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would suggest a solution like this.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't think there is much reason at all. Support for credentials was added in #9657: perhaps There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
seems sensible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||
|
||||||||||
self.http_proxy_endpoint = _http_proxy_endpoint( | ||||||||||
http_proxy, self.proxy_reactor, **self._endpoint_kwargs | ||||||||||
http_proxy, self.proxy_reactor, contextFactory, **self._endpoint_kwargs | ||||||||||
) | ||||||||||
|
||||||||||
self.https_proxy_endpoint = _http_proxy_endpoint( | ||||||||||
https_proxy, self.proxy_reactor, **self._endpoint_kwargs | ||||||||||
https_proxy, self.proxy_reactor, contextFactory, **self._endpoint_kwargs | ||||||||||
) | ||||||||||
|
||||||||||
self.no_proxy = no_proxy | ||||||||||
|
@@ -243,28 +248,46 @@ def request(self, method, uri, headers=None, bodyProducer=None): | |||||||||
) | ||||||||||
|
||||||||||
|
||||||||||
def _http_proxy_endpoint(proxy: Optional[bytes], reactor, **kwargs): | ||||||||||
def _http_proxy_endpoint( | ||||||||||
proxy: Optional[bytes], | ||||||||||
reactor: IReactorCore, | ||||||||||
tls_options_factory: IPolicyForHTTPS, | ||||||||||
**kwargs, | ||||||||||
): | ||||||||||
"""Parses an http proxy setting and returns an endpoint for the proxy | ||||||||||
Bubu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
Args: | ||||||||||
proxy: the proxy setting in the form: [<username>:<password>@]<host>[:<port>] | ||||||||||
Note that compared to other apps, this function currently lacks support | ||||||||||
for specifying a protocol schema (i.e. protocol://...). | ||||||||||
proxy: the proxy setting in the form: [scheme://][<username>:<password>@]<host>[:<port>] | ||||||||||
This currently supports http:// and https:// proxies. | ||||||||||
A hostname without scheme is assumed to be http. | ||||||||||
|
||||||||||
reactor: reactor to be used to connect to the proxy | ||||||||||
|
||||||||||
tls_options_factory: the TLS options to use when connecting through a https proxy | ||||||||||
kwargs: other args to be passed to HostnameEndpoint | ||||||||||
|
||||||||||
Returns: | ||||||||||
interfaces.IStreamClientEndpoint|None: endpoint to use to connect to the proxy, | ||||||||||
or None | ||||||||||
|
||||||||||
Raises: ValueError if given a proxy with a scheme we don't support. | ||||||||||
Bubu marked this conversation as resolved.
Show resolved
Hide resolved
Bubu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
""" | ||||||||||
if proxy is None: | ||||||||||
return None | ||||||||||
|
||||||||||
# Parse the connection string | ||||||||||
host, port = parse_host_port(proxy, default_port=1080) | ||||||||||
return HostnameEndpoint(reactor, host, port, **kwargs) | ||||||||||
# Note: we can't use urlsplit/urlparse as that is completely broken for things without a scheme:// | ||||||||||
Bubu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
scheme, host, port = parse_proxy(proxy) | ||||||||||
|
||||||||||
if scheme not in (b"http", b"https"): | ||||||||||
raise ValueError("Proxy scheme '{}' not supported".format(scheme.decode())) | ||||||||||
|
||||||||||
proxy_endpoint = HostnameEndpoint(reactor, host, port, **kwargs) | ||||||||||
|
||||||||||
if scheme == b"https": | ||||||||||
tls_options = tls_options_factory.creatorForNetloc(host, port) | ||||||||||
proxy_endpoint = wrapClientTLS(tls_options, proxy_endpoint) | ||||||||||
|
||||||||||
return proxy_endpoint | ||||||||||
|
||||||||||
|
||||||||||
def parse_username_password(proxy: bytes) -> Tuple[Optional[ProxyCredentials], bytes]: | ||||||||||
|
@@ -288,25 +311,35 @@ def parse_username_password(proxy: bytes) -> Tuple[Optional[ProxyCredentials], b | |||||||||
return None, proxy | ||||||||||
|
||||||||||
|
||||||||||
def parse_host_port(hostport: bytes, default_port: int = None) -> Tuple[bytes, int]: | ||||||||||
def parse_proxy( | ||||||||||
proxy: bytes, default_scheme: bytes = b"http", default_port: int = 1080 | ||||||||||
) -> Tuple[bytes, bytes, int]: | ||||||||||
""" | ||||||||||
Parse the hostname and port from a proxy connection byte string. | ||||||||||
Parse the scheme, hostname and port from a proxy connection byte string. | ||||||||||
|
||||||||||
Args: | ||||||||||
hostport: The proxy connection string. Must be in the form 'host[:port]'. | ||||||||||
default_port: The default port to return if one is not found in `hostport`. | ||||||||||
proxy: The proxy connection string. Must be in the form '[scheme://]host[:port]'. | ||||||||||
default_scheme: The default scheme to return if one is not found in `proxy`. Defaults to http | ||||||||||
default_port: The default port to return if one is not found in `proxy`. Defaults to 1080 | ||||||||||
|
||||||||||
Returns: | ||||||||||
A tuple containing the hostname and port. Uses `default_port` if one was not found. | ||||||||||
A tuple containing the scheme, hostname and port. | ||||||||||
""" | ||||||||||
if b":" in hostport: | ||||||||||
host, port = hostport.rsplit(b":", 1) | ||||||||||
# First check if we have a scheme present | ||||||||||
if b"://" in proxy: | ||||||||||
scheme, host = proxy.split(b"://", 1) | ||||||||||
Comment on lines
+329
to
+330
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have thought about the problem with Python 3.9 and I would suggest to work with urlparse after all. For compatibility reasons we can check if there is a sheme and if not we add this. Somthing like this
Suggested change
|
||||||||||
else: | ||||||||||
scheme, host = default_scheme, proxy | ||||||||||
# Now check the leftover part for a port | ||||||||||
if b":" in host: | ||||||||||
new_host, port = host.rsplit(b":", 1) | ||||||||||
try: | ||||||||||
port = int(port) | ||||||||||
return host, port | ||||||||||
return scheme, new_host, port | ||||||||||
except ValueError: | ||||||||||
# the thing after the : wasn't a valid port; presumably this is an | ||||||||||
# IPv6 address. | ||||||||||
# TODO: this doesn't work when the last part of the IP is also just a number. | ||||||||||
# We probably need to require ipv6's being wrapped in square brackets: [2001:db8:0:0:1::1] | ||||||||||
pass | ||||||||||
|
||||||||||
return hostport, default_port | ||||||||||
return scheme, host, default_port |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a bit confusing. A hostname where?
... and yes, of course it supports http and https proxies - what other sorts of proxies would an http/https agent possibly care about?
Suggest removing these lines.