mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 07:17:55 +03:00
url: generalise HTTPS proxy handling to accomodate Python changes
Python 2.6.3 introduced HTTPS proxy tunnelling in a way that interferes with the way HTTPS proxying is handled in Mercurial. This fix generalises it to work on Python 2.4 to 2.6.
This commit is contained in:
parent
51a3364b03
commit
f5163bdcf6
261
mercurial/url.py
261
mercurial/url.py
@ -262,108 +262,11 @@ class httpconnection(keepalive.HTTPConnection):
|
||||
# must be able to send big bundle as stream.
|
||||
send = _gen_sendfile(keepalive.HTTPConnection)
|
||||
|
||||
def _proxytunnel(self):
|
||||
proxyheaders = dict(
|
||||
[(x, self.headers[x]) for x in self.headers
|
||||
if x.lower().startswith('proxy-')])
|
||||
self._set_hostport(self.host, self.port)
|
||||
self.send('CONNECT %s:%d HTTP/1.0\r\n' % (self.realhost, self.realport))
|
||||
for header in proxyheaders.iteritems():
|
||||
self.send('%s: %s\r\n' % header)
|
||||
self.send('\r\n')
|
||||
|
||||
# majority of the following code is duplicated from
|
||||
# httplib.HTTPConnection as there are no adequate places to
|
||||
# override functions to provide the needed functionality
|
||||
res = self.response_class(self.sock,
|
||||
strict=self.strict,
|
||||
method=self._method)
|
||||
|
||||
while True:
|
||||
version, status, reason = res._read_status()
|
||||
if status != httplib.CONTINUE:
|
||||
break
|
||||
while True:
|
||||
skip = res.fp.readline().strip()
|
||||
if not skip:
|
||||
break
|
||||
res.status = status
|
||||
res.reason = reason.strip()
|
||||
|
||||
if res.status == 200:
|
||||
while True:
|
||||
line = res.fp.readline()
|
||||
if line == '\r\n':
|
||||
break
|
||||
return True
|
||||
|
||||
if version == 'HTTP/1.0':
|
||||
res.version = 10
|
||||
elif version.startswith('HTTP/1.'):
|
||||
res.version = 11
|
||||
elif version == 'HTTP/0.9':
|
||||
res.version = 9
|
||||
else:
|
||||
raise httplib.UnknownProtocol(version)
|
||||
|
||||
if res.version == 9:
|
||||
res.length = None
|
||||
res.chunked = 0
|
||||
res.will_close = 1
|
||||
res.msg = httplib.HTTPMessage(cStringIO.StringIO())
|
||||
return False
|
||||
|
||||
res.msg = httplib.HTTPMessage(res.fp)
|
||||
res.msg.fp = None
|
||||
|
||||
# are we using the chunked-style of transfer encoding?
|
||||
trenc = res.msg.getheader('transfer-encoding')
|
||||
if trenc and trenc.lower() == "chunked":
|
||||
res.chunked = 1
|
||||
res.chunk_left = None
|
||||
else:
|
||||
res.chunked = 0
|
||||
|
||||
# will the connection close at the end of the response?
|
||||
res.will_close = res._check_close()
|
||||
|
||||
# do we have a Content-Length?
|
||||
# NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
|
||||
length = res.msg.getheader('content-length')
|
||||
if length and not res.chunked:
|
||||
try:
|
||||
res.length = int(length)
|
||||
except ValueError:
|
||||
res.length = None
|
||||
else:
|
||||
if res.length < 0: # ignore nonsensical negative lengths
|
||||
res.length = None
|
||||
else:
|
||||
res.length = None
|
||||
|
||||
# does the body have a fixed length? (of zero)
|
||||
if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
|
||||
100 <= status < 200 or # 1xx codes
|
||||
res._method == 'HEAD'):
|
||||
res.length = 0
|
||||
|
||||
# if the connection remains open, and we aren't using chunked, and
|
||||
# a content-length was not provided, then assume that the connection
|
||||
# WILL close.
|
||||
if (not res.will_close and
|
||||
not res.chunked and
|
||||
res.length is None):
|
||||
res.will_close = 1
|
||||
|
||||
self.proxyres = res
|
||||
|
||||
return False
|
||||
|
||||
def connect(self):
|
||||
if has_https and self.realhost: # use CONNECT proxy
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect((self.host, self.port))
|
||||
if self._proxytunnel():
|
||||
if _generic_proxytunnel(self):
|
||||
# we do not support client x509 certificates
|
||||
self.sock = _ssl_wrap_socket(self.sock, None, None)
|
||||
else:
|
||||
@ -378,30 +281,141 @@ class httpconnection(keepalive.HTTPConnection):
|
||||
return proxyres
|
||||
return keepalive.HTTPConnection.getresponse(self)
|
||||
|
||||
# general transaction handler to support different ways to handle
|
||||
# HTTPS proxying before and after Python 2.6.3.
|
||||
def _generic_start_transaction(handler, h, req):
|
||||
if hasattr(req, '_tunnel_host') and req._tunnel_host:
|
||||
tunnel_host = req._tunnel_host
|
||||
if tunnel_host[:7] not in ['http://', 'https:/']:
|
||||
tunnel_host = 'https://' + tunnel_host
|
||||
new_tunnel = True
|
||||
else:
|
||||
tunnel_host = req.get_selector()
|
||||
new_tunnel = False
|
||||
|
||||
if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
|
||||
urlparts = urlparse.urlparse(tunnel_host)
|
||||
if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
|
||||
if ':' in urlparts[1]:
|
||||
realhost, realport = urlparts[1].split(':')
|
||||
realport = int(realport)
|
||||
else:
|
||||
realhost = urlparts[1]
|
||||
realport = 443
|
||||
|
||||
h.realhost = realhost
|
||||
h.realport = realport
|
||||
h.headers = req.headers.copy()
|
||||
h.headers.update(handler.parent.addheaders)
|
||||
return
|
||||
|
||||
h.realhost = None
|
||||
h.realport = None
|
||||
h.headers = None
|
||||
|
||||
def _generic_proxytunnel(self):
|
||||
proxyheaders = dict(
|
||||
[(x, self.headers[x]) for x in self.headers
|
||||
if x.lower().startswith('proxy-')])
|
||||
self._set_hostport(self.host, self.port)
|
||||
self.send('CONNECT %s:%d HTTP/1.0\r\n' % (self.realhost, self.realport))
|
||||
for header in proxyheaders.iteritems():
|
||||
self.send('%s: %s\r\n' % header)
|
||||
self.send('\r\n')
|
||||
|
||||
# majority of the following code is duplicated from
|
||||
# httplib.HTTPConnection as there are no adequate places to
|
||||
# override functions to provide the needed functionality
|
||||
res = self.response_class(self.sock,
|
||||
strict=self.strict,
|
||||
method=self._method)
|
||||
|
||||
while True:
|
||||
version, status, reason = res._read_status()
|
||||
if status != httplib.CONTINUE:
|
||||
break
|
||||
while True:
|
||||
skip = res.fp.readline().strip()
|
||||
if not skip:
|
||||
break
|
||||
res.status = status
|
||||
res.reason = reason.strip()
|
||||
|
||||
if res.status == 200:
|
||||
while True:
|
||||
line = res.fp.readline()
|
||||
if line == '\r\n':
|
||||
break
|
||||
return True
|
||||
|
||||
if version == 'HTTP/1.0':
|
||||
res.version = 10
|
||||
elif version.startswith('HTTP/1.'):
|
||||
res.version = 11
|
||||
elif version == 'HTTP/0.9':
|
||||
res.version = 9
|
||||
else:
|
||||
raise httplib.UnknownProtocol(version)
|
||||
|
||||
if res.version == 9:
|
||||
res.length = None
|
||||
res.chunked = 0
|
||||
res.will_close = 1
|
||||
res.msg = httplib.HTTPMessage(cStringIO.StringIO())
|
||||
return False
|
||||
|
||||
res.msg = httplib.HTTPMessage(res.fp)
|
||||
res.msg.fp = None
|
||||
|
||||
# are we using the chunked-style of transfer encoding?
|
||||
trenc = res.msg.getheader('transfer-encoding')
|
||||
if trenc and trenc.lower() == "chunked":
|
||||
res.chunked = 1
|
||||
res.chunk_left = None
|
||||
else:
|
||||
res.chunked = 0
|
||||
|
||||
# will the connection close at the end of the response?
|
||||
res.will_close = res._check_close()
|
||||
|
||||
# do we have a Content-Length?
|
||||
# NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
|
||||
length = res.msg.getheader('content-length')
|
||||
if length and not res.chunked:
|
||||
try:
|
||||
res.length = int(length)
|
||||
except ValueError:
|
||||
res.length = None
|
||||
else:
|
||||
if res.length < 0: # ignore nonsensical negative lengths
|
||||
res.length = None
|
||||
else:
|
||||
res.length = None
|
||||
|
||||
# does the body have a fixed length? (of zero)
|
||||
if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
|
||||
100 <= status < 200 or # 1xx codes
|
||||
res._method == 'HEAD'):
|
||||
res.length = 0
|
||||
|
||||
# if the connection remains open, and we aren't using chunked, and
|
||||
# a content-length was not provided, then assume that the connection
|
||||
# WILL close.
|
||||
if (not res.will_close and
|
||||
not res.chunked and
|
||||
res.length is None):
|
||||
res.will_close = 1
|
||||
|
||||
self.proxyres = res
|
||||
|
||||
return False
|
||||
|
||||
class httphandler(keepalive.HTTPHandler):
|
||||
def http_open(self, req):
|
||||
return self.do_open(httpconnection, req)
|
||||
|
||||
def _start_transaction(self, h, req):
|
||||
if req.get_selector() == req.get_full_url(): # has proxy
|
||||
urlparts = urlparse.urlparse(req.get_selector())
|
||||
if urlparts[0] == 'https': # only use CONNECT for HTTPS
|
||||
if ':' in urlparts[1]:
|
||||
realhost, realport = urlparts[1].split(':')
|
||||
realport = int(realport)
|
||||
else:
|
||||
realhost = urlparts[1]
|
||||
realport = 443
|
||||
|
||||
h.realhost = realhost
|
||||
h.realport = realport
|
||||
h.headers = req.headers.copy()
|
||||
h.headers.update(self.parent.addheaders)
|
||||
return keepalive.HTTPHandler._start_transaction(self, h, req)
|
||||
|
||||
h.realhost = None
|
||||
h.realport = None
|
||||
h.headers = None
|
||||
_generic_start_transaction(self, h, req)
|
||||
return keepalive.HTTPHandler._start_transaction(self, h, req)
|
||||
|
||||
def __del__(self):
|
||||
@ -417,6 +431,15 @@ if has_https:
|
||||
send = _gen_sendfile(BetterHTTPS)
|
||||
getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
|
||||
|
||||
def connect(self):
|
||||
if self.realhost: # use CONNECT proxy
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect((self.host, self.port))
|
||||
if _generic_proxytunnel(self):
|
||||
self.sock = _ssl_wrap_socket(self.sock, self.cert_file, self.key_file)
|
||||
else:
|
||||
BetterHTTPS.connect(self)
|
||||
|
||||
class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
|
||||
def __init__(self, ui):
|
||||
keepalive.KeepAliveHandler.__init__(self)
|
||||
@ -424,6 +447,10 @@ if has_https:
|
||||
self.ui = ui
|
||||
self.pwmgr = passwordmgr(self.ui)
|
||||
|
||||
def _start_transaction(self, h, req):
|
||||
_generic_start_transaction(self, h, req)
|
||||
return keepalive.KeepAliveHandler._start_transaction(self, h, req)
|
||||
|
||||
def https_open(self, req):
|
||||
self.auth = self.pwmgr.readauthtoken(req.get_full_url())
|
||||
return self.do_open(self._makeconnection, req)
|
||||
|
Loading…
Reference in New Issue
Block a user