##// END OF EJS Templates
url: refactor BetterHTTPS.connect
Mads Kiilerich -
r13421:bd8bfa85 default
parent child Browse files
Show More
@@ -1,767 +1,765 b''
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 11 import __builtin__
12 12 from i18n import _
13 13 import keepalive, util
14 14
15 15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
16 16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
17 17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
18 18 if (scheme and
19 19 result.startswith(scheme + ':') and
20 20 not result.startswith(scheme + '://') and
21 21 url.startswith(scheme + '://')
22 22 ):
23 23 result = scheme + '://' + result[len(scheme + ':'):]
24 24 return result
25 25
26 26 def hidepassword(url):
27 27 '''hide user credential in a url string'''
28 28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
29 29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
30 30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
31 31
32 32 def removeauth(url):
33 33 '''remove all authentication information from a url string'''
34 34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
35 35 netloc = netloc[netloc.find('@')+1:]
36 36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
37 37
38 38 def netlocsplit(netloc):
39 39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
40 40
41 41 a = netloc.find('@')
42 42 if a == -1:
43 43 user, passwd = None, None
44 44 else:
45 45 userpass, netloc = netloc[:a], netloc[a + 1:]
46 46 c = userpass.find(':')
47 47 if c == -1:
48 48 user, passwd = urllib.unquote(userpass), None
49 49 else:
50 50 user = urllib.unquote(userpass[:c])
51 51 passwd = urllib.unquote(userpass[c + 1:])
52 52 c = netloc.find(':')
53 53 if c == -1:
54 54 host, port = netloc, None
55 55 else:
56 56 host, port = netloc[:c], netloc[c + 1:]
57 57 return host, port, user, passwd
58 58
59 59 def netlocunsplit(host, port, user=None, passwd=None):
60 60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
61 61 if port:
62 62 hostport = host + ':' + port
63 63 else:
64 64 hostport = host
65 65 if user:
66 66 quote = lambda s: urllib.quote(s, safe='')
67 67 if passwd:
68 68 userpass = quote(user) + ':' + quote(passwd)
69 69 else:
70 70 userpass = quote(user)
71 71 return userpass + '@' + hostport
72 72 return hostport
73 73
74 74 def readauthforuri(ui, uri):
75 75 # Read configuration
76 76 config = dict()
77 77 for key, val in ui.configitems('auth'):
78 78 if '.' not in key:
79 79 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
80 80 continue
81 81 group, setting = key.rsplit('.', 1)
82 82 gdict = config.setdefault(group, dict())
83 83 if setting in ('username', 'cert', 'key'):
84 84 val = util.expandpath(val)
85 85 gdict[setting] = val
86 86
87 87 # Find the best match
88 88 scheme, hostpath = uri.split('://', 1)
89 89 bestlen = 0
90 90 bestauth = None
91 91 for group, auth in config.iteritems():
92 92 prefix = auth.get('prefix')
93 93 if not prefix:
94 94 continue
95 95 p = prefix.split('://', 1)
96 96 if len(p) > 1:
97 97 schemes, prefix = [p[0]], p[1]
98 98 else:
99 99 schemes = (auth.get('schemes') or 'https').split()
100 100 if (prefix == '*' or hostpath.startswith(prefix)) and \
101 101 len(prefix) > bestlen and scheme in schemes:
102 102 bestlen = len(prefix)
103 103 bestauth = group, auth
104 104 return bestauth
105 105
106 106 _safe = ('abcdefghijklmnopqrstuvwxyz'
107 107 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
108 108 '0123456789' '_.-/')
109 109 _safeset = None
110 110 _hex = None
111 111 def quotepath(path):
112 112 '''quote the path part of a URL
113 113
114 114 This is similar to urllib.quote, but it also tries to avoid
115 115 quoting things twice (inspired by wget):
116 116
117 117 >>> quotepath('abc def')
118 118 'abc%20def'
119 119 >>> quotepath('abc%20def')
120 120 'abc%20def'
121 121 >>> quotepath('abc%20 def')
122 122 'abc%20%20def'
123 123 >>> quotepath('abc def%20')
124 124 'abc%20def%20'
125 125 >>> quotepath('abc def%2')
126 126 'abc%20def%252'
127 127 >>> quotepath('abc def%')
128 128 'abc%20def%25'
129 129 '''
130 130 global _safeset, _hex
131 131 if _safeset is None:
132 132 _safeset = set(_safe)
133 133 _hex = set('abcdefABCDEF0123456789')
134 134 l = list(path)
135 135 for i in xrange(len(l)):
136 136 c = l[i]
137 137 if (c == '%' and i + 2 < len(l) and
138 138 l[i + 1] in _hex and l[i + 2] in _hex):
139 139 pass
140 140 elif c not in _safeset:
141 141 l[i] = '%%%02X' % ord(c)
142 142 return ''.join(l)
143 143
144 144 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
145 145 def __init__(self, ui):
146 146 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
147 147 self.ui = ui
148 148
149 149 def find_user_password(self, realm, authuri):
150 150 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
151 151 self, realm, authuri)
152 152 user, passwd = authinfo
153 153 if user and passwd:
154 154 self._writedebug(user, passwd)
155 155 return (user, passwd)
156 156
157 157 if not user:
158 158 res = readauthforuri(self.ui, authuri)
159 159 if res:
160 160 group, auth = res
161 161 user, passwd = auth.get('username'), auth.get('password')
162 162 self.ui.debug("using auth.%s.* for authentication\n" % group)
163 163 if not user or not passwd:
164 164 if not self.ui.interactive():
165 165 raise util.Abort(_('http authorization required'))
166 166
167 167 self.ui.write(_("http authorization required\n"))
168 168 self.ui.write(_("realm: %s\n") % realm)
169 169 if user:
170 170 self.ui.write(_("user: %s\n") % user)
171 171 else:
172 172 user = self.ui.prompt(_("user:"), default=None)
173 173
174 174 if not passwd:
175 175 passwd = self.ui.getpass()
176 176
177 177 self.add_password(realm, authuri, user, passwd)
178 178 self._writedebug(user, passwd)
179 179 return (user, passwd)
180 180
181 181 def _writedebug(self, user, passwd):
182 182 msg = _('http auth: user %s, password %s\n')
183 183 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
184 184
185 185 class proxyhandler(urllib2.ProxyHandler):
186 186 def __init__(self, ui):
187 187 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
188 188 # XXX proxyauthinfo = None
189 189
190 190 if proxyurl:
191 191 # proxy can be proper url or host[:port]
192 192 if not (proxyurl.startswith('http:') or
193 193 proxyurl.startswith('https:')):
194 194 proxyurl = 'http://' + proxyurl + '/'
195 195 snpqf = urlparse.urlsplit(proxyurl)
196 196 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
197 197 hpup = netlocsplit(proxynetloc)
198 198
199 199 proxyhost, proxyport, proxyuser, proxypasswd = hpup
200 200 if not proxyuser:
201 201 proxyuser = ui.config("http_proxy", "user")
202 202 proxypasswd = ui.config("http_proxy", "passwd")
203 203
204 204 # see if we should use a proxy for this url
205 205 no_list = ["localhost", "127.0.0.1"]
206 206 no_list.extend([p.lower() for
207 207 p in ui.configlist("http_proxy", "no")])
208 208 no_list.extend([p.strip().lower() for
209 209 p in os.getenv("no_proxy", '').split(',')
210 210 if p.strip()])
211 211 # "http_proxy.always" config is for running tests on localhost
212 212 if ui.configbool("http_proxy", "always"):
213 213 self.no_list = []
214 214 else:
215 215 self.no_list = no_list
216 216
217 217 proxyurl = urlparse.urlunsplit((
218 218 proxyscheme, netlocunsplit(proxyhost, proxyport,
219 219 proxyuser, proxypasswd or ''),
220 220 proxypath, proxyquery, proxyfrag))
221 221 proxies = {'http': proxyurl, 'https': proxyurl}
222 222 ui.debug('proxying through http://%s:%s\n' %
223 223 (proxyhost, proxyport))
224 224 else:
225 225 proxies = {}
226 226
227 227 # urllib2 takes proxy values from the environment and those
228 228 # will take precedence if found, so drop them
229 229 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
230 230 try:
231 231 if env in os.environ:
232 232 del os.environ[env]
233 233 except OSError:
234 234 pass
235 235
236 236 urllib2.ProxyHandler.__init__(self, proxies)
237 237 self.ui = ui
238 238
239 239 def proxy_open(self, req, proxy, type_):
240 240 host = req.get_host().split(':')[0]
241 241 if host in self.no_list:
242 242 return None
243 243
244 244 # work around a bug in Python < 2.4.2
245 245 # (it leaves a "\n" at the end of Proxy-authorization headers)
246 246 baseclass = req.__class__
247 247 class _request(baseclass):
248 248 def add_header(self, key, val):
249 249 if key.lower() == 'proxy-authorization':
250 250 val = val.strip()
251 251 return baseclass.add_header(self, key, val)
252 252 req.__class__ = _request
253 253
254 254 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
255 255
256 256 class httpsendfile(object):
257 257 """This is a wrapper around the objects returned by python's "open".
258 258
259 259 Its purpose is to send file-like objects via HTTP and, to do so, it
260 260 defines a __len__ attribute to feed the Content-Length header.
261 261 """
262 262
263 263 def __init__(self, ui, *args, **kwargs):
264 264 # We can't just "self._data = open(*args, **kwargs)" here because there
265 265 # is an "open" function defined in this module that shadows the global
266 266 # one
267 267 self.ui = ui
268 268 self._data = __builtin__.open(*args, **kwargs)
269 269 self.seek = self._data.seek
270 270 self.close = self._data.close
271 271 self.write = self._data.write
272 272 self._len = os.fstat(self._data.fileno()).st_size
273 273 self._pos = 0
274 274 self._total = len(self) / 1024 * 2
275 275
276 276 def read(self, *args, **kwargs):
277 277 try:
278 278 ret = self._data.read(*args, **kwargs)
279 279 except EOFError:
280 280 self.ui.progress(_('sending'), None)
281 281 self._pos += len(ret)
282 282 # We pass double the max for total because we currently have
283 283 # to send the bundle twice in the case of a server that
284 284 # requires authentication. Since we can't know until we try
285 285 # once whether authentication will be required, just lie to
286 286 # the user and maybe the push succeeds suddenly at 50%.
287 287 self.ui.progress(_('sending'), self._pos / 1024,
288 288 unit=_('kb'), total=self._total)
289 289 return ret
290 290
291 291 def __len__(self):
292 292 return self._len
293 293
294 294 def _gen_sendfile(orgsend):
295 295 def _sendfile(self, data):
296 296 # send a file
297 297 if isinstance(data, httpsendfile):
298 298 # if auth required, some data sent twice, so rewind here
299 299 data.seek(0)
300 300 for chunk in util.filechunkiter(data):
301 301 orgsend(self, chunk)
302 302 else:
303 303 orgsend(self, data)
304 304 return _sendfile
305 305
306 306 has_https = hasattr(urllib2, 'HTTPSHandler')
307 307 if has_https:
308 308 try:
309 309 # avoid using deprecated/broken FakeSocket in python 2.6
310 310 import ssl
311 311 _ssl_wrap_socket = ssl.wrap_socket
312 312 CERT_REQUIRED = ssl.CERT_REQUIRED
313 313 except ImportError:
314 314 CERT_REQUIRED = 2
315 315
316 316 def _ssl_wrap_socket(sock, key_file, cert_file,
317 317 cert_reqs=CERT_REQUIRED, ca_certs=None):
318 318 if ca_certs:
319 319 raise util.Abort(_(
320 320 'certificate checking requires Python 2.6'))
321 321
322 322 ssl = socket.ssl(sock, key_file, cert_file)
323 323 return httplib.FakeSocket(sock, ssl)
324 324
325 325 try:
326 326 _create_connection = socket.create_connection
327 327 except AttributeError:
328 328 _GLOBAL_DEFAULT_TIMEOUT = object()
329 329
330 330 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
331 331 source_address=None):
332 332 # lifted from Python 2.6
333 333
334 334 msg = "getaddrinfo returns an empty list"
335 335 host, port = address
336 336 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
337 337 af, socktype, proto, canonname, sa = res
338 338 sock = None
339 339 try:
340 340 sock = socket.socket(af, socktype, proto)
341 341 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
342 342 sock.settimeout(timeout)
343 343 if source_address:
344 344 sock.bind(source_address)
345 345 sock.connect(sa)
346 346 return sock
347 347
348 348 except socket.error, msg:
349 349 if sock is not None:
350 350 sock.close()
351 351
352 352 raise socket.error, msg
353 353
354 354 class httpconnection(keepalive.HTTPConnection):
355 355 # must be able to send big bundle as stream.
356 356 send = _gen_sendfile(keepalive.HTTPConnection.send)
357 357
358 358 def connect(self):
359 359 if has_https and self.realhostport: # use CONNECT proxy
360 360 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
361 361 self.sock.connect((self.host, self.port))
362 362 if _generic_proxytunnel(self):
363 363 # we do not support client x509 certificates
364 364 self.sock = _ssl_wrap_socket(self.sock, None, None)
365 365 else:
366 366 keepalive.HTTPConnection.connect(self)
367 367
368 368 def getresponse(self):
369 369 proxyres = getattr(self, 'proxyres', None)
370 370 if proxyres:
371 371 if proxyres.will_close:
372 372 self.close()
373 373 self.proxyres = None
374 374 return proxyres
375 375 return keepalive.HTTPConnection.getresponse(self)
376 376
377 377 # general transaction handler to support different ways to handle
378 378 # HTTPS proxying before and after Python 2.6.3.
379 379 def _generic_start_transaction(handler, h, req):
380 380 if hasattr(req, '_tunnel_host') and req._tunnel_host:
381 381 tunnel_host = req._tunnel_host
382 382 if tunnel_host[:7] not in ['http://', 'https:/']:
383 383 tunnel_host = 'https://' + tunnel_host
384 384 new_tunnel = True
385 385 else:
386 386 tunnel_host = req.get_selector()
387 387 new_tunnel = False
388 388
389 389 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
390 390 urlparts = urlparse.urlparse(tunnel_host)
391 391 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
392 392 realhostport = urlparts[1]
393 393 if realhostport[-1] == ']' or ':' not in realhostport:
394 394 realhostport += ':443'
395 395
396 396 h.realhostport = realhostport
397 397 h.headers = req.headers.copy()
398 398 h.headers.update(handler.parent.addheaders)
399 399 return
400 400
401 401 h.realhostport = None
402 402 h.headers = None
403 403
404 404 def _generic_proxytunnel(self):
405 405 proxyheaders = dict(
406 406 [(x, self.headers[x]) for x in self.headers
407 407 if x.lower().startswith('proxy-')])
408 408 self._set_hostport(self.host, self.port)
409 409 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
410 410 for header in proxyheaders.iteritems():
411 411 self.send('%s: %s\r\n' % header)
412 412 self.send('\r\n')
413 413
414 414 # majority of the following code is duplicated from
415 415 # httplib.HTTPConnection as there are no adequate places to
416 416 # override functions to provide the needed functionality
417 417 res = self.response_class(self.sock,
418 418 strict=self.strict,
419 419 method=self._method)
420 420
421 421 while True:
422 422 version, status, reason = res._read_status()
423 423 if status != httplib.CONTINUE:
424 424 break
425 425 while True:
426 426 skip = res.fp.readline().strip()
427 427 if not skip:
428 428 break
429 429 res.status = status
430 430 res.reason = reason.strip()
431 431
432 432 if res.status == 200:
433 433 while True:
434 434 line = res.fp.readline()
435 435 if line == '\r\n':
436 436 break
437 437 return True
438 438
439 439 if version == 'HTTP/1.0':
440 440 res.version = 10
441 441 elif version.startswith('HTTP/1.'):
442 442 res.version = 11
443 443 elif version == 'HTTP/0.9':
444 444 res.version = 9
445 445 else:
446 446 raise httplib.UnknownProtocol(version)
447 447
448 448 if res.version == 9:
449 449 res.length = None
450 450 res.chunked = 0
451 451 res.will_close = 1
452 452 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
453 453 return False
454 454
455 455 res.msg = httplib.HTTPMessage(res.fp)
456 456 res.msg.fp = None
457 457
458 458 # are we using the chunked-style of transfer encoding?
459 459 trenc = res.msg.getheader('transfer-encoding')
460 460 if trenc and trenc.lower() == "chunked":
461 461 res.chunked = 1
462 462 res.chunk_left = None
463 463 else:
464 464 res.chunked = 0
465 465
466 466 # will the connection close at the end of the response?
467 467 res.will_close = res._check_close()
468 468
469 469 # do we have a Content-Length?
470 470 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
471 471 length = res.msg.getheader('content-length')
472 472 if length and not res.chunked:
473 473 try:
474 474 res.length = int(length)
475 475 except ValueError:
476 476 res.length = None
477 477 else:
478 478 if res.length < 0: # ignore nonsensical negative lengths
479 479 res.length = None
480 480 else:
481 481 res.length = None
482 482
483 483 # does the body have a fixed length? (of zero)
484 484 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
485 485 100 <= status < 200 or # 1xx codes
486 486 res._method == 'HEAD'):
487 487 res.length = 0
488 488
489 489 # if the connection remains open, and we aren't using chunked, and
490 490 # a content-length was not provided, then assume that the connection
491 491 # WILL close.
492 492 if (not res.will_close and
493 493 not res.chunked and
494 494 res.length is None):
495 495 res.will_close = 1
496 496
497 497 self.proxyres = res
498 498
499 499 return False
500 500
501 501 class httphandler(keepalive.HTTPHandler):
502 502 def http_open(self, req):
503 503 return self.do_open(httpconnection, req)
504 504
505 505 def _start_transaction(self, h, req):
506 506 _generic_start_transaction(self, h, req)
507 507 return keepalive.HTTPHandler._start_transaction(self, h, req)
508 508
509 509 def _verifycert(cert, hostname):
510 510 '''Verify that cert (in socket.getpeercert() format) matches hostname.
511 511 CRLs is not handled.
512 512
513 513 Returns error message if any problems are found and None on success.
514 514 '''
515 515 if not cert:
516 516 return _('no certificate received')
517 517 dnsname = hostname.lower()
518 518 def matchdnsname(certname):
519 519 return (certname == dnsname or
520 520 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
521 521
522 522 san = cert.get('subjectAltName', [])
523 523 if san:
524 524 certnames = [value.lower() for key, value in san if key == 'DNS']
525 525 for name in certnames:
526 526 if matchdnsname(name):
527 527 return None
528 528 return _('certificate is for %s') % ', '.join(certnames)
529 529
530 530 # subject is only checked when subjectAltName is empty
531 531 for s in cert.get('subject', []):
532 532 key, value = s[0]
533 533 if key == 'commonName':
534 534 try:
535 535 # 'subject' entries are unicode
536 536 certname = value.lower().encode('ascii')
537 537 except UnicodeEncodeError:
538 538 return _('IDN in certificate not supported')
539 539 if matchdnsname(certname):
540 540 return None
541 541 return _('certificate is for %s') % certname
542 542 return _('no commonName or subjectAltName found in certificate')
543 543
544 544 if has_https:
545 545 class BetterHTTPS(httplib.HTTPSConnection):
546 546 send = keepalive.safesend
547 547
548 548 def connect(self):
549 host = self.host
549 550 cacerts = self.ui.config('web', 'cacerts')
550 if cacerts:
551 cacerts = util.expandpath(cacerts)
551 hostfingerprint = self.ui.config('hostfingerprints', host)
552 552
553 hostfingerprint = self.ui.config('hostfingerprints', self.host)
554 553 if cacerts and not hostfingerprint:
555 554 sock = _create_connection((self.host, self.port))
556 self.sock = _ssl_wrap_socket(sock, self.key_file,
555 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
557 556 self.cert_file, cert_reqs=CERT_REQUIRED,
558 ca_certs=cacerts)
559 msg = _verifycert(self.sock.getpeercert(), self.host)
557 ca_certs=util.expandpath(cacerts))
558 msg = _verifycert(self.sock.getpeercert(), host)
560 559 if msg:
561 560 raise util.Abort(_('%s certificate error: %s '
562 561 '(use --insecure to connect '
563 'insecurely)') % (self.host, msg))
564 self.ui.debug('%s certificate successfully verified\n' %
565 self.host)
562 'insecurely)') % (host, msg))
563 self.ui.debug('%s certificate successfully verified\n' % host)
566 564 else:
567 565 httplib.HTTPSConnection.connect(self)
568 566 if hasattr(self.sock, 'getpeercert'):
569 567 peercert = self.sock.getpeercert(True)
570 568 peerfingerprint = util.sha1(peercert).hexdigest()
571 569 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
572 570 for x in xrange(0, len(peerfingerprint), 2)])
573 571 if hostfingerprint:
574 572 if peerfingerprint.lower() != \
575 573 hostfingerprint.replace(':', '').lower():
576 574 raise util.Abort(_('invalid certificate for %s '
577 575 'with fingerprint %s') %
578 (self.host, nicefingerprint))
576 (host, nicefingerprint))
579 577 self.ui.debug('%s certificate matched fingerprint %s\n' %
580 (self.host, nicefingerprint))
578 (host, nicefingerprint))
581 579 else:
582 580 self.ui.warn(_('warning: %s certificate '
583 581 'with fingerprint %s not verified '
584 582 '(check hostfingerprints or web.cacerts '
585 583 'config setting)\n') %
586 (self.host, nicefingerprint))
584 (host, nicefingerprint))
587 585 else: # python 2.5 ?
588 586 if hostfingerprint:
589 raise util.Abort(_('no certificate for %s '
590 'with fingerprint') % self.host)
587 raise util.Abort(_('no certificate for %s with '
588 'configured hostfingerprint') % host)
591 589 self.ui.warn(_('warning: %s certificate not verified '
592 590 '(check web.cacerts config setting)\n') %
593 self.host)
591 host)
594 592
595 593 class httpsconnection(BetterHTTPS):
596 594 response_class = keepalive.HTTPResponse
597 595 # must be able to send big bundle as stream.
598 596 send = _gen_sendfile(BetterHTTPS.send)
599 597 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
600 598
601 599 def connect(self):
602 600 if self.realhostport: # use CONNECT proxy
603 601 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
604 602 self.sock.connect((self.host, self.port))
605 603 if _generic_proxytunnel(self):
606 604 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
607 605 self.cert_file)
608 606 else:
609 607 BetterHTTPS.connect(self)
610 608
611 609 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
612 610 def __init__(self, ui):
613 611 keepalive.KeepAliveHandler.__init__(self)
614 612 urllib2.HTTPSHandler.__init__(self)
615 613 self.ui = ui
616 614 self.pwmgr = passwordmgr(self.ui)
617 615
618 616 def _start_transaction(self, h, req):
619 617 _generic_start_transaction(self, h, req)
620 618 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
621 619
622 620 def https_open(self, req):
623 621 res = readauthforuri(self.ui, req.get_full_url())
624 622 if res:
625 623 group, auth = res
626 624 self.auth = auth
627 625 self.ui.debug("using auth.%s.* for authentication\n" % group)
628 626 else:
629 627 self.auth = None
630 628 return self.do_open(self._makeconnection, req)
631 629
632 630 def _makeconnection(self, host, port=None, *args, **kwargs):
633 631 keyfile = None
634 632 certfile = None
635 633
636 634 if len(args) >= 1: # key_file
637 635 keyfile = args[0]
638 636 if len(args) >= 2: # cert_file
639 637 certfile = args[1]
640 638 args = args[2:]
641 639
642 640 # if the user has specified different key/cert files in
643 641 # hgrc, we prefer these
644 642 if self.auth and 'key' in self.auth and 'cert' in self.auth:
645 643 keyfile = self.auth['key']
646 644 certfile = self.auth['cert']
647 645
648 646 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
649 647 conn.ui = self.ui
650 648 return conn
651 649
652 650 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
653 651 def __init__(self, *args, **kwargs):
654 652 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
655 653 self.retried_req = None
656 654
657 655 def reset_retry_count(self):
658 656 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
659 657 # forever. We disable reset_retry_count completely and reset in
660 658 # http_error_auth_reqed instead.
661 659 pass
662 660
663 661 def http_error_auth_reqed(self, auth_header, host, req, headers):
664 662 # Reset the retry counter once for each request.
665 663 if req is not self.retried_req:
666 664 self.retried_req = req
667 665 self.retried = 0
668 666 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
669 667 # it doesn't know about the auth type requested. This can happen if
670 668 # somebody is using BasicAuth and types a bad password.
671 669 try:
672 670 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
673 671 self, auth_header, host, req, headers)
674 672 except ValueError, inst:
675 673 arg = inst.args[0]
676 674 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
677 675 return
678 676 raise
679 677
680 678 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
681 679 def __init__(self, *args, **kwargs):
682 680 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
683 681 self.retried_req = None
684 682
685 683 def reset_retry_count(self):
686 684 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
687 685 # forever. We disable reset_retry_count completely and reset in
688 686 # http_error_auth_reqed instead.
689 687 pass
690 688
691 689 def http_error_auth_reqed(self, auth_header, host, req, headers):
692 690 # Reset the retry counter once for each request.
693 691 if req is not self.retried_req:
694 692 self.retried_req = req
695 693 self.retried = 0
696 694 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
697 695 self, auth_header, host, req, headers)
698 696
699 697 def getauthinfo(path):
700 698 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
701 699 if not urlpath:
702 700 urlpath = '/'
703 701 if scheme != 'file':
704 702 # XXX: why are we quoting the path again with some smart
705 703 # heuristic here? Anyway, it cannot be done with file://
706 704 # urls since path encoding is os/fs dependent (see
707 705 # urllib.pathname2url() for details).
708 706 urlpath = quotepath(urlpath)
709 707 host, port, user, passwd = netlocsplit(netloc)
710 708
711 709 # urllib cannot handle URLs with embedded user or passwd
712 710 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
713 711 urlpath, query, frag))
714 712 if user:
715 713 netloc = host
716 714 if port:
717 715 netloc += ':' + port
718 716 # Python < 2.4.3 uses only the netloc to search for a password
719 717 authinfo = (None, (url, netloc), user, passwd or '')
720 718 else:
721 719 authinfo = None
722 720 return url, authinfo
723 721
724 722 handlerfuncs = []
725 723
726 724 def opener(ui, authinfo=None):
727 725 '''
728 726 construct an opener suitable for urllib2
729 727 authinfo will be added to the password manager
730 728 '''
731 729 handlers = [httphandler()]
732 730 if has_https:
733 731 handlers.append(httpshandler(ui))
734 732
735 733 handlers.append(proxyhandler(ui))
736 734
737 735 passmgr = passwordmgr(ui)
738 736 if authinfo is not None:
739 737 passmgr.add_password(*authinfo)
740 738 user, passwd = authinfo[2:4]
741 739 ui.debug('http auth: user %s, password %s\n' %
742 740 (user, passwd and '*' * len(passwd) or 'not set'))
743 741
744 742 handlers.extend((httpbasicauthhandler(passmgr),
745 743 httpdigestauthhandler(passmgr)))
746 744 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
747 745 opener = urllib2.build_opener(*handlers)
748 746
749 747 # 1.0 here is the _protocol_ version
750 748 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
751 749 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
752 750 return opener
753 751
754 752 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
755 753
756 754 def open(ui, url, data=None):
757 755 scheme = None
758 756 m = scheme_re.search(url)
759 757 if m:
760 758 scheme = m.group(1).lower()
761 759 if not scheme:
762 760 path = util.normpath(os.path.abspath(url))
763 761 url = 'file://' + urllib.pathname2url(path)
764 762 authinfo = None
765 763 else:
766 764 url, authinfo = getauthinfo(url)
767 765 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now