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