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