--- a/mercurial/url.py Thu Nov 12 14:05:52 2009 -0800
+++ b/mercurial/url.py Fri Nov 13 06:36:26 2009 +0100
@@ -262,108 +262,11 @@
# 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 @@
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 @@
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 @@
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)