##// END OF EJS Templates
merge with stable
Mads Kiilerich -
r13250:1a4330e3 merge default
parent child Browse files
Show More
@@ -1,721 +1,737 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, ui, *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.ui = ui
266 266 self._data = __builtin__.open(*args, **kwargs)
267 267 self.seek = self._data.seek
268 268 self.close = self._data.close
269 269 self.write = self._data.write
270 270 self._len = os.fstat(self._data.fileno()).st_size
271 271 self._pos = 0
272 272 self._total = len(self) / 1024 * 2
273 273
274 274 def read(self, *args, **kwargs):
275 275 try:
276 276 ret = self._data.read(*args, **kwargs)
277 277 except EOFError:
278 278 self.ui.progress(_('sending'), None)
279 279 self._pos += len(ret)
280 280 # We pass double the max for total because we currently have
281 281 # to send the bundle twice in the case of a server that
282 282 # requires authentication. Since we can't know until we try
283 283 # once whether authentication will be required, just lie to
284 284 # the user and maybe the push succeeds suddenly at 50%.
285 285 self.ui.progress(_('sending'), self._pos / 1024,
286 286 unit=_('kb'), total=self._total)
287 287 return ret
288 288
289 289 def __len__(self):
290 290 return self._len
291 291
292 292 def _gen_sendfile(connection):
293 293 def _sendfile(self, data):
294 294 # send a file
295 295 if isinstance(data, httpsendfile):
296 296 # if auth required, some data sent twice, so rewind here
297 297 data.seek(0)
298 298 for chunk in util.filechunkiter(data):
299 299 connection.send(self, chunk)
300 300 else:
301 301 connection.send(self, data)
302 302 return _sendfile
303 303
304 304 has_https = hasattr(urllib2, 'HTTPSHandler')
305 305 if has_https:
306 306 try:
307 307 # avoid using deprecated/broken FakeSocket in python 2.6
308 308 import ssl
309 309 _ssl_wrap_socket = ssl.wrap_socket
310 310 CERT_REQUIRED = ssl.CERT_REQUIRED
311 311 except ImportError:
312 312 CERT_REQUIRED = 2
313 313
314 314 def _ssl_wrap_socket(sock, key_file, cert_file,
315 315 cert_reqs=CERT_REQUIRED, ca_certs=None):
316 316 if ca_certs:
317 317 raise util.Abort(_(
318 318 'certificate checking requires Python 2.6'))
319 319
320 320 ssl = socket.ssl(sock, key_file, cert_file)
321 321 return httplib.FakeSocket(sock, ssl)
322 322
323 323 try:
324 324 _create_connection = socket.create_connection
325 325 except AttributeError:
326 326 _GLOBAL_DEFAULT_TIMEOUT = object()
327 327
328 328 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
329 329 source_address=None):
330 330 # lifted from Python 2.6
331 331
332 332 msg = "getaddrinfo returns an empty list"
333 333 host, port = address
334 334 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
335 335 af, socktype, proto, canonname, sa = res
336 336 sock = None
337 337 try:
338 338 sock = socket.socket(af, socktype, proto)
339 339 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
340 340 sock.settimeout(timeout)
341 341 if source_address:
342 342 sock.bind(source_address)
343 343 sock.connect(sa)
344 344 return sock
345 345
346 346 except socket.error, msg:
347 347 if sock is not None:
348 348 sock.close()
349 349
350 350 raise socket.error, msg
351 351
352 352 class httpconnection(keepalive.HTTPConnection):
353 353 # must be able to send big bundle as stream.
354 354 send = _gen_sendfile(keepalive.HTTPConnection)
355 355
356 356 def connect(self):
357 357 if has_https and self.realhostport: # use CONNECT proxy
358 358 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
359 359 self.sock.connect((self.host, self.port))
360 360 if _generic_proxytunnel(self):
361 361 # we do not support client x509 certificates
362 362 self.sock = _ssl_wrap_socket(self.sock, None, None)
363 363 else:
364 364 keepalive.HTTPConnection.connect(self)
365 365
366 366 def getresponse(self):
367 367 proxyres = getattr(self, 'proxyres', None)
368 368 if proxyres:
369 369 if proxyres.will_close:
370 370 self.close()
371 371 self.proxyres = None
372 372 return proxyres
373 373 return keepalive.HTTPConnection.getresponse(self)
374 374
375 375 # general transaction handler to support different ways to handle
376 376 # HTTPS proxying before and after Python 2.6.3.
377 377 def _generic_start_transaction(handler, h, req):
378 378 if hasattr(req, '_tunnel_host') and req._tunnel_host:
379 379 tunnel_host = req._tunnel_host
380 380 if tunnel_host[:7] not in ['http://', 'https:/']:
381 381 tunnel_host = 'https://' + tunnel_host
382 382 new_tunnel = True
383 383 else:
384 384 tunnel_host = req.get_selector()
385 385 new_tunnel = False
386 386
387 387 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
388 388 urlparts = urlparse.urlparse(tunnel_host)
389 389 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
390 390 realhostport = urlparts[1]
391 391 if realhostport[-1] == ']' or ':' not in realhostport:
392 392 realhostport += ':443'
393 393
394 394 h.realhostport = realhostport
395 395 h.headers = req.headers.copy()
396 396 h.headers.update(handler.parent.addheaders)
397 397 return
398 398
399 399 h.realhostport = None
400 400 h.headers = None
401 401
402 402 def _generic_proxytunnel(self):
403 403 proxyheaders = dict(
404 404 [(x, self.headers[x]) for x in self.headers
405 405 if x.lower().startswith('proxy-')])
406 406 self._set_hostport(self.host, self.port)
407 407 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
408 408 for header in proxyheaders.iteritems():
409 409 self.send('%s: %s\r\n' % header)
410 410 self.send('\r\n')
411 411
412 412 # majority of the following code is duplicated from
413 413 # httplib.HTTPConnection as there are no adequate places to
414 414 # override functions to provide the needed functionality
415 415 res = self.response_class(self.sock,
416 416 strict=self.strict,
417 417 method=self._method)
418 418
419 419 while True:
420 420 version, status, reason = res._read_status()
421 421 if status != httplib.CONTINUE:
422 422 break
423 423 while True:
424 424 skip = res.fp.readline().strip()
425 425 if not skip:
426 426 break
427 427 res.status = status
428 428 res.reason = reason.strip()
429 429
430 430 if res.status == 200:
431 431 while True:
432 432 line = res.fp.readline()
433 433 if line == '\r\n':
434 434 break
435 435 return True
436 436
437 437 if version == 'HTTP/1.0':
438 438 res.version = 10
439 439 elif version.startswith('HTTP/1.'):
440 440 res.version = 11
441 441 elif version == 'HTTP/0.9':
442 442 res.version = 9
443 443 else:
444 444 raise httplib.UnknownProtocol(version)
445 445
446 446 if res.version == 9:
447 447 res.length = None
448 448 res.chunked = 0
449 449 res.will_close = 1
450 450 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
451 451 return False
452 452
453 453 res.msg = httplib.HTTPMessage(res.fp)
454 454 res.msg.fp = None
455 455
456 456 # are we using the chunked-style of transfer encoding?
457 457 trenc = res.msg.getheader('transfer-encoding')
458 458 if trenc and trenc.lower() == "chunked":
459 459 res.chunked = 1
460 460 res.chunk_left = None
461 461 else:
462 462 res.chunked = 0
463 463
464 464 # will the connection close at the end of the response?
465 465 res.will_close = res._check_close()
466 466
467 467 # do we have a Content-Length?
468 468 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
469 469 length = res.msg.getheader('content-length')
470 470 if length and not res.chunked:
471 471 try:
472 472 res.length = int(length)
473 473 except ValueError:
474 474 res.length = None
475 475 else:
476 476 if res.length < 0: # ignore nonsensical negative lengths
477 477 res.length = None
478 478 else:
479 479 res.length = None
480 480
481 481 # does the body have a fixed length? (of zero)
482 482 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
483 483 100 <= status < 200 or # 1xx codes
484 484 res._method == 'HEAD'):
485 485 res.length = 0
486 486
487 487 # if the connection remains open, and we aren't using chunked, and
488 488 # a content-length was not provided, then assume that the connection
489 489 # WILL close.
490 490 if (not res.will_close and
491 491 not res.chunked and
492 492 res.length is None):
493 493 res.will_close = 1
494 494
495 495 self.proxyres = res
496 496
497 497 return False
498 498
499 499 class httphandler(keepalive.HTTPHandler):
500 500 def http_open(self, req):
501 501 return self.do_open(httpconnection, req)
502 502
503 503 def _start_transaction(self, h, req):
504 504 _generic_start_transaction(self, h, req)
505 505 return keepalive.HTTPHandler._start_transaction(self, h, req)
506 506
507 507 def _verifycert(cert, hostname):
508 508 '''Verify that cert (in socket.getpeercert() format) matches hostname.
509 CRLs and subjectAltName are not handled.
509 CRLs is not handled.
510 510
511 511 Returns error message if any problems are found and None on success.
512 512 '''
513 513 if not cert:
514 514 return _('no certificate received')
515 515 dnsname = hostname.lower()
516 def matchdnsname(certname):
517 return (certname == dnsname or
518 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
519
520 san = cert.get('subjectAltName', [])
521 if san:
522 certnames = [value.lower() for key, value in san if key == 'DNS']
523 for name in certnames:
524 if matchdnsname(name):
525 return None
526 return _('certificate is for %s') % ', '.join(certnames)
527
528 # subject is only checked when subjectAltName is empty
516 529 for s in cert.get('subject', []):
517 530 key, value = s[0]
518 531 if key == 'commonName':
519 certname = value.lower()
520 if (certname == dnsname or
521 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
532 try:
533 # 'subject' entries are unicode
534 certname = value.lower().encode('ascii')
535 except UnicodeEncodeError:
536 return _('IDN in certificate not supported')
537 if matchdnsname(certname):
522 538 return None
523 539 return _('certificate is for %s') % certname
524 return _('no commonName found in certificate')
540 return _('no commonName or subjectAltName found in certificate')
525 541
526 542 if has_https:
527 543 class BetterHTTPS(httplib.HTTPSConnection):
528 544 send = keepalive.safesend
529 545
530 546 def connect(self):
531 547 if hasattr(self, 'ui'):
532 548 cacerts = self.ui.config('web', 'cacerts')
533 549 if cacerts:
534 550 cacerts = util.expandpath(cacerts)
535 551 else:
536 552 cacerts = None
537 553
538 554 if cacerts:
539 555 sock = _create_connection((self.host, self.port))
540 556 self.sock = _ssl_wrap_socket(sock, self.key_file,
541 557 self.cert_file, cert_reqs=CERT_REQUIRED,
542 558 ca_certs=cacerts)
543 559 msg = _verifycert(self.sock.getpeercert(), self.host)
544 560 if msg:
545 561 raise util.Abort(_('%s certificate error: %s') %
546 562 (self.host, msg))
547 563 self.ui.debug('%s certificate successfully verified\n' %
548 564 self.host)
549 565 else:
550 566 self.ui.warn(_("warning: %s certificate not verified "
551 567 "(check web.cacerts config setting)\n") %
552 568 self.host)
553 569 httplib.HTTPSConnection.connect(self)
554 570
555 571 class httpsconnection(BetterHTTPS):
556 572 response_class = keepalive.HTTPResponse
557 573 # must be able to send big bundle as stream.
558 574 send = _gen_sendfile(BetterHTTPS)
559 575 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
560 576
561 577 def connect(self):
562 578 if self.realhostport: # use CONNECT proxy
563 579 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
564 580 self.sock.connect((self.host, self.port))
565 581 if _generic_proxytunnel(self):
566 582 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
567 583 self.cert_file)
568 584 else:
569 585 BetterHTTPS.connect(self)
570 586
571 587 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
572 588 def __init__(self, ui):
573 589 keepalive.KeepAliveHandler.__init__(self)
574 590 urllib2.HTTPSHandler.__init__(self)
575 591 self.ui = ui
576 592 self.pwmgr = passwordmgr(self.ui)
577 593
578 594 def _start_transaction(self, h, req):
579 595 _generic_start_transaction(self, h, req)
580 596 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
581 597
582 598 def https_open(self, req):
583 599 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
584 600 return self.do_open(self._makeconnection, req)
585 601
586 602 def _makeconnection(self, host, port=None, *args, **kwargs):
587 603 keyfile = None
588 604 certfile = None
589 605
590 606 if len(args) >= 1: # key_file
591 607 keyfile = args[0]
592 608 if len(args) >= 2: # cert_file
593 609 certfile = args[1]
594 610 args = args[2:]
595 611
596 612 # if the user has specified different key/cert files in
597 613 # hgrc, we prefer these
598 614 if self.auth and 'key' in self.auth and 'cert' in self.auth:
599 615 keyfile = self.auth['key']
600 616 certfile = self.auth['cert']
601 617
602 618 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
603 619 conn.ui = self.ui
604 620 return conn
605 621
606 622 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
607 623 def __init__(self, *args, **kwargs):
608 624 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
609 625 self.retried_req = None
610 626
611 627 def reset_retry_count(self):
612 628 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
613 629 # forever. We disable reset_retry_count completely and reset in
614 630 # http_error_auth_reqed instead.
615 631 pass
616 632
617 633 def http_error_auth_reqed(self, auth_header, host, req, headers):
618 634 # Reset the retry counter once for each request.
619 635 if req is not self.retried_req:
620 636 self.retried_req = req
621 637 self.retried = 0
622 638 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
623 639 # it doesn't know about the auth type requested. This can happen if
624 640 # somebody is using BasicAuth and types a bad password.
625 641 try:
626 642 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
627 643 self, auth_header, host, req, headers)
628 644 except ValueError, inst:
629 645 arg = inst.args[0]
630 646 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
631 647 return
632 648 raise
633 649
634 650 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
635 651 def __init__(self, *args, **kwargs):
636 652 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
637 653 self.retried_req = None
638 654
639 655 def reset_retry_count(self):
640 656 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
641 657 # forever. We disable reset_retry_count completely and reset in
642 658 # http_error_auth_reqed instead.
643 659 pass
644 660
645 661 def http_error_auth_reqed(self, auth_header, host, req, headers):
646 662 # Reset the retry counter once for each request.
647 663 if req is not self.retried_req:
648 664 self.retried_req = req
649 665 self.retried = 0
650 666 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
651 667 self, auth_header, host, req, headers)
652 668
653 669 def getauthinfo(path):
654 670 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
655 671 if not urlpath:
656 672 urlpath = '/'
657 673 if scheme != 'file':
658 674 # XXX: why are we quoting the path again with some smart
659 675 # heuristic here? Anyway, it cannot be done with file://
660 676 # urls since path encoding is os/fs dependent (see
661 677 # urllib.pathname2url() for details).
662 678 urlpath = quotepath(urlpath)
663 679 host, port, user, passwd = netlocsplit(netloc)
664 680
665 681 # urllib cannot handle URLs with embedded user or passwd
666 682 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
667 683 urlpath, query, frag))
668 684 if user:
669 685 netloc = host
670 686 if port:
671 687 netloc += ':' + port
672 688 # Python < 2.4.3 uses only the netloc to search for a password
673 689 authinfo = (None, (url, netloc), user, passwd or '')
674 690 else:
675 691 authinfo = None
676 692 return url, authinfo
677 693
678 694 handlerfuncs = []
679 695
680 696 def opener(ui, authinfo=None):
681 697 '''
682 698 construct an opener suitable for urllib2
683 699 authinfo will be added to the password manager
684 700 '''
685 701 handlers = [httphandler()]
686 702 if has_https:
687 703 handlers.append(httpshandler(ui))
688 704
689 705 handlers.append(proxyhandler(ui))
690 706
691 707 passmgr = passwordmgr(ui)
692 708 if authinfo is not None:
693 709 passmgr.add_password(*authinfo)
694 710 user, passwd = authinfo[2:4]
695 711 ui.debug('http auth: user %s, password %s\n' %
696 712 (user, passwd and '*' * len(passwd) or 'not set'))
697 713
698 714 handlers.extend((httpbasicauthhandler(passmgr),
699 715 httpdigestauthhandler(passmgr)))
700 716 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
701 717 opener = urllib2.build_opener(*handlers)
702 718
703 719 # 1.0 here is the _protocol_ version
704 720 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
705 721 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
706 722 return opener
707 723
708 724 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
709 725
710 726 def open(ui, url, data=None):
711 727 scheme = None
712 728 m = scheme_re.search(url)
713 729 if m:
714 730 scheme = m.group(1).lower()
715 731 if not scheme:
716 732 path = util.normpath(os.path.abspath(url))
717 733 url = 'file://' + urllib.pathname2url(path)
718 734 authinfo = None
719 735 else:
720 736 url, authinfo = getauthinfo(url)
721 737 return opener(ui, authinfo).open(url, data)
@@ -1,38 +1,54 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 # Test subjectAltName
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 'subjectAltName': (('DNS', '*.example.net'),
31 ('DNS', 'example.net'))}
32 check(_verifycert(san_cert, 'example.net'),
33 None)
34 check(_verifycert(san_cert, 'foo.example.net'),
35 None)
36 # subject is only checked when subjectAltName is empty
37 check(_verifycert(san_cert, 'example.com'),
38 'certificate is for *.example.net, example.net')
39
28 40 # Avoid some pitfalls
29 41 check(_verifycert(cert('*.foo'), 'foo'),
30 42 'certificate is for *.foo')
31 43 check(_verifycert(cert('*o'), 'foo'),
32 44 'certificate is for *o')
33 45
34 46 check(_verifycert({'subject': ()},
35 47 'example.com'),
36 'no commonName found in certificate')
48 'no commonName or subjectAltName found in certificate')
37 49 check(_verifycert(None, 'example.com'),
38 50 'no certificate received')
51
52 # Unicode (IDN) certname isn't supported
53 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
54 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now