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