diff --git a/src/httplib2/__init__.py b/src/httplib2/__init__.py index 0b7a5696..6c5d61bd 100644 --- a/src/httplib2/__init__.py +++ b/src/httplib2/__init__.py @@ -20,7 +20,8 @@ __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)", "Jonathan Feinberg", "Blair Zajac", "Sam Ruby", - "Louis Nyffenegger"] + "Louis Nyffenegger", + "Alex Yu"] __license__ = "MIT" __version__ = "0.9.2" @@ -67,21 +68,32 @@ except ImportError: try: import ssl # python 2.6 ssl_SSLError = ssl.SSLError - def _ssl_wrap_socket(sock, key_file, cert_file, - disable_validation, ca_certs): + def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation, + ca_certs, ssl_version, hostname): if disable_validation: cert_reqs = ssl.CERT_NONE else: cert_reqs = ssl.CERT_REQUIRED - # We should be specifying SSL version 3 or TLS v1, but the ssl module - # doesn't expose the necessary knobs. So we need to go with the default - # of SSLv23. - return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file, - cert_reqs=cert_reqs, ca_certs=ca_certs) + if ssl_version is None: + ssl_version = ssl.PROTOCOL_SSLv23 + + if hasattr(ssl, 'SSLContext'): # Python 2.7.9 + context = ssl.SSLContext(ssl_version) + context.verify_mode = cert_reqs + context.check_hostname = (cert_reqs != ssl.CERT_NONE) + if cert_file: + context.load_cert_chain(cert_file, key_file) + if ca_certs: + context.load_verify_locations(ca_certs) + return context.wrap_socket(sock, server_hostname=hostname) + else: + return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file, + cert_reqs=cert_reqs, ca_certs=ca_certs, + ssl_version=ssl_version) except (AttributeError, ImportError): ssl_SSLError = None - def _ssl_wrap_socket(sock, key_file, cert_file, - disable_validation, ca_certs): + def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation, + ca_certs, ssl_version, hostname): if not disable_validation: raise CertificateValidationUnsupported( "SSL certificate validation is not supported without " @@ -162,6 +174,8 @@ class CertificateHostnameMismatch(SSLHandshakeError): self.host = host self.cert = cert +class NotRunningAppEngineEnvironment(HttpLib2Error): pass + # Open Items: # ----------- # Proxy support @@ -749,7 +763,7 @@ class ProxyInfo(object): bypass_hosts = () def __init__(self, proxy_type, proxy_host, proxy_port, - proxy_rdns=True, proxy_user=None, proxy_pass=None): + proxy_rdns=True, proxy_user=None, proxy_pass=None, proxy_headers=None): """ Args: proxy_type: The type of proxy server. This must be set to one of @@ -770,6 +784,8 @@ class ProxyInfo(object): proxy_user: The username used to authenticate with the proxy server. proxy_pass: The password used to authenticate with the proxy server. + + proxy_headers: Additional or modified headers for the proxy connect request. """ self.proxy_type = proxy_type self.proxy_host = proxy_host @@ -777,10 +793,11 @@ class ProxyInfo(object): self.proxy_rdns = proxy_rdns self.proxy_user = proxy_user self.proxy_pass = proxy_pass + self.proxy_headers = proxy_headers def astuple(self): return (self.proxy_type, self.proxy_host, self.proxy_port, - self.proxy_rdns, self.proxy_user, self.proxy_pass) + self.proxy_rdns, self.proxy_user, self.proxy_pass, self.proxy_headers) def isgood(self): return (self.proxy_host != None) and (self.proxy_port != None) @@ -858,6 +875,7 @@ def proxy_info_from_url(url, method='http'): proxy_port = port, proxy_user = username or None, proxy_pass = password or None, + proxy_headers = None, ) @@ -885,7 +903,7 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection): msg = "getaddrinfo returns an empty list" if self.proxy_info and self.proxy_info.isgood(): use_proxy = True - proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple() + proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = self.proxy_info.astuple() host = proxy_host port = proxy_port @@ -900,7 +918,7 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection): try: if use_proxy: self.sock = socks.socksocket(af, socktype, proto) - self.sock.setproxy(proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass) + self.sock.setproxy(proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers) else: self.sock = socket.socket(af, socktype, proto) self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) @@ -911,14 +929,14 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection): if self.debuglevel > 0: print "connect: (%s, %s) ************" % (self.host, self.port) if use_proxy: - print "proxy: %s ************" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass)) + print "proxy: %s ************" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)) self.sock.connect((self.host, self.port) + sa[2:]) except socket.error, msg: if self.debuglevel > 0: print "connect fail: (%s, %s)" % (self.host, self.port) if use_proxy: - print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass)) + print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)) if self.sock: self.sock.close() self.sock = None @@ -938,7 +956,8 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): """ def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=None, proxy_info=None, - ca_certs=None, disable_ssl_certificate_validation=False): + ca_certs=None, disable_ssl_certificate_validation=False, + ssl_version=None): httplib.HTTPSConnection.__init__(self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict) @@ -949,6 +968,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): self.ca_certs = ca_certs self.disable_ssl_certificate_validation = \ disable_ssl_certificate_validation + self.ssl_version = ssl_version # The following two methods were adapted from https_wrapper.py, released # with the Google Appengine SDK at @@ -1007,7 +1027,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): msg = "getaddrinfo returns an empty list" if self.proxy_info and self.proxy_info.isgood(): use_proxy = True - proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple() + proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers = self.proxy_info.astuple() host = proxy_host port = proxy_port @@ -1023,7 +1043,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): if use_proxy: sock = socks.socksocket(family, socktype, proto) - sock.setproxy(proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass) + sock.setproxy(proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers) else: sock = socket.socket(family, socktype, proto) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) @@ -1033,11 +1053,12 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): sock.connect((self.host, self.port)) self.sock =_ssl_wrap_socket( sock, self.key_file, self.cert_file, - self.disable_ssl_certificate_validation, self.ca_certs) + self.disable_ssl_certificate_validation, self.ca_certs, + self.ssl_version, self.host) if self.debuglevel > 0: print "connect: (%s, %s)" % (self.host, self.port) if use_proxy: - print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass)) + print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)) if not self.disable_ssl_certificate_validation: cert = self.sock.getpeercert() hostname = self.host.split(':', 0)[0] @@ -1065,7 +1086,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): if self.debuglevel > 0: print "connect fail: (%s, %s)" % (self.host, self.port) if use_proxy: - print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass)) + print "proxy: %s" % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers)) if self.sock: self.sock.close() self.sock = None @@ -1079,8 +1100,61 @@ SCHEME_TO_CONNECTION = { 'https': HTTPSConnectionWithTimeout } + +def _new_fixed_fetch(validate_certificate): + def fixed_fetch(url, payload=None, method="GET", headers={}, + allow_truncated=False, follow_redirects=True, + deadline=None): + if deadline is None: + deadline = socket.getdefaulttimeout() + return fetch(url, payload=payload, method=method, headers=headers, + allow_truncated=allow_truncated, + follow_redirects=follow_redirects, deadline=deadline, + validate_certificate=validate_certificate) + return fixed_fetch + + +class AppEngineHttpConnection(httplib.HTTPConnection): + """Use httplib on App Engine, but compensate for its weirdness. + + The parameters key_file, cert_file, proxy_info, ca_certs, + disable_ssl_certificate_validation, and ssl_version are all dropped on + the ground. + """ + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, timeout=None, proxy_info=None, ca_certs=None, + disable_ssl_certificate_validation=False, + ssl_version=None): + httplib.HTTPConnection.__init__(self, host, port=port, + strict=strict, timeout=timeout) + + +class AppEngineHttpsConnection(httplib.HTTPSConnection): + """Same as AppEngineHttpConnection, but for HTTPS URIs. + + The parameters proxy_info, ca_certs, disable_ssl_certificate_validation, + and ssl_version are all dropped on the ground. + """ + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, timeout=None, proxy_info=None, ca_certs=None, + disable_ssl_certificate_validation=False, + ssl_version=None): + httplib.HTTPSConnection.__init__(self, host, port=port, + key_file=key_file, + cert_file=cert_file, strict=strict, + timeout=timeout) + self._fetch = _new_fixed_fetch( + not disable_ssl_certificate_validation) + # Use a different connection object for Google App Engine try: + server_software = os.environ.get('SERVER_SOFTWARE') + if not server_software: + raise NotRunningAppEngineEnvironment() + elif not (server_software.startswith('Google App Engine/') or + server_software.startswith('Development/')): + raise NotRunningAppEngineEnvironment() + try: from google.appengine.api import apiproxy_stub_map if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None: @@ -1094,48 +1168,12 @@ try: from google3.apphosting.api.urlfetch import fetch from google3.apphosting.api.urlfetch import InvalidURLError - def _new_fixed_fetch(validate_certificate): - def fixed_fetch(url, payload=None, method="GET", headers={}, - allow_truncated=False, follow_redirects=True, - deadline=None): - if deadline is None: - deadline = socket.getdefaulttimeout() or 5 - return fetch(url, payload=payload, method=method, headers=headers, - allow_truncated=allow_truncated, - follow_redirects=follow_redirects, deadline=deadline, - validate_certificate=validate_certificate) - return fixed_fetch - - class AppEngineHttpConnection(httplib.HTTPConnection): - """Use httplib on App Engine, but compensate for its weirdness. - - The parameters key_file, cert_file, proxy_info, ca_certs, and - disable_ssl_certificate_validation are all dropped on the ground. - """ - def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=None, proxy_info=None, ca_certs=None, - disable_ssl_certificate_validation=False): - httplib.HTTPConnection.__init__(self, host, port=port, - strict=strict, timeout=timeout) - - class AppEngineHttpsConnection(httplib.HTTPSConnection): - """Same as AppEngineHttpConnection, but for HTTPS URIs.""" - def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=None, proxy_info=None, ca_certs=None, - disable_ssl_certificate_validation=False): - httplib.HTTPSConnection.__init__(self, host, port=port, - key_file=key_file, - cert_file=cert_file, strict=strict, - timeout=timeout) - self._fetch = _new_fixed_fetch( - not disable_ssl_certificate_validation) - # Update the connection classes to use the Googel App Engine specific ones. SCHEME_TO_CONNECTION = { 'http': AppEngineHttpConnection, 'https': AppEngineHttpsConnection } -except (ImportError, AttributeError): +except (ImportError, AttributeError, NotRunningAppEngineEnvironment): pass @@ -1155,7 +1193,8 @@ class Http(object): """ def __init__(self, cache=None, timeout=None, proxy_info=proxy_info_from_environment, - ca_certs=None, disable_ssl_certificate_validation=False): + ca_certs=None, disable_ssl_certificate_validation=False, + ssl_version=None): """If 'cache' is a string then it is used as a directory name for a disk cache. Otherwise it must be an object that supports the same interface as FileCache. @@ -1178,11 +1217,14 @@ class Http(object): If disable_ssl_certificate_validation is true, SSL cert validation will not be performed. + + By default, ssl.PROTOCOL_SSLv23 will be used for the ssl version. """ self.proxy_info = proxy_info self.ca_certs = ca_certs self.disable_ssl_certificate_validation = \ disable_ssl_certificate_validation + self.ssl_version = ssl_version # Map domain name to an httplib connection self.connections = {} @@ -1285,9 +1327,10 @@ class Http(object): err = getattr(e, 'args')[0] else: err = e.errno + if err == errno.ECONNREFUSED: # Connection refused + raise if err in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES: continue # retry on potentially transient socket errors - raise except httplib.HTTPException: # Just because the server closed the connection doesn't apparently mean # that the server didn't send a response. @@ -1477,14 +1520,16 @@ class Http(object): proxy_info=proxy_info, ca_certs=self.ca_certs, disable_ssl_certificate_validation= - self.disable_ssl_certificate_validation) + self.disable_ssl_certificate_validation, + ssl_version=self.ssl_version) else: conn = self.connections[conn_key] = connection_type( authority, timeout=self.timeout, proxy_info=proxy_info, ca_certs=self.ca_certs, disable_ssl_certificate_validation= - self.disable_ssl_certificate_validation) + self.disable_ssl_certificate_validation, + ssl_version=self.ssl_version) else: conn = self.connections[conn_key] = connection_type( authority, timeout=self.timeout, diff --git a/src/httplib2/cacerts.txt b/src/httplib2/cacerts.txt index 70990f1f..b2fe5f18 100644 --- a/src/httplib2/cacerts.txt +++ b/src/httplib2/cacerts.txt @@ -102,28 +102,6 @@ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 149843929435818692848040365716851702463 -# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 -# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 -# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do -lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc -AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k ------END CERTIFICATE----- - # Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network # Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network # Label: "Verisign Class 3 Public Primary Certification Authority - G2" @@ -1800,28 +1778,6 @@ kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 80507572722862485515306429940691309246 -# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 -# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b -# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- - # Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 # Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 # Label: "GlobalSign Root CA - R3" @@ -2181,3 +2137,31 @@ IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI -----END CERTIFICATE----- + +# Issuer: O=Digital Signature Trust Co., CN=DST Root CA X3 +# Subject: O=Digital Signature Trust Co., CN=DST Root CA X3 +# Label: "IdenTrust DST Root CA X3" +# Serial: 44AFB080D6A327BA893039862EF8406B +# MD5 Fingerprint: 41:03:52:DC:0F:F7:50:1B:16:F0:02:8E:BA:6F:45:C5 +# SHA1 Fingerprint: DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13 +# SHA256 Fingerprint: 06:87:26:03:31:A7:24:03:D9:09:F1:05:E6:9B:CF:0D:32:E1:BD:24:93:FF:C6:D9:20:6D:11:BC:D6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- diff --git a/src/httplib2/socks.py b/src/httplib2/socks.py index 0991f4cf..ab516caa 100644 --- a/src/httplib2/socks.py +++ b/src/httplib2/socks.py @@ -177,7 +177,7 @@ class socksocket(socket.socket): auth = self.__proxy[4] + ":" + self.__proxy[5] return "Proxy-Authorization: Basic " + base64.b64encode(auth) - def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None, headers=None): """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) Sets the proxy to be used. proxytype - The type of the proxy to be used. Three types @@ -193,8 +193,9 @@ class socksocket(socket.socket): The default is no authentication. password - Password to authenticate with to the server. Only relevant when username is also provided. + headers - Additional or modified headers for the proxy connect request. """ - self.__proxy = (proxytype, addr, port, rdns, username, password) + self.__proxy = (proxytype, addr, port, rdns, username, password, headers) def __negotiatesocks5(self, destaddr, destport): """__negotiatesocks5(self,destaddr,destport) @@ -365,8 +366,17 @@ class socksocket(socket.socket): else: addr = destaddr headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"] - headers += ["Host: ", destaddr, "\r\n"] - if (self.__proxy[4] != None and self.__proxy[5] != None): + wrote_host_header = False + wrote_auth_header = False + if self.__proxy[6] != None: + for key, val in self.__proxy[6].iteritems(): + headers += [key, ": ", val, "\r\n"] + wrote_host_header = (key.lower() == "host") + wrote_auth_header = (key.lower() == "proxy-authorization") + if not wrote_host_header: + headers += ["Host: ", destaddr, "\r\n"] + if not wrote_auth_header: + if (self.__proxy[4] != None and self.__proxy[5] != None): headers += [self.__getauthheader(), "\r\n"] headers.append("\r\n") self.sendall("".join(headers).encode()) diff --git a/src/httplib2/test/miniserver.py b/src/httplib2/test/miniserver.py index e32bf5e5..f72eccac 100644 --- a/src/httplib2/test/miniserver.py +++ b/src/httplib2/test/miniserver.py @@ -2,6 +2,7 @@ import logging import os import select import SimpleHTTPServer +import socket import SocketServer import threading @@ -27,11 +28,23 @@ class ShutdownServer(SocketServer.TCPServer): BaseServer supports the shutdown method directly. """ - def __init__(self, *args, **kwargs): + def __init__(self, use_tls, *args, **kwargs): + self.__use_tls = use_tls SocketServer.TCPServer.__init__(self, *args, **kwargs) self.__is_shut_down = threading.Event() self.__serving = False + def server_bind(self): + SocketServer.TCPServer.server_bind(self) + if self.__use_tls: + import ssl + self.socket = ssl.wrap_socket(self.socket, + os.path.join(os.path.dirname(__file__), 'server.key'), + os.path.join(os.path.dirname(__file__), 'server.pem'), + True + ) + + def serve_forever(self, poll_interval=0.1): """Handle one request at a time until shutdown. @@ -93,8 +106,8 @@ class ShutdownServer(SocketServer.TCPServer): self.close_request(request) -def start_server(handler): - httpd = ShutdownServer(("", 0), handler) +def start_server(handler, use_tls=False): + httpd = ShutdownServer(use_tls, ("", 0), handler) threading.Thread(target=httpd.serve_forever).start() _, port = httpd.socket.getsockname() return httpd, port diff --git a/src/httplib2/test/other_cacerts.txt b/src/httplib2/test/other_cacerts.txt index 360954a2..b2c24884 100644 --- a/src/httplib2/test/other_cacerts.txt +++ b/src/httplib2/test/other_cacerts.txt @@ -1,70 +1,19 @@ -# Certifcate Authority certificates for validating SSL connections. -# -# This file contains PEM format certificates generated from -# http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt -# -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is the Netscape security libraries. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1994-2000 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# -# Alternatively, the contents of this file may be used under the terms of -# either the GNU General Public License Version 2 or later (the "GPL"), or -# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** - - -Comodo CA Limited, CN=Trusted Certificate Services -================================================== - -----BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 -aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla -MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD -VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW -fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt -TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL -fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW -1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 -kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G -A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v -ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo -dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu -Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ -HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 -pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS -jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ -xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn -dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi +MIIDBzCCAe+gAwIBAgIJAIw94zvO7fk1MA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV +BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNjA2MDQwMjMxMTRaFw0yNjA2MDIwMjMx +MTRaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAK3YNcDIwK/wlTa0/iBARvDFOncQ6Jkk+Ymql1HXny7v +mWPFWeLXEW+Zw1NrQEx/SIUGvxpRA+QyhTOhu2Gcwvtqilix/dHgaKgqWEcRYu8m +L70uVDPVgB/kfNI8bpXM1Mz8Crjo0tHw5oUSD3wny8SyT6CYlXVmF923L8c2zdN9 +n9blFgYwxBq2+q+mqOiDErMFbwHES8FNBSWGBXdE1xjBdITtlfeHezmJhj/ylPW1 +7v8HInsv/WqU9DcJYlFxSnK0SZCLFBM/31Ez8O1gCfMlDUFvJoo59GyFqukUjuO1 +uB85wpu27gtcLm/J9X1Md71IxbDupV7a0dDoTvbhO4kCAwEAAaNQME4wHQYDVR0O +BBYEFIHgAmwppZSKLz2peyFSO2kwVobNMB8GA1UdIwQYMBaAFIHgAmwppZSKLz2p +eyFSO2kwVobNMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJxz+AU/ +Iq8fMEStJ0BgPP1N86W9Jpb7aPMFCYTEZ+nd8hFPhPs4//55J0yIve+1I43MNFFz +yflwwCzrIIhZdkvbsyea6CmlTo4jBc4+ihaDGobYnoNzFhavC47n5kYqJ8Ikyb2W +OMrmNRiaTeSBl0wQmftnnQCbonenjmE1LDuJtE6bCwfFjfLbMxwdWtp/ymOlXsb5 +80XcWwcqc12UHWexYwHFzEJmDfncak/8tjHBsLWMJg5p2sVTY9kVt7TYgSIl+mFb +4WVGrqZd2uTlJkRQQ4pCl+D+PKwadHuV6YI7oxkeajjcHCgbK/ANwW28MXYho6t6 +aWVIN4bWHrZ38kE= -----END CERTIFICATE----- diff --git a/src/httplib2/test/server.key b/src/httplib2/test/server.key new file mode 100644 index 00000000..4c21facf --- /dev/null +++ b/src/httplib2/test/server.key @@ -0,0 +1,146 @@ +Private TLS server key file used for HTTPS-related unit tests +------------------------------------------------------------- + +Public Key Info: + Public Key Algorithm: RSA + Key Security Level: Medium (2048 bits) + +modulus: + 00:cc:fb:f1:c5:de:29:29:40:3f:c4:9f:af:da:f6:be + 27:f4:6a:00:ae:5a:f2:99:c3:5f:7a:e6:9b:cf:d9:08 + 34:01:9b:ea:fb:da:b5:d0:b5:b2:4e:60:b4:0d:8d:05 + 57:e4:2e:04:d4:57:1a:58:3c:0b:3a:ed:67:a3:13:31 + 58:0a:c2:eb:fd:d6:27:ee:07:95:30:35:b5:98:91:c7 + a5:9b:be:a9:7e:ae:fd:73:c3:6b:21:bc:52:f8:ef:71 + db:d3:b1:cd:51:df:b3:37:b3:fd:7d:ae:7e:02:38:be + 8e:6f:45:55:e5:6d:8a:02:cb:36:c4:17:7a:ea:24:9a + 72:8d:1e:75:03:3a:6f:c4:cb:a0:3a:50:56:32:bb:4c + e2:ea:74:f0:96:31:74:b2:c1:03:e8:c3:d4:a3:59:fc + 7a:cc:68:35:c4:97:eb:aa:46:fa:64:c3:f9:55:59:22 + b5:2b:3c:96:84:c6:d2:7d:b4:9f:b9:9c:af:d1:20:30 + 7c:e8:60:4e:ee:0a:60:a0:9d:4e:8a:d8:34:74:bd:f2 + 40:bc:d7:c2:b3:1a:b2:bb:d7:a5:4a:4c:65:94:43:82 + 16:9a:8f:76:2a:05:b0:9e:3d:a7:fb:e2:c7:78:25:f7 + df:ca:08:ee:ec:4f:cd:1a:3c:03:41:ec:91:c5:50:70 + 4b: + +public exponent: + 01:00:01: + +private exponent: + 00:ad:01:83:b8:7d:dd:fd:ab:f5:66:2d:64:ce:08:ec + cb:6a:15:41:87:e6:c8:d5:10:39:78:d0:43:f7:73:f4 + e1:77:ee:31:b0:e9:92:04:9a:25:e8:d2:e3:84:80:5e + 5f:24:fd:d6:23:a5:74:5d:be:27:b8:4f:80:e5:f9:1f + ef:6f:fd:be:12:1a:7a:cf:02:65:5f:30:25:99:a4:88 + 7d:74:ea:c1:c1:63:4e:15:33:7d:2b:16:f8:6c:94:23 + 63:e6:d3:2d:38:89:f6:87:f0:08:e5:d7:ad:10:90:f5 + fb:df:5c:04:b8:43:f0:74:95:31:1e:e5:b6:5f:02:0f + bb:55:cb:e1:b5:48:9f:1f:d3:1b:55:a7:bc:39:2b:8e + 6d:14:64:3b:bf:e8:ca:6b:af:a9:f3:13:9a:c6:df:15 + ef:6d:17:4e:8e:67:6c:41:20:dc:6b:08:0d:b9:14:cd + 83:10:62:15:e6:b0:89:5d:37:fb:f6:fd:f0:bf:3b:9c + 0b:e9:fd:b8:de:e4:64:90:bf:81:d5:59:2c:30:43:07 + b9:60:8c:d0:ac:4f:95:87:aa:38:62:bd:c7:06:a7:c4 + 2d:08:c1:3c:86:10:c7:8e:1e:df:58:bf:95:ad:39:84 + a0:2b:13:e2:18:e6:4a:80:f0:bc:04:50:bd:7d:cf:23 + a1: + +prime1: + 00:f0:8f:ad:2f:c9:64:f3:0d:2c:aa:06:17:05:8f:2f + d5:cb:92:22:90:05:66:3c:78:75:9d:7b:4c:6a:af:a9 + 1e:d6:28:4f:13:0e:3a:e7:31:49:3d:87:ef:2c:17:70 + be:69:b3:42:82:6d:9c:b4:13:0a:e4:bc:8c:0f:1a:bd + 04:b6:a0:be:ba:12:15:bf:04:db:91:1c:26:91:d6:d7 + f2:ff:2f:0e:5f:96:a1:7c:4b:90:a8:2f:07:2a:cb:dc + 40:a0:0b:1d:2a:1d:48:98:bd:4a:6b:9d:5c:69:b0:2b + 6e:9b:2c:b2:a9:cb:28:fe:fa:7f:93:eb:20:c8:59:d0 + 11: + +prime2: + 00:da:23:c0:3e:82:4c:88:7c:d4:fb:de:24:45:eb:9c + ae:2c:80:2d:52:a6:95:05:33:b9:d8:c1:7b:52:01:62 + 11:e6:b6:c6:0d:56:a3:68:39:26:9a:90:08:95:12:a9 + 1c:59:f6:0b:1d:af:6d:c0:c6:9b:2e:7a:62:98:21:36 + e1:15:4c:e6:6d:a4:08:ac:90:af:57:86:71:78:2e:0e + cf:59:0f:35:79:cb:6a:a2:e2:30:2a:a8:f2:84:68:bc + 8a:f2:48:3b:07:d5:a5:34:f3:d3:ec:25:61:38:f1:0a + 07:f7:7e:29:61:e4:15:01:80:e3:7b:bd:63:9c:2e:16 + 9b: + +coefficient: + 00:cb:b4:d2:9f:b4:04:db:8c:54:e6:ae:a9:28:a0:c9 + 70:ad:7a:94:72:5e:86:33:91:d9:43:61:2b:4d:55:e8 + b7:25:d2:cd:db:1e:c4:56:95:68:85:e2:9b:4f:31:24 + 3a:40:06:41:1c:aa:7a:31:13:fa:07:e0:a6:59:c3:d1 + d2:c5:2c:6a:82:98:bb:a1:59:c0:6f:ad:d7:2e:ed:5a + 64:5f:e6:ea:4a:ee:45:29:d9:0f:96:b3:39:f7:ab:57 + 97:aa:c9:f7:b6:9c:c0:51:5d:9f:01:2c:ec:58:8d:06 + 6a:19:d0:33:74:11:6a:25:7c:8f:b7:31:d2:97:05:02 + 6f: + +exp1: + 00:87:60:43:95:1d:e0:0a:8b:82:74:18:43:42:64:a7 + 05:c8:ae:ef:76:5f:23:7e:aa:47:7e:1d:52:0e:c3:d6 + 07:bd:7b:27:ac:d0:98:43:5c:d0:1b:a9:70:e6:3e:36 + bb:61:5e:78:f2:4f:5f:1d:53:8e:10:d5:2e:78:9d:92 + 7b:a1:8e:ea:66:6a:21:04:c3:66:10:ce:67:c2:30:c6 + 8c:40:21:2a:14:8e:ff:47:a4:7a:be:ba:e0:6c:ac:16 + c1:e3:8e:fd:95:a2:af:25:0d:79:61:00:48:6e:4d:ae + d3:6a:ce:07:a9:57:e4:35:41:a1:24:0b:f1:01:ee:d1 + 11: + +exp2: + 00:ca:ca:bd:a7:de:fe:43:4c:b9:bb:c4:d2:37:e6:47 + ec:6c:16:65:0c:17:2d:26:7e:e5:e1:2a:4d:f8:f8:ac + 31:34:28:ea:89:ef:e7:4d:b7:03:ba:60:f8:79:8d:b5 + 85:53:e4:b6:84:cc:57:de:05:44:b2:ba:b7:f9:f1:b6 + d1:1d:3a:36:65:eb:3e:dd:1e:4c:c3:b3:8a:bd:4d:24 + 1b:83:11:ee:86:e1:a2:aa:f6:58:0c:f0:af:34:85:21 + f2:92:36:b0:1a:22:75:c9:7a:7b:a3:67:44:b0:e8:f4 + 88:5f:7e:fb:fd:b3:4a:0b:f1:c4:89:7e:91:a1:d9:fe + cd: + + +Public Key ID: 92:D5:B4:2A:B6:A8:64:67:2C:2A:08:DB:51:B8:97:86:5E:44:CD:6C +Public key's random art: ++--[ RSA 2048]----+ +| + . | +| . E o . | +| o . . o | +| . o o . | +| = .= S | +|. +.=o + | +|o++== . | +|++o= | +|o . | ++-----------------+ + + +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEAzPvxxd4pKUA/xJ+v2va+J/RqAK5a8pnDX3rmm8/ZCDQBm+r7 +2rXQtbJOYLQNjQVX5C4E1FcaWDwLOu1noxMxWArC6/3WJ+4HlTA1tZiRx6Wbvql+ +rv1zw2shvFL473Hb07HNUd+zN7P9fa5+Aji+jm9FVeVtigLLNsQXeuokmnKNHnUD +Om/Ey6A6UFYyu0zi6nTwljF0ssED6MPUo1n8esxoNcSX66pG+mTD+VVZIrUrPJaE +xtJ9tJ+5nK/RIDB86GBO7gpgoJ1Oitg0dL3yQLzXwrMasrvXpUpMZZRDghaaj3Yq +BbCePaf74sd4Jfffygju7E/NGjwDQeyRxVBwSwIDAQABAoIBAQCtAYO4fd39q/Vm +LWTOCOzLahVBh+bI1RA5eNBD93P04XfuMbDpkgSaJejS44SAXl8k/dYjpXRdvie4 +T4Dl+R/vb/2+Ehp6zwJlXzAlmaSIfXTqwcFjThUzfSsW+GyUI2Pm0y04ifaH8Ajl +160QkPX731wEuEPwdJUxHuW2XwIPu1XL4bVInx/TG1WnvDkrjm0UZDu/6Mprr6nz +E5rG3xXvbRdOjmdsQSDcawgNuRTNgxBiFeawiV03+/b98L87nAvp/bje5GSQv4HV +WSwwQwe5YIzQrE+Vh6o4Yr3HBqfELQjBPIYQx44e31i/la05hKArE+IY5kqA8LwE +UL19zyOhAoGBAPCPrS/JZPMNLKoGFwWPL9XLkiKQBWY8eHWde0xqr6ke1ihPEw46 +5zFJPYfvLBdwvmmzQoJtnLQTCuS8jA8avQS2oL66EhW/BNuRHCaR1tfy/y8OX5ah +fEuQqC8HKsvcQKALHSodSJi9SmudXGmwK26bLLKpyyj++n+T6yDIWdARAoGBANoj +wD6CTIh81PveJEXrnK4sgC1SppUFM7nYwXtSAWIR5rbGDVajaDkmmpAIlRKpHFn2 +Cx2vbcDGmy56YpghNuEVTOZtpAiskK9XhnF4Lg7PWQ81ectqouIwKqjyhGi8ivJI +OwfVpTTz0+wlYTjxCgf3filh5BUBgON7vWOcLhabAoGBAIdgQ5Ud4AqLgnQYQ0Jk +pwXIru92XyN+qkd+HVIOw9YHvXsnrNCYQ1zQG6lw5j42u2FeePJPXx1TjhDVLnid +knuhjupmaiEEw2YQzmfCMMaMQCEqFI7/R6R6vrrgbKwWweOO/ZWiryUNeWEASG5N +rtNqzgepV+Q1QaEkC/EB7tERAoGBAMrKvafe/kNMubvE0jfmR+xsFmUMFy0mfuXh +Kk34+KwxNCjqie/nTbcDumD4eY21hVPktoTMV94FRLK6t/nxttEdOjZl6z7dHkzD +s4q9TSQbgxHuhuGiqvZYDPCvNIUh8pI2sBoidcl6e6NnRLDo9Ihffvv9s0oL8cSJ +fpGh2f7NAoGBAMu00p+0BNuMVOauqSigyXCtepRyXoYzkdlDYStNVei3JdLN2x7E +VpVoheKbTzEkOkAGQRyqejET+gfgplnD0dLFLGqCmLuhWcBvrdcu7VpkX+bqSu5F +KdkPlrM596tXl6rJ97acwFFdnwEs7FiNBmoZ0DN0EWolfI+3MdKXBQJv +-----END RSA PRIVATE KEY----- diff --git a/src/httplib2/test/server.pem b/src/httplib2/test/server.pem new file mode 100644 index 00000000..49b6efc8 --- /dev/null +++ b/src/httplib2/test/server.pem @@ -0,0 +1,50 @@ +Public, self-signed TLS server key file used for HTTPS-related unit tests +------------------------------------------------------------------------- + +Public Key Information: + Public Key Algorithm: RSA + Algorithm Security Level: Medium (2048 bits) + Modulus (bits 2048): + 00:cc:fb:f1:c5:de:29:29:40:3f:c4:9f:af:da:f6:be + 27:f4:6a:00:ae:5a:f2:99:c3:5f:7a:e6:9b:cf:d9:08 + 34:01:9b:ea:fb:da:b5:d0:b5:b2:4e:60:b4:0d:8d:05 + 57:e4:2e:04:d4:57:1a:58:3c:0b:3a:ed:67:a3:13:31 + 58:0a:c2:eb:fd:d6:27:ee:07:95:30:35:b5:98:91:c7 + a5:9b:be:a9:7e:ae:fd:73:c3:6b:21:bc:52:f8:ef:71 + db:d3:b1:cd:51:df:b3:37:b3:fd:7d:ae:7e:02:38:be + 8e:6f:45:55:e5:6d:8a:02:cb:36:c4:17:7a:ea:24:9a + 72:8d:1e:75:03:3a:6f:c4:cb:a0:3a:50:56:32:bb:4c + e2:ea:74:f0:96:31:74:b2:c1:03:e8:c3:d4:a3:59:fc + 7a:cc:68:35:c4:97:eb:aa:46:fa:64:c3:f9:55:59:22 + b5:2b:3c:96:84:c6:d2:7d:b4:9f:b9:9c:af:d1:20:30 + 7c:e8:60:4e:ee:0a:60:a0:9d:4e:8a:d8:34:74:bd:f2 + 40:bc:d7:c2:b3:1a:b2:bb:d7:a5:4a:4c:65:94:43:82 + 16:9a:8f:76:2a:05:b0:9e:3d:a7:fb:e2:c7:78:25:f7 + df:ca:08:ee:ec:4f:cd:1a:3c:03:41:ec:91:c5:50:70 + 4b + Exponent (bits 24): + 01:00:01 + +Public Key Usage: + +Public Key ID: 92d5b42ab6a864672c2a08db51b897865e44cd6c + + +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAISbkoXpX75CMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xNjA2MTcxNDA1NTlaFw0yNjA2MTUxNDA1NTlaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMz78cXeKSlAP8Sfr9r2vif0agCuWvKZw1965pvP2Qg0AZvq+9q10LWyTmC0 +DY0FV+QuBNRXGlg8CzrtZ6MTMVgKwuv91ifuB5UwNbWYkcelm76pfq79c8NrIbxS ++O9x29OxzVHfszez/X2ufgI4vo5vRVXlbYoCyzbEF3rqJJpyjR51AzpvxMugOlBW +MrtM4up08JYxdLLBA+jD1KNZ/HrMaDXEl+uqRvpkw/lVWSK1KzyWhMbSfbSfuZyv +0SAwfOhgTu4KYKCdTorYNHS98kC818KzGrK716VKTGWUQ4IWmo92KgWwnj2n++LH +eCX338oI7uxPzRo8A0HskcVQcEsCAwEAAaNQME4wHQYDVR0OBBYEFFdXD8Z8k0et +ZNyM4e4WypNnGlcCMB8GA1UdIwQYMBaAFFdXD8Z8k0etZNyM4e4WypNnGlcCMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAC4FsnK1Ph/JpdoqSRTCJiVM +MPFaaavKEEyYdAKPk/Acmb9vf07sqsT+OZg/0obsZG9LxJb7x0iAnhfM3aS+CmO9 +Ym2lXeFDaJ2bHooB9MsG2C3+n8lJUMwxm7Cqpff/lpCK6Z+6MGPx3GRs6HUEl34k +BB5pue2vqhtFQ03UdHMpAK0M7n3TloAWbFb1a/JmqzTbsQ0oaMHGoECQEAbaBl+a +/up6vA3iZHq+ZPYS1KIx+xuT/SapLcyUtjfhmq1bROVZP4+6EHMsBMnhJBYKxxHy +0qKvqJL9X3NQLMgMKKUKzX+BuG2u5aRRyVIqewT/ORjaUr9Y8lU7WlXPf7Ljm6s= +-----END CERTIFICATE----- diff --git a/src/httplib2/test/test_ssl_context.py b/src/httplib2/test/test_ssl_context.py new file mode 100644 index 00000000..baea240a --- /dev/null +++ b/src/httplib2/test/test_ssl_context.py @@ -0,0 +1,66 @@ +#!/usr/bin/python2 +import BaseHTTPServer +import logging +import os.path +import unittest +import sys + +import httplib2 + +from httplib2.test import miniserver + +logger = logging.getLogger(__name__) + +class KeepAliveHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """ + Request handler that keeps the HTTP connection open, so that the test can + inspect the resulting SSL connection object + """ + def do_GET(self): + self.send_response(200) + self.send_header("Content-Length", "0") + self.send_header("Connection", "keep-alive") + self.end_headers() + + self.close_connection = 0 + + def log_message(self, s, *args): + # output via logging so nose can catch it + logger.info(s, *args) + +class HttpsContextTest(unittest.TestCase): + def setUp(self): + if sys.version_info < (2, 7, 9): + return + + self.httpd, self.port = miniserver.start_server( + KeepAliveHandler, True) + + def tearDown(self): + self.httpd.shutdown() + + def testHttpsContext(self): + if sys.version_info < (2, 7, 9): + if hasattr(unittest, "skipTest"): + self.skipTest("SSLContext requires Python 2.7.9")# Python 2.7.0 + else: + return + import ssl + + client = httplib2.Http( + ca_certs=os.path.join(os.path.dirname(__file__), 'server.pem')) + + # Establish connection to local server + client.request('https://localhost:%d/' % (self.port)) + + # Verify that connection uses a TLS context with the correct hostname + conn = client.connections['https:localhost:%d' % self.port] + + self.assertIsInstance(conn.sock, ssl.SSLSocket) + self.assertTrue(hasattr(conn.sock, 'context')) + self.assertIsInstance(conn.sock.context, ssl.SSLContext) + self.assertTrue(conn.sock.context.check_hostname) + self.assertEqual(conn.sock.server_hostname, 'localhost') + self.assertEqual(conn.sock.context.check_hostname, True) + self.assertEqual(conn.sock.context.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(conn.sock.context.protocol, ssl.PROTOCOL_SSLv23)