__init__.py (51966B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\httplib2\__init__.py 2 from __future__ import generators 3 from __future__ import print_function 4 from __future__ import absolute_import 5 __author__ = 'Joe Gregorio (joe@bitworking.org)' 6 __copyright__ = 'Copyright 2006, Joe Gregorio' 7 __contributors__ = ['Thomas Broyer (t.broyer@ltgt.net)', 8 'James Antill', 9 'Xavier Verges Farrero', 10 'Jonathan Feinberg', 11 'Blair Zajac', 12 'Sam Ruby', 13 'Louis Nyffenegger'] 14 __license__ = 'MIT' 15 __version__ = '0.9.2' 16 import re 17 import sys 18 import email 19 import email.Utils 20 import email.Message 21 import email.FeedParser 22 import StringIO 23 import gzip 24 import zlib 25 import httplib 26 import urlparse 27 import urllib 28 import base64 29 import os 30 import copy 31 import calendar 32 import time 33 import random 34 import errno 35 try: 36 from hashlib import sha1 as _sha, md5 as _md5 37 except ImportError: 38 import sha 39 import md5 40 _sha = sha.new 41 _md5 = md5.new 42 43 import hmac 44 from gettext import gettext as _ 45 import socket 46 try: 47 from httplib2 import socks 48 except ImportError: 49 try: 50 from . import socks 51 except (ImportError, AttributeError): 52 socks = None 53 54 try: 55 import ssl 56 ssl_SSLError = ssl.SSLError 57 58 def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation, ca_certs): 59 if disable_validation: 60 cert_reqs = ssl.CERT_NONE 61 else: 62 cert_reqs = ssl.CERT_REQUIRED 63 return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file, cert_reqs=cert_reqs, ca_certs=ca_certs) 64 65 66 except (AttributeError, ImportError): 67 ssl_SSLError = None 68 69 def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation, ca_certs): 70 if not disable_validation: 71 raise CertificateValidationUnsupported('SSL certificate validation is not supported without the ssl module installed. To avoid this error, install the ssl module, or explicity disable validation.') 72 ssl_sock = socket.ssl(sock, key_file, cert_file) 73 return httplib.FakeSocket(sock, ssl_sock) 74 75 76 if sys.version_info >= (2, 3): 77 from .iri2uri import iri2uri 78 else: 79 80 def iri2uri(uri): 81 return uri 82 83 84 def has_timeout(timeout): 85 if hasattr(socket, '_GLOBAL_DEFAULT_TIMEOUT'): 86 return timeout is not None and timeout is not socket._GLOBAL_DEFAULT_TIMEOUT 87 return timeout is not None 88 89 90 __all__ = ['Http', 91 'Response', 92 'ProxyInfo', 93 'HttpLib2Error', 94 'RedirectMissingLocation', 95 'RedirectLimit', 96 'FailedToDecompressContent', 97 'UnimplementedDigestAuthOptionError', 98 'UnimplementedHmacDigestAuthOptionError', 99 'debuglevel', 100 'ProxiesUnavailableError'] 101 debuglevel = 0 102 RETRIES = 2 103 if sys.version_info < (2, 4): 104 105 def sorted(seq): 106 seq.sort() 107 return seq 108 109 110 def HTTPResponse__getheaders(self): 111 if self.msg is None: 112 raise httplib.ResponseNotReady() 113 return self.msg.items() 114 115 116 if not hasattr(httplib.HTTPResponse, 'getheaders'): 117 httplib.HTTPResponse.getheaders = HTTPResponse__getheaders 118 119 class HttpLib2Error(Exception): 120 pass 121 122 123 class HttpLib2ErrorWithResponse(HttpLib2Error): 124 125 def __init__(self, desc, response, content): 126 self.response = response 127 self.content = content 128 HttpLib2Error.__init__(self, desc) 129 130 131 class RedirectMissingLocation(HttpLib2ErrorWithResponse): 132 pass 133 134 135 class RedirectLimit(HttpLib2ErrorWithResponse): 136 pass 137 138 139 class FailedToDecompressContent(HttpLib2ErrorWithResponse): 140 pass 141 142 143 class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): 144 pass 145 146 147 class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): 148 pass 149 150 151 class MalformedHeader(HttpLib2Error): 152 pass 153 154 155 class RelativeURIError(HttpLib2Error): 156 pass 157 158 159 class ServerNotFoundError(HttpLib2Error): 160 pass 161 162 163 class ProxiesUnavailableError(HttpLib2Error): 164 pass 165 166 167 class CertificateValidationUnsupported(HttpLib2Error): 168 pass 169 170 171 class SSLHandshakeError(HttpLib2Error): 172 pass 173 174 175 class NotSupportedOnThisPlatform(HttpLib2Error): 176 pass 177 178 179 class CertificateHostnameMismatch(SSLHandshakeError): 180 181 def __init__(self, desc, host, cert): 182 HttpLib2Error.__init__(self, desc) 183 self.host = host 184 self.cert = cert 185 186 187 DEFAULT_MAX_REDIRECTS = 5 188 try: 189 import ca_certs_locater 190 CA_CERTS = ca_certs_locater.get() 191 except ImportError: 192 CA_CERTS = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cacerts.txt') 193 194 HOP_BY_HOP = ['connection', 195 'keep-alive', 196 'proxy-authenticate', 197 'proxy-authorization', 198 'te', 199 'trailers', 200 'transfer-encoding', 201 'upgrade'] 202 203 def _get_end2end_headers(response): 204 hopbyhop = list(HOP_BY_HOP) 205 hopbyhop.extend([ x.strip() for x in response.get('connection', '').split(',') ]) 206 return [ header for header in response.keys() if header not in hopbyhop ] 207 208 209 URI = re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?') 210 211 def parse_uri(uri): 212 groups = URI.match(uri).groups() 213 return (groups[1], 214 groups[3], 215 groups[4], 216 groups[6], 217 groups[8]) 218 219 220 def urlnorm(uri): 221 scheme, authority, path, query, fragment = parse_uri(uri) 222 if not scheme or not authority: 223 raise RelativeURIError('Only absolute URIs are allowed. uri = %s' % uri) 224 authority = authority.lower() 225 scheme = scheme.lower() 226 if not path: 227 path = '/' 228 request_uri = query and '?'.join([path, query]) or path 229 scheme = scheme.lower() 230 defrag_uri = scheme + '://' + authority + request_uri 231 return (scheme, 232 authority, 233 request_uri, 234 defrag_uri) 235 236 237 re_url_scheme = re.compile('^\\w+://') 238 re_slash = re.compile('[?/:|]+') 239 240 def safename(filename): 241 try: 242 if re_url_scheme.match(filename): 243 if isinstance(filename, str): 244 filename = filename.decode('utf-8') 245 filename = filename.encode('idna') 246 else: 247 filename = filename.encode('idna') 248 except UnicodeError: 249 pass 250 251 if isinstance(filename, unicode): 252 filename = filename.encode('utf-8') 253 filemd5 = _md5(filename).hexdigest() 254 filename = re_url_scheme.sub('', filename) 255 filename = re_slash.sub(',', filename) 256 if len(filename) > 200: 257 filename = filename[:200] 258 return ','.join((filename, filemd5)) 259 260 261 NORMALIZE_SPACE = re.compile('(?:\\r\\n)?[ \\t]+') 262 263 def _normalize_headers(headers): 264 return dict([ (key.lower(), NORMALIZE_SPACE.sub(value, ' ').strip()) for key, value in headers.iteritems() ]) 265 266 267 def _parse_cache_control(headers): 268 retval = {} 269 if 'cache-control' in headers: 270 parts = headers['cache-control'].split(',') 271 parts_with_args = [ tuple([ x.strip().lower() for x in part.split('=', 1) ]) for part in parts if -1 != part.find('=') ] 272 parts_wo_args = [ (name.strip().lower(), 1) for name in parts if -1 == name.find('=') ] 273 retval = dict(parts_with_args + parts_wo_args) 274 return retval 275 276 277 USE_WWW_AUTH_STRICT_PARSING = 0 278 WWW_AUTH_STRICT = re.compile('^(?:\\s*(?:,\\s*)?([^\\0-\\x1f\\x7f-\\xff()<>@,;:\\\\\\"/[\\]?={} \\t]+)\\s*=\\s*\\"?((?<=\\")(?:[^\\0-\\x08\\x0A-\\x1f\\x7f-\\xff\\\\\\"]|\\\\[\\0-\\x7f])*?(?=\\")|(?<!\\")[^\\0-\\x1f\\x7f-\\xff()<>@,;:\\\\\\"/[\\]?={} \\t]+(?!\\"))\\"?)(.*)$') 279 WWW_AUTH_RELAXED = re.compile('^(?:\\s*(?:,\\s*)?([^ \\t\\r\\n=]+)\\s*=\\s*\\"?((?<=\\")(?:[^\\\\\\"]|\\\\.)*?(?=\\")|(?<!\\")[^ \\t\\r\\n,]+(?!\\"))\\"?)(.*)$') 280 UNQUOTE_PAIRS = re.compile('\\\\(.)') 281 282 def _parse_www_authenticate(headers, headername = 'www-authenticate'): 283 retval = {} 284 if headername in headers: 285 try: 286 authenticate = headers[headername].strip() 287 www_auth = USE_WWW_AUTH_STRICT_PARSING and WWW_AUTH_STRICT or WWW_AUTH_RELAXED 288 while authenticate: 289 if headername == 'authentication-info': 290 auth_scheme, the_rest = 'digest', authenticate 291 else: 292 auth_scheme, the_rest = authenticate.split(' ', 1) 293 match = www_auth.search(the_rest) 294 auth_params = {} 295 while match: 296 if match and len(match.groups()) == 3: 297 key, value, the_rest = match.groups() 298 auth_params[key.lower()] = UNQUOTE_PAIRS.sub('\\1', value) 299 match = www_auth.search(the_rest) 300 301 retval[auth_scheme.lower()] = auth_params 302 authenticate = the_rest.strip() 303 304 except ValueError: 305 raise MalformedHeader('WWW-Authenticate') 306 307 return retval 308 309 310 def _entry_disposition(response_headers, request_headers): 311 retval = 'STALE' 312 cc = _parse_cache_control(request_headers) 313 cc_response = _parse_cache_control(response_headers) 314 if 'pragma' in request_headers and request_headers['pragma'].lower().find('no-cache') != -1: 315 retval = 'TRANSPARENT' 316 if 'cache-control' not in request_headers: 317 request_headers['cache-control'] = 'no-cache' 318 elif 'no-cache' in cc: 319 retval = 'TRANSPARENT' 320 elif 'no-cache' in cc_response: 321 retval = 'STALE' 322 elif 'only-if-cached' in cc: 323 retval = 'FRESH' 324 elif 'date' in response_headers: 325 date = calendar.timegm(email.Utils.parsedate_tz(response_headers['date'])) 326 now = time.time() 327 current_age = max(0, now - date) 328 if 'max-age' in cc_response: 329 try: 330 freshness_lifetime = int(cc_response['max-age']) 331 except ValueError: 332 freshness_lifetime = 0 333 334 elif 'expires' in response_headers: 335 expires = email.Utils.parsedate_tz(response_headers['expires']) 336 if None == expires: 337 freshness_lifetime = 0 338 else: 339 freshness_lifetime = max(0, calendar.timegm(expires) - date) 340 else: 341 freshness_lifetime = 0 342 if 'max-age' in cc: 343 try: 344 freshness_lifetime = int(cc['max-age']) 345 except ValueError: 346 freshness_lifetime = 0 347 348 if 'min-fresh' in cc: 349 try: 350 min_fresh = int(cc['min-fresh']) 351 except ValueError: 352 min_fresh = 0 353 354 current_age += min_fresh 355 if freshness_lifetime > current_age: 356 retval = 'FRESH' 357 return retval 358 359 360 def _decompressContent(response, new_content): 361 content = new_content 362 try: 363 encoding = response.get('content-encoding', None) 364 if encoding in ('gzip', 'deflate'): 365 if encoding == 'gzip': 366 content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read() 367 if encoding == 'deflate': 368 content = zlib.decompress(content) 369 response['content-length'] = str(len(content)) 370 response['-content-encoding'] = response['content-encoding'] 371 del response['content-encoding'] 372 except IOError: 373 content = '' 374 raise FailedToDecompressContent(_('Content purported to be compressed with %s but failed to decompress.') % response.get('content-encoding'), response, content) 375 376 return content 377 378 379 def _updateCache(request_headers, response_headers, content, cache, cachekey): 380 if cachekey: 381 cc = _parse_cache_control(request_headers) 382 cc_response = _parse_cache_control(response_headers) 383 if 'no-store' in cc or 'no-store' in cc_response: 384 cache.delete(cachekey) 385 else: 386 info = email.Message.Message() 387 for key, value in response_headers.iteritems(): 388 if key not in ('status', 'content-encoding', 'transfer-encoding'): 389 info[key] = value 390 391 vary = response_headers.get('vary', None) 392 if vary: 393 vary_headers = vary.lower().replace(' ', '').split(',') 394 for header in vary_headers: 395 key = '-varied-%s' % header 396 try: 397 info[key] = request_headers[header] 398 except KeyError: 399 pass 400 401 status = response_headers.status 402 if status == 304: 403 status = 200 404 status_header = 'status: %d\r\n' % status 405 header_str = info.as_string() 406 header_str = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', header_str) 407 text = ''.join([status_header, header_str, content]) 408 cache.set(cachekey, text) 409 410 411 def _cnonce(): 412 dig = _md5('%s:%s' % (time.ctime(), [ '0123456789'[random.randrange(0, 9)] for i in range(20) ])).hexdigest() 413 return dig[:16] 414 415 416 def _wsse_username_token(cnonce, iso_now, password): 417 return base64.b64encode(_sha('%s%s%s' % (cnonce, iso_now, password)).digest()).strip() 418 419 420 class Authentication(object): 421 422 def __init__(self, credentials, host, request_uri, headers, response, content, http): 423 scheme, authority, path, query, fragment = parse_uri(request_uri) 424 self.path = path 425 self.host = host 426 self.credentials = credentials 427 self.http = http 428 429 def depth(self, request_uri): 430 scheme, authority, path, query, fragment = parse_uri(request_uri) 431 return request_uri[len(self.path):].count('/') 432 433 def inscope(self, host, request_uri): 434 scheme, authority, path, query, fragment = parse_uri(request_uri) 435 return host == self.host and path.startswith(self.path) 436 437 def request(self, method, request_uri, headers, content): 438 pass 439 440 def response(self, response, content): 441 return False 442 443 444 class BasicAuthentication(Authentication): 445 446 def __init__(self, credentials, host, request_uri, headers, response, content, http): 447 Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http) 448 449 def request(self, method, request_uri, headers, content): 450 headers['authorization'] = 'Basic ' + base64.b64encode('%s:%s' % self.credentials).strip() 451 452 453 class DigestAuthentication(Authentication): 454 455 def __init__(self, credentials, host, request_uri, headers, response, content, http): 456 Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http) 457 challenge = _parse_www_authenticate(response, 'www-authenticate') 458 self.challenge = challenge['digest'] 459 qop = self.challenge.get('qop', 'auth') 460 self.challenge['qop'] = 'auth' in [ x.strip() for x in qop.split() ] and 'auth' or None 461 if self.challenge['qop'] is None: 462 raise UnimplementedDigestAuthOptionError(_('Unsupported value for qop: %s.' % qop)) 463 self.challenge['algorithm'] = self.challenge.get('algorithm', 'MD5').upper() 464 if self.challenge['algorithm'] != 'MD5': 465 raise UnimplementedDigestAuthOptionError(_('Unsupported value for algorithm: %s.' % self.challenge['algorithm'])) 466 self.A1 = ''.join([self.credentials[0], 467 ':', 468 self.challenge['realm'], 469 ':', 470 self.credentials[1]]) 471 self.challenge['nc'] = 1 472 473 def request(self, method, request_uri, headers, content, cnonce = None): 474 H = lambda x: _md5(x).hexdigest() 475 KD = lambda s, d: H('%s:%s' % (s, d)) 476 A2 = ''.join([method, ':', request_uri]) 477 self.challenge['cnonce'] = cnonce or _cnonce() 478 request_digest = '"%s"' % KD(H(self.A1), '%s:%s:%s:%s:%s' % (self.challenge['nonce'], 479 '%08x' % self.challenge['nc'], 480 self.challenge['cnonce'], 481 self.challenge['qop'], 482 H(A2))) 483 headers['authorization'] = 'Digest username="%s", realm="%s", nonce="%s", uri="%s", algorithm=%s, response=%s, qop=%s, nc=%08x, cnonce="%s"' % (self.credentials[0], 484 self.challenge['realm'], 485 self.challenge['nonce'], 486 request_uri, 487 self.challenge['algorithm'], 488 request_digest, 489 self.challenge['qop'], 490 self.challenge['nc'], 491 self.challenge['cnonce']) 492 if self.challenge.get('opaque'): 493 headers['authorization'] += ', opaque="%s"' % self.challenge['opaque'] 494 self.challenge['nc'] += 1 495 496 def response(self, response, content): 497 if 'authentication-info' not in response: 498 challenge = _parse_www_authenticate(response, 'www-authenticate').get('digest', {}) 499 if 'true' == challenge.get('stale'): 500 self.challenge['nonce'] = challenge['nonce'] 501 self.challenge['nc'] = 1 502 return True 503 else: 504 updated_challenge = _parse_www_authenticate(response, 'authentication-info').get('digest', {}) 505 if 'nextnonce' in updated_challenge: 506 self.challenge['nonce'] = updated_challenge['nextnonce'] 507 self.challenge['nc'] = 1 508 return False 509 510 511 class HmacDigestAuthentication(Authentication): 512 __author__ = 'Thomas Broyer (t.broyer@ltgt.net)' 513 514 def __init__(self, credentials, host, request_uri, headers, response, content, http): 515 Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http) 516 challenge = _parse_www_authenticate(response, 'www-authenticate') 517 self.challenge = challenge['hmacdigest'] 518 self.challenge['reason'] = self.challenge.get('reason', 'unauthorized') 519 if self.challenge['reason'] not in ('unauthorized', 'integrity'): 520 self.challenge['reason'] = 'unauthorized' 521 self.challenge['salt'] = self.challenge.get('salt', '') 522 if not self.challenge.get('snonce'): 523 raise UnimplementedHmacDigestAuthOptionError(_("The challenge doesn't contain a server nonce, or this one is empty.")) 524 self.challenge['algorithm'] = self.challenge.get('algorithm', 'HMAC-SHA-1') 525 if self.challenge['algorithm'] not in ('HMAC-SHA-1', 'HMAC-MD5'): 526 raise UnimplementedHmacDigestAuthOptionError(_('Unsupported value for algorithm: %s.' % self.challenge['algorithm'])) 527 self.challenge['pw-algorithm'] = self.challenge.get('pw-algorithm', 'SHA-1') 528 if self.challenge['pw-algorithm'] not in ('SHA-1', 'MD5'): 529 raise UnimplementedHmacDigestAuthOptionError(_('Unsupported value for pw-algorithm: %s.' % self.challenge['pw-algorithm'])) 530 if self.challenge['algorithm'] == 'HMAC-MD5': 531 self.hashmod = _md5 532 else: 533 self.hashmod = _sha 534 if self.challenge['pw-algorithm'] == 'MD5': 535 self.pwhashmod = _md5 536 else: 537 self.pwhashmod = _sha 538 self.key = ''.join([self.credentials[0], 539 ':', 540 self.pwhashmod.new(''.join([self.credentials[1], self.challenge['salt']])).hexdigest().lower(), 541 ':', 542 self.challenge['realm']]) 543 self.key = self.pwhashmod.new(self.key).hexdigest().lower() 544 545 def request(self, method, request_uri, headers, content): 546 keys = _get_end2end_headers(headers) 547 keylist = ''.join([ '%s ' % k for k in keys ]) 548 headers_val = ''.join([ headers[k] for k in keys ]) 549 created = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) 550 cnonce = _cnonce() 551 request_digest = '%s:%s:%s:%s:%s' % (method, 552 request_uri, 553 cnonce, 554 self.challenge['snonce'], 555 headers_val) 556 request_digest = hmac.new(self.key, request_digest, self.hashmod).hexdigest().lower() 557 headers['authorization'] = 'HMACDigest username="%s", realm="%s", snonce="%s", cnonce="%s", uri="%s", created="%s", response="%s", headers="%s"' % (self.credentials[0], 558 self.challenge['realm'], 559 self.challenge['snonce'], 560 cnonce, 561 request_uri, 562 created, 563 request_digest, 564 keylist) 565 566 def response(self, response, content): 567 challenge = _parse_www_authenticate(response, 'www-authenticate').get('hmacdigest', {}) 568 if challenge.get('reason') in ('integrity', 'stale'): 569 return True 570 return False 571 572 573 class WsseAuthentication(Authentication): 574 575 def __init__(self, credentials, host, request_uri, headers, response, content, http): 576 Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http) 577 578 def request(self, method, request_uri, headers, content): 579 headers['authorization'] = 'WSSE profile="UsernameToken"' 580 iso_now = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) 581 cnonce = _cnonce() 582 password_digest = _wsse_username_token(cnonce, iso_now, self.credentials[1]) 583 headers['X-WSSE'] = 'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"' % (self.credentials[0], 584 password_digest, 585 cnonce, 586 iso_now) 587 588 589 class GoogleLoginAuthentication(Authentication): 590 591 def __init__(self, credentials, host, request_uri, headers, response, content, http): 592 from urllib import urlencode 593 Authentication.__init__(self, credentials, host, request_uri, headers, response, content, http) 594 challenge = _parse_www_authenticate(response, 'www-authenticate') 595 service = challenge['googlelogin'].get('service', 'xapi') 596 if service == 'xapi' and request_uri.find('calendar') > 0: 597 service = 'cl' 598 auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers['user-agent']) 599 resp, content = self.http.request('https://www.google.com/accounts/ClientLogin', method='POST', body=urlencode(auth), headers={'Content-Type': 'application/x-www-form-urlencoded'}) 600 lines = content.split('\n') 601 d = dict([ tuple(line.split('=', 1)) for line in lines if line ]) 602 if resp.status == 403: 603 self.Auth = '' 604 else: 605 self.Auth = d['Auth'] 606 607 def request(self, method, request_uri, headers, content): 608 headers['authorization'] = 'GoogleLogin Auth=' + self.Auth 609 610 611 AUTH_SCHEME_CLASSES = {'basic': BasicAuthentication, 612 'wsse': WsseAuthentication, 613 'digest': DigestAuthentication, 614 'hmacdigest': HmacDigestAuthentication, 615 'googlelogin': GoogleLoginAuthentication} 616 AUTH_SCHEME_ORDER = ['hmacdigest', 617 'googlelogin', 618 'digest', 619 'wsse', 620 'basic'] 621 622 class FileCache(object): 623 624 def __init__(self, cache, safe = safename): 625 self.cache = cache 626 self.safe = safe 627 if not os.path.exists(cache): 628 os.makedirs(self.cache) 629 630 def get(self, key): 631 retval = None 632 cacheFullPath = os.path.join(self.cache, self.safe(key)) 633 try: 634 f = file(cacheFullPath, 'rb') 635 retval = f.read() 636 f.close() 637 except IOError: 638 pass 639 640 return retval 641 642 def set(self, key, value): 643 cacheFullPath = os.path.join(self.cache, self.safe(key)) 644 f = file(cacheFullPath, 'wb') 645 f.write(value) 646 f.close() 647 648 def delete(self, key): 649 cacheFullPath = os.path.join(self.cache, self.safe(key)) 650 if os.path.exists(cacheFullPath): 651 os.remove(cacheFullPath) 652 653 654 class Credentials(object): 655 656 def __init__(self): 657 self.credentials = [] 658 659 def add(self, name, password, domain = ''): 660 self.credentials.append((domain.lower(), name, password)) 661 662 def clear(self): 663 self.credentials = [] 664 665 def iter(self, domain): 666 for cdomain, name, password in self.credentials: 667 if cdomain == '' or domain == cdomain: 668 yield (name, password) 669 670 671 class KeyCerts(Credentials): 672 pass 673 674 675 class AllHosts(object): 676 pass 677 678 679 class ProxyInfo(object): 680 bypass_hosts = () 681 682 def __init__(self, proxy_type, proxy_host, proxy_port, proxy_rdns = True, proxy_user = None, proxy_pass = None): 683 self.proxy_type = proxy_type 684 self.proxy_host = proxy_host 685 self.proxy_port = proxy_port 686 self.proxy_rdns = proxy_rdns 687 self.proxy_user = proxy_user 688 self.proxy_pass = proxy_pass 689 690 def astuple(self): 691 return (self.proxy_type, 692 self.proxy_host, 693 self.proxy_port, 694 self.proxy_rdns, 695 self.proxy_user, 696 self.proxy_pass) 697 698 def isgood(self): 699 return self.proxy_host != None and self.proxy_port != None 700 701 def applies_to(self, hostname): 702 return not self.bypass_host(hostname) 703 704 def bypass_host(self, hostname): 705 if self.bypass_hosts is AllHosts: 706 return True 707 bypass = False 708 for domain in self.bypass_hosts: 709 if hostname.endswith(domain): 710 bypass = True 711 712 return bypass 713 714 715 def proxy_info_from_environment(method = 'http'): 716 if method not in ('http', 'https'): 717 return 718 env_var = method + '_proxy' 719 url = os.environ.get(env_var, os.environ.get(env_var.upper())) 720 if not url: 721 return 722 pi = proxy_info_from_url(url, method) 723 no_proxy = os.environ.get('no_proxy', os.environ.get('NO_PROXY', '')) 724 bypass_hosts = [] 725 if no_proxy: 726 bypass_hosts = no_proxy.split(',') 727 if no_proxy == '*': 728 bypass_hosts = AllHosts 729 pi.bypass_hosts = bypass_hosts 730 return pi 731 732 733 def proxy_info_from_url(url, method = 'http'): 734 url = urlparse.urlparse(url) 735 username = None 736 password = None 737 port = None 738 if '@' in url[1]: 739 ident, host_port = url[1].split('@', 1) 740 if ':' in ident: 741 username, password = ident.split(':', 1) 742 else: 743 password = ident 744 else: 745 host_port = url[1] 746 if ':' in host_port: 747 host, port = host_port.split(':', 1) 748 else: 749 host = host_port 750 if port: 751 port = int(port) 752 else: 753 port = dict(https=443, http=80)[method] 754 proxy_type = 3 755 return ProxyInfo(proxy_type=proxy_type, proxy_host=host, proxy_port=port, proxy_user=username or None, proxy_pass=password or None) 756 757 758 class HTTPConnectionWithTimeout(httplib.HTTPConnection): 759 760 def __init__(self, host, port = None, strict = None, timeout = None, proxy_info = None): 761 httplib.HTTPConnection.__init__(self, host, port, strict) 762 self.timeout = timeout 763 self.proxy_info = proxy_info 764 765 def connect(self): 766 if self.proxy_info and socks is None: 767 raise ProxiesUnavailableError('Proxy support missing but proxy use was requested!') 768 msg = 'getaddrinfo returns an empty list' 769 if self.proxy_info and self.proxy_info.isgood(): 770 use_proxy = True 771 proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple() 772 host = proxy_host 773 port = proxy_port 774 else: 775 use_proxy = False 776 host = self.host 777 port = self.port 778 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): 779 af, socktype, proto, canonname, sa = res 780 try: 781 if use_proxy: 782 self.sock = socks.socksocket(af, socktype, proto) 783 self.sock.setproxy(proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass) 784 else: 785 self.sock = socket.socket(af, socktype, proto) 786 self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 787 if has_timeout(self.timeout): 788 self.sock.settimeout(self.timeout) 789 if self.debuglevel > 0: 790 print('connect: (%s, %s) ************' % (self.host, self.port)) 791 if use_proxy: 792 print('proxy: %s ************' % str((proxy_host, 793 proxy_port, 794 proxy_rdns, 795 proxy_user, 796 proxy_pass))) 797 self.sock.connect((self.host, self.port) + sa[2:]) 798 except socket.error as msg: 799 if self.debuglevel > 0: 800 print('connect fail: (%s, %s)' % (self.host, self.port)) 801 if use_proxy: 802 print('proxy: %s' % str((proxy_host, 803 proxy_port, 804 proxy_rdns, 805 proxy_user, 806 proxy_pass))) 807 if self.sock: 808 self.sock.close() 809 self.sock = None 810 continue 811 812 break 813 814 if not self.sock: 815 raise socket.error, msg 816 817 818 class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): 819 820 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): 821 httplib.HTTPSConnection.__init__(self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict) 822 self.timeout = timeout 823 self.proxy_info = proxy_info 824 if ca_certs is None: 825 ca_certs = CA_CERTS 826 self.ca_certs = ca_certs 827 self.disable_ssl_certificate_validation = disable_ssl_certificate_validation 828 829 def _GetValidHostsForCert(self, cert): 830 if 'subjectAltName' in cert: 831 return [ x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns' ] 832 else: 833 return [ x[0][1] for x in cert['subject'] if x[0][0].lower() == 'commonname' ] 834 835 def _ValidateCertificateHostname(self, cert, hostname): 836 hosts = self._GetValidHostsForCert(cert) 837 for host in hosts: 838 host_re = host.replace('.', '\\.').replace('*', '[^.]*') 839 if re.search('^%s$' % (host_re,), hostname, re.I): 840 return True 841 842 return False 843 844 def connect(self): 845 msg = 'getaddrinfo returns an empty list' 846 if self.proxy_info and self.proxy_info.isgood(): 847 use_proxy = True 848 proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple() 849 host = proxy_host 850 port = proxy_port 851 else: 852 use_proxy = False 853 host = self.host 854 port = self.port 855 address_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) 856 for family, socktype, proto, canonname, sockaddr in address_info: 857 try: 858 if use_proxy: 859 sock = socks.socksocket(family, socktype, proto) 860 sock.setproxy(proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass) 861 else: 862 sock = socket.socket(family, socktype, proto) 863 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 864 if has_timeout(self.timeout): 865 sock.settimeout(self.timeout) 866 sock.connect((self.host, self.port)) 867 self.sock = _ssl_wrap_socket(sock, self.key_file, self.cert_file, self.disable_ssl_certificate_validation, self.ca_certs) 868 if self.debuglevel > 0: 869 print('connect: (%s, %s)' % (self.host, self.port)) 870 if use_proxy: 871 print('proxy: %s' % str((proxy_host, 872 proxy_port, 873 proxy_rdns, 874 proxy_user, 875 proxy_pass))) 876 if not self.disable_ssl_certificate_validation: 877 cert = self.sock.getpeercert() 878 hostname = self.host.split(':', 0)[0] 879 if not self._ValidateCertificateHostname(cert, hostname): 880 raise CertificateHostnameMismatch('Server presented certificate that does not match host %s: %s' % (hostname, cert), hostname, cert) 881 except ssl_SSLError as e: 882 if sock: 883 sock.close() 884 if self.sock: 885 self.sock.close() 886 self.sock = None 887 if e.errno == ssl.SSL_ERROR_SSL: 888 raise SSLHandshakeError(e) 889 else: 890 raise 891 except (socket.timeout, socket.gaierror): 892 raise 893 except socket.error as msg: 894 if self.debuglevel > 0: 895 print('connect fail: (%s, %s)' % (self.host, self.port)) 896 if use_proxy: 897 print('proxy: %s' % str((proxy_host, 898 proxy_port, 899 proxy_rdns, 900 proxy_user, 901 proxy_pass))) 902 if self.sock: 903 self.sock.close() 904 self.sock = None 905 continue 906 907 break 908 909 if not self.sock: 910 raise socket.error, msg 911 912 913 SCHEME_TO_CONNECTION = {'http': HTTPConnectionWithTimeout, 914 'https': HTTPSConnectionWithTimeout} 915 try: 916 try: 917 from google.appengine.api import apiproxy_stub_map 918 if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None: 919 raise ImportError 920 from google.appengine.api.urlfetch import fetch 921 from google.appengine.api.urlfetch import InvalidURLError 922 except (ImportError, AttributeError): 923 from google3.apphosting.api import apiproxy_stub_map 924 if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None: 925 raise ImportError 926 from google3.apphosting.api.urlfetch import fetch 927 from google3.apphosting.api.urlfetch import InvalidURLError 928 929 def _new_fixed_fetch(validate_certificate): 930 931 def fixed_fetch(url, payload = None, method = 'GET', headers = {}, allow_truncated = False, follow_redirects = True, deadline = None): 932 if deadline is None: 933 deadline = socket.getdefaulttimeout() or 5 934 return fetch(url, payload=payload, method=method, headers=headers, allow_truncated=allow_truncated, follow_redirects=follow_redirects, deadline=deadline, validate_certificate=validate_certificate) 935 936 return fixed_fetch 937 938 939 class AppEngineHttpConnection(httplib.HTTPConnection): 940 941 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): 942 httplib.HTTPConnection.__init__(self, host, port=port, strict=strict, timeout=timeout) 943 944 945 class AppEngineHttpsConnection(httplib.HTTPSConnection): 946 947 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): 948 httplib.HTTPSConnection.__init__(self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict, timeout=timeout) 949 self._fetch = _new_fixed_fetch(not disable_ssl_certificate_validation) 950 951 952 SCHEME_TO_CONNECTION = {'http': AppEngineHttpConnection, 953 'https': AppEngineHttpsConnection} 954 except (ImportError, AttributeError): 955 pass 956 957 class Http(object): 958 959 def __init__(self, cache = None, timeout = None, proxy_info = proxy_info_from_environment, ca_certs = None, disable_ssl_certificate_validation = False): 960 self.proxy_info = proxy_info 961 self.ca_certs = ca_certs 962 self.disable_ssl_certificate_validation = disable_ssl_certificate_validation 963 self.connections = {} 964 if cache and isinstance(cache, basestring): 965 self.cache = FileCache(cache) 966 else: 967 self.cache = cache 968 self.credentials = Credentials() 969 self.certificates = KeyCerts() 970 self.authorizations = [] 971 self.follow_redirects = True 972 self.optimistic_concurrency_methods = ['PUT', 'PATCH'] 973 self.follow_all_redirects = False 974 self.ignore_etag = False 975 self.force_exception_to_status_code = False 976 self.timeout = timeout 977 self.forward_authorization_headers = False 978 979 def __getstate__(self): 980 state_dict = copy.copy(self.__dict__) 981 if 'request' in state_dict: 982 del state_dict['request'] 983 if 'connections' in state_dict: 984 del state_dict['connections'] 985 return state_dict 986 987 def __setstate__(self, state): 988 self.__dict__.update(state) 989 self.connections = {} 990 991 def _auth_from_challenge(self, host, request_uri, headers, response, content): 992 challenges = _parse_www_authenticate(response, 'www-authenticate') 993 for cred in self.credentials.iter(host): 994 for scheme in AUTH_SCHEME_ORDER: 995 if scheme in challenges: 996 yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self) 997 998 def add_credentials(self, name, password, domain = ''): 999 self.credentials.add(name, password, domain) 1000 1001 def add_certificate(self, key, cert, domain): 1002 self.certificates.add(key, cert, domain) 1003 1004 def clear_credentials(self): 1005 self.credentials.clear() 1006 self.authorizations = [] 1007 1008 def _conn_request(self, conn, request_uri, method, body, headers): 1009 i = 0 1010 seen_bad_status_line = False 1011 while i < RETRIES: 1012 i += 1 1013 try: 1014 if hasattr(conn, 'sock') and conn.sock is None: 1015 conn.connect() 1016 conn.request(method, request_uri, body, headers) 1017 except socket.timeout: 1018 raise 1019 except socket.gaierror: 1020 conn.close() 1021 raise ServerNotFoundError('Unable to find the server at %s' % conn.host) 1022 except ssl_SSLError: 1023 conn.close() 1024 raise 1025 except socket.error as e: 1026 err = 0 1027 if hasattr(e, 'args'): 1028 err = getattr(e, 'args')[0] 1029 else: 1030 err = e.errno 1031 if err in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES: 1032 continue 1033 raise 1034 except httplib.HTTPException: 1035 if hasattr(conn, 'sock') and conn.sock is None: 1036 if i < RETRIES - 1: 1037 conn.close() 1038 conn.connect() 1039 continue 1040 else: 1041 conn.close() 1042 raise 1043 if i < RETRIES - 1: 1044 conn.close() 1045 conn.connect() 1046 continue 1047 1048 try: 1049 response = conn.getresponse() 1050 except httplib.BadStatusLine: 1051 if not seen_bad_status_line and i == 1: 1052 i = 0 1053 seen_bad_status_line = True 1054 conn.close() 1055 conn.connect() 1056 continue 1057 else: 1058 conn.close() 1059 raise 1060 except (socket.error, httplib.HTTPException): 1061 if i < RETRIES - 1: 1062 conn.close() 1063 conn.connect() 1064 continue 1065 else: 1066 conn.close() 1067 raise 1068 else: 1069 content = '' 1070 if method == 'HEAD': 1071 conn.close() 1072 else: 1073 content = response.read() 1074 response = Response(response) 1075 if method != 'HEAD': 1076 content = _decompressContent(response, content) 1077 1078 break 1079 1080 return (response, content) 1081 1082 def _request(self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey): 1083 auths = [ (auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri) ] 1084 auth = auths and sorted(auths)[0][1] or None 1085 if auth: 1086 auth.request(method, request_uri, headers, body) 1087 response, content = self._conn_request(conn, request_uri, method, body, headers) 1088 if auth: 1089 if auth.response(response, body): 1090 auth.request(method, request_uri, headers, body) 1091 response, content = self._conn_request(conn, request_uri, method, body, headers) 1092 response._stale_digest = 1 1093 if response.status == 401: 1094 for authorization in self._auth_from_challenge(host, request_uri, headers, response, content): 1095 authorization.request(method, request_uri, headers, body) 1096 response, content = self._conn_request(conn, request_uri, method, body, headers) 1097 if response.status != 401: 1098 self.authorizations.append(authorization) 1099 authorization.response(response, body) 1100 break 1101 1102 if self.follow_all_redirects or method in ('GET', 'HEAD') or response.status == 303: 1103 if self.follow_redirects and response.status in (300, 301, 302, 303, 307): 1104 if redirections: 1105 if 'location' not in response and response.status != 300: 1106 raise RedirectMissingLocation(_('Redirected but the response is missing a Location: header.'), response, content) 1107 if 'location' in response: 1108 location = response['location'] 1109 scheme, authority, path, query, fragment = parse_uri(location) 1110 if authority == None: 1111 response['location'] = urlparse.urljoin(absolute_uri, location) 1112 if response.status == 301 and method in ('GET', 'HEAD'): 1113 response['-x-permanent-redirect-url'] = response['location'] 1114 if 'content-location' not in response: 1115 response['content-location'] = absolute_uri 1116 _updateCache(headers, response, content, self.cache, cachekey) 1117 if 'if-none-match' in headers: 1118 del headers['if-none-match'] 1119 if 'if-modified-since' in headers: 1120 del headers['if-modified-since'] 1121 if 'authorization' in headers and not self.forward_authorization_headers: 1122 del headers['authorization'] 1123 if 'location' in response: 1124 location = response['location'] 1125 old_response = copy.deepcopy(response) 1126 if 'content-location' not in old_response: 1127 old_response['content-location'] = absolute_uri 1128 redirect_method = method 1129 if response.status in (302, 303): 1130 redirect_method = 'GET' 1131 body = None 1132 response, content = self.request(location, method=redirect_method, body=body, headers=headers, redirections=redirections - 1) 1133 response.previous = old_response 1134 else: 1135 raise RedirectLimit('Redirected more times than rediection_limit allows.', response, content) 1136 elif response.status in (200, 203) and method in ('GET', 'HEAD'): 1137 if 'content-location' not in response: 1138 response['content-location'] = absolute_uri 1139 _updateCache(headers, response, content, self.cache, cachekey) 1140 return (response, content) 1141 1142 def _normalize_headers(self, headers): 1143 return _normalize_headers(headers) 1144 1145 def request(self, uri, method = 'GET', body = None, headers = None, redirections = DEFAULT_MAX_REDIRECTS, connection_type = None): 1146 try: 1147 if headers is None: 1148 headers = {} 1149 else: 1150 headers = self._normalize_headers(headers) 1151 if 'user-agent' not in headers: 1152 headers['user-agent'] = 'Python-httplib2/%s (gzip)' % __version__ 1153 uri = iri2uri(uri) 1154 scheme, authority, request_uri, defrag_uri = urlnorm(uri) 1155 domain_port = authority.split(':')[0:2] 1156 if len(domain_port) == 2 and domain_port[1] == '443' and scheme == 'http': 1157 scheme = 'https' 1158 authority = domain_port[0] 1159 proxy_info = self._get_proxy_info(scheme, authority) 1160 conn_key = scheme + ':' + authority 1161 if conn_key in self.connections: 1162 conn = self.connections[conn_key] 1163 else: 1164 if not connection_type: 1165 connection_type = SCHEME_TO_CONNECTION[scheme] 1166 certs = list(self.certificates.iter(authority)) 1167 if scheme == 'https': 1168 if certs: 1169 conn = self.connections[conn_key] = connection_type(authority, key_file=certs[0][0], cert_file=certs[0][1], timeout=self.timeout, proxy_info=proxy_info, ca_certs=self.ca_certs, disable_ssl_certificate_validation=self.disable_ssl_certificate_validation) 1170 else: 1171 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) 1172 else: 1173 conn = self.connections[conn_key] = connection_type(authority, timeout=self.timeout, proxy_info=proxy_info) 1174 conn.set_debuglevel(debuglevel) 1175 if 'range' not in headers and 'accept-encoding' not in headers: 1176 headers['accept-encoding'] = 'gzip, deflate' 1177 info = email.Message.Message() 1178 cached_value = None 1179 if self.cache: 1180 cachekey = defrag_uri.encode('utf-8') 1181 cached_value = self.cache.get(cachekey) 1182 if cached_value: 1183 try: 1184 info, content = cached_value.split('\r\n\r\n', 1) 1185 feedparser = email.FeedParser.FeedParser() 1186 feedparser.feed(info) 1187 info = feedparser.close() 1188 feedparser._parse = None 1189 except (IndexError, ValueError): 1190 self.cache.delete(cachekey) 1191 cachekey = None 1192 cached_value = None 1193 1194 else: 1195 cachekey = None 1196 if method in self.optimistic_concurrency_methods and self.cache and 'etag' in info and not self.ignore_etag and 'if-match' not in headers: 1197 headers['if-match'] = info['etag'] 1198 if method not in ('GET', 'HEAD') and self.cache and cachekey: 1199 self.cache.delete(cachekey) 1200 if method in ('GET', 'HEAD') and 'vary' in info: 1201 vary = info['vary'] 1202 vary_headers = vary.lower().replace(' ', '').split(',') 1203 for header in vary_headers: 1204 key = '-varied-%s' % header 1205 value = info[key] 1206 if headers.get(header, None) != value: 1207 cached_value = None 1208 break 1209 1210 if cached_value and method in ('GET', 'HEAD') and self.cache and 'range' not in headers: 1211 if '-x-permanent-redirect-url' in info: 1212 if redirections <= 0: 1213 raise RedirectLimit('Redirected more times than rediection_limit allows.', {}, '') 1214 response, new_content = self.request(info['-x-permanent-redirect-url'], method='GET', headers=headers, redirections=redirections - 1) 1215 response.previous = Response(info) 1216 response.previous.fromcache = True 1217 else: 1218 entry_disposition = _entry_disposition(info, headers) 1219 if entry_disposition == 'FRESH': 1220 if not cached_value: 1221 info['status'] = '504' 1222 content = '' 1223 response = Response(info) 1224 if cached_value: 1225 response.fromcache = True 1226 return (response, content) 1227 if entry_disposition == 'STALE': 1228 if 'etag' in info and not self.ignore_etag and 'if-none-match' not in headers: 1229 headers['if-none-match'] = info['etag'] 1230 if 'last-modified' in info and 'last-modified' not in headers: 1231 headers['if-modified-since'] = info['last-modified'] 1232 elif entry_disposition == 'TRANSPARENT': 1233 pass 1234 response, new_content = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey) 1235 if response.status == 304 and method == 'GET': 1236 for key in _get_end2end_headers(response): 1237 info[key] = response[key] 1238 1239 merged_response = Response(info) 1240 if hasattr(response, '_stale_digest'): 1241 merged_response._stale_digest = response._stale_digest 1242 _updateCache(headers, merged_response, content, self.cache, cachekey) 1243 response = merged_response 1244 response.status = 200 1245 response.fromcache = True 1246 elif response.status == 200: 1247 content = new_content 1248 else: 1249 self.cache.delete(cachekey) 1250 content = new_content 1251 else: 1252 cc = _parse_cache_control(headers) 1253 if 'only-if-cached' in cc: 1254 info['status'] = '504' 1255 response = Response(info) 1256 content = '' 1257 else: 1258 response, content = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey) 1259 except Exception as e: 1260 if self.force_exception_to_status_code: 1261 if isinstance(e, HttpLib2ErrorWithResponse): 1262 response = e.response 1263 content = e.content 1264 response.status = 500 1265 response.reason = str(e) 1266 elif isinstance(e, socket.timeout): 1267 content = 'Request Timeout' 1268 response = Response({'content-type': 'text/plain', 1269 'status': '408', 1270 'content-length': len(content)}) 1271 response.reason = 'Request Timeout' 1272 else: 1273 content = str(e) 1274 response = Response({'content-type': 'text/plain', 1275 'status': '400', 1276 'content-length': len(content)}) 1277 response.reason = 'Bad Request' 1278 else: 1279 raise 1280 1281 return (response, content) 1282 1283 def _get_proxy_info(self, scheme, authority): 1284 hostname, port = urllib.splitport(authority) 1285 proxy_info = self.proxy_info 1286 if callable(proxy_info): 1287 proxy_info = proxy_info(scheme) 1288 if hasattr(proxy_info, 'applies_to') and not proxy_info.applies_to(hostname): 1289 proxy_info = None 1290 return proxy_info 1291 1292 1293 class Response(dict): 1294 fromcache = False 1295 version = 11 1296 status = 200 1297 reason = 'Ok' 1298 previous = None 1299 1300 def __init__(self, info): 1301 if isinstance(info, httplib.HTTPResponse): 1302 for key, value in info.getheaders(): 1303 self[key.lower()] = value 1304 1305 self.status = info.status 1306 self['status'] = str(self.status) 1307 self.reason = info.reason 1308 self.version = info.version 1309 elif isinstance(info, email.Message.Message): 1310 for key, value in info.items(): 1311 self[key.lower()] = value 1312 1313 self.status = int(self['status']) 1314 else: 1315 for key, value in info.iteritems(): 1316 self[key.lower()] = value 1317 1318 self.status = int(self.get('status', self.status)) 1319 self.reason = self.get('reason', self.reason) 1320 1321 def __getattr__(self, name): 1322 if name == 'dict': 1323 return self 1324 raise AttributeError, name