Index: Lib/httplib.py =================================================================== --- Lib/httplib.py (revision 88417) +++ Lib/httplib.py (working copy) @@ -729,25 +729,18 @@ def _tunnel(self): self._set_hostport(self._tunnel_host, self._tunnel_port) - self.send("CONNECT %s:%d HTTP/1.0\r\n" % (self.host, self.port)) + connect = ["CONNECT %s:%d HTTP/1.0\r\n" % (self.host, self.port)] for header, value in self._tunnel_headers.iteritems(): - self.send("%s: %s\r\n" % (header, value)) - self.send("\r\n") + connect.append("%s: %s\r\n" % (header, value)) + connect.append("\r\n") + self.send("".join(connect)) response = self.response_class(self.sock, strict = self.strict, method = self._method) - (version, code, message) = response._read_status() + response.begin() - if code != 200: - self.close() - raise socket.error("Tunnel connection failed: %d %s" % (code, - message.strip())) - while True: - line = response.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise LineTooLong("header line") - if line == '\r\n': break + if response.status != 200: + raise ProxyTunnelError(response) - def connect(self): """Connect to the host and port specified in __init__.""" self.sock = socket.create_connection((self.host,self.port), @@ -1197,6 +1190,14 @@ # or define self.args. Otherwise, str() will fail. pass +class ProxyTunnelError(HTTPException): + def __init__(self, response): + self.response = response + + def __str__(self): + return "ProxyTunnelError(HTTPResponse(code=%d, reason=%s))" % ( + self.response.status, self.response.reason) + class NotConnected(HTTPException): pass Index: Lib/urllib2.py =================================================================== --- Lib/urllib2.py (revision 88417) +++ Lib/urllib2.py (working copy) @@ -980,7 +980,11 @@ if H is None: return None - user, pw = self.passwd.find_user_password(realm, req.get_full_url()) + if req.has_proxy() or (req.type == 'https' and req._tunnel_host): + authuri = req.get_host() + else: + authuri = req.get_full_url() + user, pw = self.passwd.find_user_password(realm, authuri) if user is None: return None @@ -991,9 +995,14 @@ entdig = None A1 = "%s:%s:%s" % (user, realm, pw) - A2 = "%s:%s" % (req.get_method(), - # XXX selector: what about proxies and full urls - req.get_selector()) + if req._tunnel_host: + # All requests with _tunnel_host are https; hardwire 443 + uri = "%s:443" % req._tunnel_host + A2 = "CONNECT:%s" % uri + else: + # XXX selector: what about full urls + uri = req.get_selector() + A2 = "%s:%s" % (req.get_method(), uri) if qop == 'auth': if nonce == self.last_nonce: self.nonce_count += 1 @@ -1014,8 +1023,7 @@ # XXX should the partial digests be encoded too? base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ - 'response="%s"' % (user, realm, nonce, req.get_selector(), - respdig) + 'response="%s"' % (user, realm, nonce, uri, respdig) if opaque: base += ', opaque="%s"' % opaque if entdig: @@ -1190,7 +1198,18 @@ class HTTPSHandler(AbstractHTTPHandler): def https_open(self, req): - return self.do_open(httplib.HTTPSConnection, req) + try: + return self.do_open(httplib.HTTPSConnection, req) + except httplib.ProxyTunnelError as e: + if e.response.status == 407: + fp = socket._fileobject(e.response, close=True) + resp = addinfourl(fp, e.response.msg, req.get_full_url()) + return self.parent.error('https', req, resp, + e.response.status, e.response.reason, resp.info()) + else: + raise HTTPError(req.get_full_url(), + e.response.status, e.response.reason, + e.response.msg, e.response.fp) https_request = AbstractHTTPHandler.do_request_