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