##// END OF EJS Templates
url: remove test for self.ui in BetterHTTPS...
Mads Kiilerich -
r13419:1cc73868 default
parent child Browse files
Show More
@@ -1,770 +1,767
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(connection):
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 connection.send(self, chunk)
302 302 else:
303 303 connection.send(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)
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 if hasattr(self, 'ui'):
550 cacerts = self.ui.config('web', 'cacerts')
551 if cacerts:
552 cacerts = util.expandpath(cacerts)
553 else:
554 cacerts = None
549 cacerts = self.ui.config('web', 'cacerts')
550 if cacerts:
551 cacerts = util.expandpath(cacerts)
555 552
556 553 hostfingerprint = self.ui.config('hostfingerprints', self.host)
557 554 if cacerts and not hostfingerprint:
558 555 sock = _create_connection((self.host, self.port))
559 556 self.sock = _ssl_wrap_socket(sock, self.key_file,
560 557 self.cert_file, cert_reqs=CERT_REQUIRED,
561 558 ca_certs=cacerts)
562 559 msg = _verifycert(self.sock.getpeercert(), self.host)
563 560 if msg:
564 561 raise util.Abort(_('%s certificate error: %s '
565 562 '(use --insecure to connect '
566 563 'insecurely)') % (self.host, msg))
567 564 self.ui.debug('%s certificate successfully verified\n' %
568 565 self.host)
569 566 else:
570 567 httplib.HTTPSConnection.connect(self)
571 568 if hasattr(self.sock, 'getpeercert'):
572 569 peercert = self.sock.getpeercert(True)
573 570 peerfingerprint = util.sha1(peercert).hexdigest()
574 571 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
575 572 for x in xrange(0, len(peerfingerprint), 2)])
576 573 if hostfingerprint:
577 574 if peerfingerprint.lower() != \
578 575 hostfingerprint.replace(':', '').lower():
579 576 raise util.Abort(_('invalid certificate for %s '
580 577 'with fingerprint %s') %
581 578 (self.host, nicefingerprint))
582 579 self.ui.debug('%s certificate matched fingerprint %s\n' %
583 580 (self.host, nicefingerprint))
584 581 else:
585 582 self.ui.warn(_('warning: %s certificate '
586 583 'with fingerprint %s not verified '
587 584 '(check hostfingerprints or web.cacerts '
588 585 'config setting)\n') %
589 586 (self.host, nicefingerprint))
590 587 else: # python 2.5 ?
591 588 if hostfingerprint:
592 589 raise util.Abort(_('no certificate for %s '
593 590 'with fingerprint') % self.host)
594 591 self.ui.warn(_('warning: %s certificate not verified '
595 592 '(check web.cacerts config setting)\n') %
596 593 self.host)
597 594
598 595 class httpsconnection(BetterHTTPS):
599 596 response_class = keepalive.HTTPResponse
600 597 # must be able to send big bundle as stream.
601 598 send = _gen_sendfile(BetterHTTPS)
602 599 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
603 600
604 601 def connect(self):
605 602 if self.realhostport: # use CONNECT proxy
606 603 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
607 604 self.sock.connect((self.host, self.port))
608 605 if _generic_proxytunnel(self):
609 606 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
610 607 self.cert_file)
611 608 else:
612 609 BetterHTTPS.connect(self)
613 610
614 611 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
615 612 def __init__(self, ui):
616 613 keepalive.KeepAliveHandler.__init__(self)
617 614 urllib2.HTTPSHandler.__init__(self)
618 615 self.ui = ui
619 616 self.pwmgr = passwordmgr(self.ui)
620 617
621 618 def _start_transaction(self, h, req):
622 619 _generic_start_transaction(self, h, req)
623 620 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
624 621
625 622 def https_open(self, req):
626 623 res = readauthforuri(self.ui, req.get_full_url())
627 624 if res:
628 625 group, auth = res
629 626 self.auth = auth
630 627 self.ui.debug("using auth.%s.* for authentication\n" % group)
631 628 else:
632 629 self.auth = None
633 630 return self.do_open(self._makeconnection, req)
634 631
635 632 def _makeconnection(self, host, port=None, *args, **kwargs):
636 633 keyfile = None
637 634 certfile = None
638 635
639 636 if len(args) >= 1: # key_file
640 637 keyfile = args[0]
641 638 if len(args) >= 2: # cert_file
642 639 certfile = args[1]
643 640 args = args[2:]
644 641
645 642 # if the user has specified different key/cert files in
646 643 # hgrc, we prefer these
647 644 if self.auth and 'key' in self.auth and 'cert' in self.auth:
648 645 keyfile = self.auth['key']
649 646 certfile = self.auth['cert']
650 647
651 648 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
652 649 conn.ui = self.ui
653 650 return conn
654 651
655 652 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
656 653 def __init__(self, *args, **kwargs):
657 654 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
658 655 self.retried_req = None
659 656
660 657 def reset_retry_count(self):
661 658 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
662 659 # forever. We disable reset_retry_count completely and reset in
663 660 # http_error_auth_reqed instead.
664 661 pass
665 662
666 663 def http_error_auth_reqed(self, auth_header, host, req, headers):
667 664 # Reset the retry counter once for each request.
668 665 if req is not self.retried_req:
669 666 self.retried_req = req
670 667 self.retried = 0
671 668 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
672 669 # it doesn't know about the auth type requested. This can happen if
673 670 # somebody is using BasicAuth and types a bad password.
674 671 try:
675 672 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
676 673 self, auth_header, host, req, headers)
677 674 except ValueError, inst:
678 675 arg = inst.args[0]
679 676 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
680 677 return
681 678 raise
682 679
683 680 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
684 681 def __init__(self, *args, **kwargs):
685 682 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
686 683 self.retried_req = None
687 684
688 685 def reset_retry_count(self):
689 686 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
690 687 # forever. We disable reset_retry_count completely and reset in
691 688 # http_error_auth_reqed instead.
692 689 pass
693 690
694 691 def http_error_auth_reqed(self, auth_header, host, req, headers):
695 692 # Reset the retry counter once for each request.
696 693 if req is not self.retried_req:
697 694 self.retried_req = req
698 695 self.retried = 0
699 696 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
700 697 self, auth_header, host, req, headers)
701 698
702 699 def getauthinfo(path):
703 700 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
704 701 if not urlpath:
705 702 urlpath = '/'
706 703 if scheme != 'file':
707 704 # XXX: why are we quoting the path again with some smart
708 705 # heuristic here? Anyway, it cannot be done with file://
709 706 # urls since path encoding is os/fs dependent (see
710 707 # urllib.pathname2url() for details).
711 708 urlpath = quotepath(urlpath)
712 709 host, port, user, passwd = netlocsplit(netloc)
713 710
714 711 # urllib cannot handle URLs with embedded user or passwd
715 712 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
716 713 urlpath, query, frag))
717 714 if user:
718 715 netloc = host
719 716 if port:
720 717 netloc += ':' + port
721 718 # Python < 2.4.3 uses only the netloc to search for a password
722 719 authinfo = (None, (url, netloc), user, passwd or '')
723 720 else:
724 721 authinfo = None
725 722 return url, authinfo
726 723
727 724 handlerfuncs = []
728 725
729 726 def opener(ui, authinfo=None):
730 727 '''
731 728 construct an opener suitable for urllib2
732 729 authinfo will be added to the password manager
733 730 '''
734 731 handlers = [httphandler()]
735 732 if has_https:
736 733 handlers.append(httpshandler(ui))
737 734
738 735 handlers.append(proxyhandler(ui))
739 736
740 737 passmgr = passwordmgr(ui)
741 738 if authinfo is not None:
742 739 passmgr.add_password(*authinfo)
743 740 user, passwd = authinfo[2:4]
744 741 ui.debug('http auth: user %s, password %s\n' %
745 742 (user, passwd and '*' * len(passwd) or 'not set'))
746 743
747 744 handlers.extend((httpbasicauthhandler(passmgr),
748 745 httpdigestauthhandler(passmgr)))
749 746 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
750 747 opener = urllib2.build_opener(*handlers)
751 748
752 749 # 1.0 here is the _protocol_ version
753 750 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
754 751 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
755 752 return opener
756 753
757 754 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
758 755
759 756 def open(ui, url, data=None):
760 757 scheme = None
761 758 m = scheme_re.search(url)
762 759 if m:
763 760 scheme = m.group(1).lower()
764 761 if not scheme:
765 762 path = util.normpath(os.path.abspath(url))
766 763 url = 'file://' + urllib.pathname2url(path)
767 764 authinfo = None
768 765 else:
769 766 url, authinfo = getauthinfo(url)
770 767 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now