Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: third_party/fancy_urllib/__init__.py

Issue 1273083004: Update fancy_urllib to work with python-2.7.10 (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/fancy_urllib/README ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software 1 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software
4 # Foundation; All Rights Reserved 2 # Foundation; All Rights Reserved
5 3
6 """A HTTPSConnection/Handler with additional proxy and cert validation features. 4 """A HTTPSConnection/Handler with additional proxy and cert validation features.
7 5
8 In particular, monkey patches in Python r74203 to provide support for CONNECT 6 In particular, monkey patches in Python r74203 to provide support for CONNECT
9 proxies and adds SSL cert validation if the ssl module is present. 7 proxies and adds SSL cert validation if the ssl module is present.
10 """ 8 """
11 9
12 __author__ = "{frew,nick.johnson}@google.com (Fred Wulff and Nick Johnson)" 10 __author__ = "{frew,nick.johnson}@google.com (Fred Wulff and Nick Johnson)"
13 11
14 import base64 12 import base64
15 import httplib 13 import httplib
16 import logging 14 import logging
17 import re
18 import socket 15 import socket
16 from urllib import splitpasswd
17 from urllib import splittype
18 from urllib import splituser
19 import urllib2 19 import urllib2
20 20
21 from urllib import splittype
22 from urllib import splituser
23 from urllib import splitpasswd
24 21
25 class InvalidCertificateException(httplib.HTTPException): 22 class InvalidCertificateException(httplib.HTTPException):
26 """Raised when a certificate is provided with an invalid hostname.""" 23 """Raised when a certificate is provided with an invalid hostname."""
27 24
28 def __init__(self, host, cert, reason): 25 def __init__(self, host, cert, reason):
29 """Constructor. 26 """Constructor.
30 27
31 Args: 28 Args:
32 host: The hostname the connection was made to. 29 host: The hostname the connection was made to.
33 cert: The SSL certificate (as a dictionary) the host returned. 30 cert: The SSL certificate (as a dictionary) the host returned.
31 reason: user readable error reason.
34 """ 32 """
35 httplib.HTTPException.__init__(self) 33 httplib.HTTPException.__init__(self)
36 self.host = host 34 self.host = host
37 self.cert = cert 35 self.cert = cert
38 self.reason = reason 36 self.reason = reason
39 37
40 def __str__(self): 38 def __str__(self):
41 return ('Host %s returned an invalid certificate (%s): %s\n' 39 return ("Host %s returned an invalid certificate (%s): %s\n"
42 'To learn more, see ' 40 "To learn more, see "
43 'http://code.google.com/appengine/kb/general.html#rpcssl' % 41 "http://code.google.com/appengine/kb/general.html#rpcssl" %
44 (self.host, self.reason, self.cert)) 42 (self.host, self.reason, self.cert))
45 43
44
45 try:
46 import ssl
47 _CAN_VALIDATE_CERTS = True
48 except ImportError:
49 _CAN_VALIDATE_CERTS = False
50
51
46 def can_validate_certs(): 52 def can_validate_certs():
47 """Return True if we have the SSL package and can validate certificates.""" 53 """Return True if we have the SSL package and can validate certificates."""
48 try: 54 return _CAN_VALIDATE_CERTS
49 import ssl
50 return True
51 except ImportError:
52 return False
53 55
54 def _create_fancy_connection(tunnel_host=None, key_file=None, 56
55 cert_file=None, ca_certs=None): 57 # Reexport SSLError so clients don't have to to do their own checking for ssl's
58 # existence.
59 if can_validate_certs():
60 SSLError = ssl.SSLError
61 else:
62 SSLError = None
63
64
65 def create_fancy_connection(tunnel_host=None, key_file=None,
66 cert_file=None, ca_certs=None,
67 proxy_authorization=None):
56 # This abomination brought to you by the fact that 68 # This abomination brought to you by the fact that
57 # the HTTPHandler creates the connection instance in the middle 69 # the HTTPHandler creates the connection instance in the middle
58 # of do_open so we need to add the tunnel host to the class. 70 # of do_open so we need to add the tunnel host to the class.
59 71
60 class PresetProxyHTTPSConnection(httplib.HTTPSConnection): 72 class PresetProxyHTTPSConnection(httplib.HTTPSConnection):
61 """An HTTPS connection that uses a proxy defined by the enclosing scope.""" 73 """An HTTPS connection that uses a proxy defined by the enclosing scope."""
62 74
63 def __init__(self, *args, **kwargs): 75 def __init__(self, *args, **kwargs):
64 httplib.HTTPSConnection.__init__(self, *args, **kwargs) 76 httplib.HTTPSConnection.__init__(self, *args, **kwargs)
65 77
66 self._tunnel_host = tunnel_host 78 self._tunnel_host = tunnel_host
67 if tunnel_host: 79 if tunnel_host:
68 logging.debug("Creating preset proxy https conn: %s", tunnel_host) 80 logging.debug("Creating preset proxy https conn: %s", tunnel_host)
69 81
70 self.key_file = key_file 82 self.key_file = key_file
71 self.cert_file = cert_file 83 self.cert_file = cert_file
72 self.ca_certs = ca_certs 84 self.ca_certs = ca_certs
73 try: 85 if can_validate_certs():
74 import ssl
75 if self.ca_certs: 86 if self.ca_certs:
76 self.cert_reqs = ssl.CERT_REQUIRED 87 self.cert_reqs = ssl.CERT_REQUIRED
77 else: 88 else:
78 self.cert_reqs = ssl.CERT_NONE 89 self.cert_reqs = ssl.CERT_NONE
79 except ImportError: 90
80 pass 91 def _get_hostport(self, host, port):
92 # Python 2.7.7rc1 (hg r90728:568041fd8090), 3.4.1 and 3.5 rename
93 # _set_hostport to _get_hostport and changes it's functionality. The
94 # Python 2.7.7rc1 version of this method is included here for
95 # compatibility with earlier versions of Python. Without this, HTTPS over
96 # HTTP CONNECT proxies cannot be used.
97
98 # This method may be removed if compatibility with Python <2.7.7rc1 is not
99 # required.
100
101 # Python bug: http://bugs.python.org/issue7776
102 if port is None:
103 i = host.rfind(":")
104 j = host.rfind("]") # ipv6 addresses have [...]
105 if i > j:
106 try:
107 port = int(host[i+1:])
108 except ValueError:
109 if host[i+1:] == "": # http://foo.com:/ == http://foo.com/
110 port = self.default_port
111 else:
112 raise httplib.InvalidURL("nonnumeric port: '%s'" % host[i+1:])
113 host = host[:i]
114 else:
115 port = self.default_port
116 if host and host[0] == "[" and host[-1] == "]":
117 host = host[1:-1]
118
119 return (host, port)
81 120
82 def _tunnel(self): 121 def _tunnel(self):
83 self._set_hostport(self._tunnel_host, None) 122 self.host, self.port = self._get_hostport(self._tunnel_host, None)
84 logging.info("Connecting through tunnel to: %s:%d", 123 logging.info("Connecting through tunnel to: %s:%d",
85 self.host, self.port) 124 self.host, self.port)
86 self.send("CONNECT %s:%d HTTP/1.0\r\n\r\n" % (self.host, self.port)) 125
126 self.send("CONNECT %s:%d HTTP/1.0\r\n" % (self.host, self.port))
127
128 if proxy_authorization:
129 self.send("Proxy-Authorization: %s\r\n" % proxy_authorization)
130
131 # blank line
132 self.send("\r\n")
133
87 response = self.response_class(self.sock, strict=self.strict, 134 response = self.response_class(self.sock, strict=self.strict,
88 method=self._method) 135 method=self._method)
136 # pylint: disable=protected-access
89 (_, code, message) = response._read_status() 137 (_, code, message) = response._read_status()
90 138
91 if code != 200: 139 if code != 200:
92 self.close() 140 self.close()
93 raise socket.error, "Tunnel connection failed: %d %s" % ( 141 raise socket.error("Tunnel connection failed: %d %s" %
94 code, message.strip()) 142 (code, message.strip()))
95 143
96 while True: 144 while True:
97 line = response.fp.readline() 145 line = response.fp.readline()
98 if line == "\r\n": 146 if line == "\r\n":
99 break 147 break
100 148
101 def _get_valid_hosts_for_cert(self, cert): 149 def _get_valid_hosts_for_cert(self, cert):
102 """Returns a list of valid host globs for an SSL certificate. 150 """Returns a list of valid host globs for an SSL certificate.
103 151
104 Args: 152 Args:
105 cert: A dictionary representing an SSL certificate. 153 cert: A dictionary representing an SSL certificate.
106 Returns: 154 Returns:
107 list: A list of valid host globs. 155 list: A list of valid host globs.
108 """ 156 """
109 if 'subjectAltName' in cert: 157 if "subjectAltName" in cert:
110 return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns'] 158 return [x[1] for x in cert["subjectAltName"] if x[0].lower() == "dns"]
111 else: 159 else:
112 # Return a list of commonName fields 160 # Return a list of commonName fields
113 return [x[0][1] for x in cert['subject'] 161 return [x[0][1] for x in cert["subject"]
114 if x[0][0].lower() == 'commonname'] 162 if x[0][0].lower() == "commonname"]
115 163
116 def _validate_certificate_hostname(self, cert, hostname): 164 def _validate_certificate_hostname(self, cert, hostname):
117 """Validates that a given hostname is valid for an SSL certificate. 165 """Perform RFC2818/6125 validation against a cert and hostname.
118 166
119 Args: 167 Args:
120 cert: A dictionary representing an SSL certificate. 168 cert: A dictionary representing an SSL certificate.
121 hostname: The hostname to test. 169 hostname: The hostname to test.
122 Returns: 170 Returns:
123 bool: Whether or not the hostname is valid for this certificate. 171 bool: Whether or not the hostname is valid for this certificate.
124 """ 172 """
125 hosts = self._get_valid_hosts_for_cert(cert) 173 hosts = self._get_valid_hosts_for_cert(cert)
126 for host in hosts: 174 for host in hosts:
127 # Convert the glob-style hostname expression (eg, '*.google.com') into a 175 # Wildcards are only valid when the * exists at the end of the last
128 # valid regular expression. 176 # (left-most) label, and there are at least 3 labels in the expression.
129 host_re = host.replace('.', '\.').replace('*', '[^.]*') 177 if ("*." in host and host.count("*") == 1 and
130 if re.search('^%s$' % (host_re,), hostname, re.I): 178 host.count(".") > 1 and "." in hostname):
179 left_expected, right_expected = host.split("*.")
180 left_hostname, right_hostname = hostname.split(".", 1)
181 if (left_hostname.startswith(left_expected) and
182 right_expected == right_hostname):
183 return True
184 elif host == hostname:
131 return True 185 return True
132 return False 186 return False
133 187
134
135 def connect(self): 188 def connect(self):
136 # TODO(frew): When we drop support for <2.6 (in the far distant future), 189 # TODO(frew): When we drop support for <2.6 (in the far distant future),
137 # change this to socket.create_connection. 190 # change this to socket.create_connection.
138 self.sock = _create_connection((self.host, self.port)) 191 self.sock = _create_connection((self.host, self.port))
139 192
140 if self._tunnel_host: 193 if self._tunnel_host:
141 self._tunnel() 194 self._tunnel()
142 195
143 # ssl and FakeSocket got deprecated. Try for the new hotness of wrap_ssl, 196 # ssl and FakeSocket got deprecated. Try for the new hotness of wrap_ssl,
144 # with fallback. 197 # with fallback. Note: Since can_validate_certs() just checks for the
145 try: 198 # ssl module, it's equivalent to attempting to import ssl from
146 import ssl 199 # the function, but doesn't require a dynamic import, which doesn't
200 # play nicely with dev_appserver.
201 if can_validate_certs():
147 self.sock = ssl.wrap_socket(self.sock, 202 self.sock = ssl.wrap_socket(self.sock,
148 keyfile=self.key_file, 203 keyfile=self.key_file,
149 certfile=self.cert_file, 204 certfile=self.cert_file,
150 ca_certs=self.ca_certs, 205 ca_certs=self.ca_certs,
151 cert_reqs=self.cert_reqs) 206 cert_reqs=self.cert_reqs)
152 207
153 if self.cert_reqs & ssl.CERT_REQUIRED: 208 if self.cert_reqs & ssl.CERT_REQUIRED:
154 cert = self.sock.getpeercert() 209 cert = self.sock.getpeercert()
155 hostname = self.host.split(':', 0)[0] 210 hostname = self.host.split(":", 0)[0]
156 if not self._validate_certificate_hostname(cert, hostname): 211 if not self._validate_certificate_hostname(cert, hostname):
157 raise InvalidCertificateException(hostname, cert, 212 raise InvalidCertificateException(hostname, cert,
158 'hostname mismatch') 213 "hostname mismatch")
159 except ImportError: 214 else:
160 ssl = socket.ssl(self.sock, 215 ssl_socket = socket.ssl(self.sock,
161 keyfile=self.key_file, 216 keyfile=self.key_file,
162 certfile=self.cert_file) 217 certfile=self.cert_file)
163 self.sock = httplib.FakeSocket(self.sock, ssl) 218 self.sock = httplib.FakeSocket(self.sock, ssl_socket)
164 219
165 return PresetProxyHTTPSConnection 220 return PresetProxyHTTPSConnection
166 221
167 222
168 # Here to end of _create_connection copied wholesale from Python 2.6"s socket.py 223 # Here to end of _create_connection copied wholesale from Python 2.6"s socket.py
169 _GLOBAL_DEFAULT_TIMEOUT = object() 224 _GLOBAL_DEFAULT_TIMEOUT = object()
170 225
171 226
172 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT): 227 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
173 """Connect to *address* and return the socket object. 228 """Connect to *address* and return the socket object.
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 # This condition is the change 377 # This condition is the change
323 if orig_type == "https": 378 if orig_type == "https":
324 return None 379 return None
325 380
326 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type) 381 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type)
327 382
328 383
329 class FancyHTTPSHandler(urllib2.HTTPSHandler): 384 class FancyHTTPSHandler(urllib2.HTTPSHandler):
330 """An HTTPSHandler that works with CONNECT-enabled proxies.""" 385 """An HTTPSHandler that works with CONNECT-enabled proxies."""
331 386
332 def do_open(self, http_class, req): 387 def do_open(self, http_class, req, *args, **kwargs):
388 proxy_authorization = None
389 for header in req.headers:
390 if header.lower() == "proxy-authorization":
391 proxy_authorization = req.headers[header]
392 break
393
333 # Intentionally very specific so as to opt for false negatives 394 # Intentionally very specific so as to opt for false negatives
334 # rather than false positives. 395 # rather than false positives.
335 try: 396 try:
336 return urllib2.HTTPSHandler.do_open( 397 return urllib2.HTTPSHandler.do_open(
337 self, 398 self,
338 _create_fancy_connection(req._tunnel_host, 399 create_fancy_connection(req._tunnel_host,
339 req._key_file, 400 req._key_file,
340 req._cert_file, 401 req._cert_file,
341 req._ca_certs), 402 req._ca_certs,
342 req) 403 proxy_authorization),
404 req, *args, **kwargs)
343 except urllib2.URLError, url_error: 405 except urllib2.URLError, url_error:
344 try: 406 try:
345 import ssl 407 import ssl
346 if (type(url_error.reason) == ssl.SSLError and 408 if (type(url_error.reason) == ssl.SSLError and
347 url_error.reason.args[0] == 1): 409 url_error.reason.args[0] == 1):
348 # Display the reason to the user. Need to use args for python2.5 410 # Display the reason to the user. Need to use args for python2.5
349 # compat. 411 # compat.
350 raise InvalidCertificateException(req.host, '', 412 raise InvalidCertificateException(req.host, "",
351 url_error.reason.args[1]) 413 url_error.reason.args[1])
352 except ImportError: 414 except ImportError:
353 pass 415 pass
354 416
355 raise url_error 417 raise url_error
356 418
357 419
358 # We have to implement this so that we persist the tunneling behavior 420 # We have to implement this so that we persist the tunneling behavior
359 # through redirects. 421 # through redirects.
360 class FancyRedirectHandler(urllib2.HTTPRedirectHandler): 422 class FancyRedirectHandler(urllib2.HTTPRedirectHandler):
(...skipping 28 matching lines...) Expand all
389 # req is not proxied, so just make sure _tunnel_host is defined. 451 # req is not proxied, so just make sure _tunnel_host is defined.
390 new_req._tunnel_host = None 452 new_req._tunnel_host = None
391 new_req.type = "https" 453 new_req.type = "https"
392 if hasattr(req, "_key_file") and isinstance(new_req, urllib2.Request): 454 if hasattr(req, "_key_file") and isinstance(new_req, urllib2.Request):
393 # Copy the auxiliary data in case this or any further redirect is https 455 # Copy the auxiliary data in case this or any further redirect is https
394 new_req._key_file = req._key_file 456 new_req._key_file = req._key_file
395 new_req._cert_file = req._cert_file 457 new_req._cert_file = req._cert_file
396 new_req._ca_certs = req._ca_certs 458 new_req._ca_certs = req._ca_certs
397 459
398 return new_req 460 return new_req
OLDNEW
« no previous file with comments | « third_party/fancy_urllib/README ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698