mercurial/url.py
changeset 8590 59acb9c7d90f
parent 8344 873429914ec5
child 8847 7951f385fcb7
--- a/mercurial/url.py	Thu May 14 19:54:26 2009 +0200
+++ b/mercurial/url.py	Fri May 22 08:56:43 2009 +0200
@@ -7,7 +7,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2, incorporated herein by reference.
 
-import urllib, urllib2, urlparse, httplib, os, re
+import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
 from i18n import _
 import keepalive, util
 
@@ -245,18 +245,165 @@
             connection.send(self, data)
     return _sendfile
 
+has_https = hasattr(urllib2, 'HTTPSHandler')
+if has_https:
+    try:
+        # avoid using deprecated/broken FakeSocket in python 2.6
+        import ssl
+        _ssl_wrap_socket = ssl.wrap_socket
+    except ImportError:
+        def _ssl_wrap_socket(sock, key_file, cert_file):
+            ssl = socket.ssl(sock, key_file, cert_file)
+            return httplib.FakeSocket(sock, ssl)
+
 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():
+                # we do not support client x509 certificates
+                self.sock = _ssl_wrap_socket(self.sock, None, None)
+        else:
+            keepalive.HTTPConnection.connect(self)
+
+    def getresponse(self):
+        proxyres = getattr(self, 'proxyres', None)
+        if proxyres:
+            if proxyres.will_close:
+                self.close()
+            self.proxyres = None
+            return proxyres
+        return keepalive.HTTPConnection.getresponse(self)
+
 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(':')
+                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
+        return keepalive.HTTPHandler._start_transaction(self, h, req)
+
     def __del__(self):
         self.close_all()
 
-has_https = hasattr(urllib2, 'HTTPSHandler')
 if has_https:
     class httpsconnection(httplib.HTTPSConnection):
         response_class = keepalive.HTTPResponse