##// END OF EJS Templates
windows: use abspath in url...
marmoute -
r48430:8e5192e4 default
parent child Browse files
Show More
@@ -1,740 +1,739 b''
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Olivia Mackall <olivia@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 from __future__ import absolute_import
11 11
12 12 import base64
13 import os
14 13 import socket
15 14 import sys
16 15
17 16 from .i18n import _
18 17 from .pycompat import getattr
19 18 from . import (
20 19 encoding,
21 20 error,
22 21 httpconnection as httpconnectionmod,
23 22 keepalive,
24 23 pycompat,
25 24 sslutil,
26 25 urllibcompat,
27 26 util,
28 27 )
29 28 from .utils import (
30 29 stringutil,
31 30 urlutil,
32 31 )
33 32
34 33 httplib = util.httplib
35 34 stringio = util.stringio
36 35 urlerr = util.urlerr
37 36 urlreq = util.urlreq
38 37
39 38
40 39 def escape(s, quote=None):
41 40 """Replace special characters "&", "<" and ">" to HTML-safe sequences.
42 41 If the optional flag quote is true, the quotation mark character (")
43 42 is also translated.
44 43
45 44 This is the same as cgi.escape in Python, but always operates on
46 45 bytes, whereas cgi.escape in Python 3 only works on unicodes.
47 46 """
48 47 s = s.replace(b"&", b"&amp;")
49 48 s = s.replace(b"<", b"&lt;")
50 49 s = s.replace(b">", b"&gt;")
51 50 if quote:
52 51 s = s.replace(b'"', b"&quot;")
53 52 return s
54 53
55 54
56 55 class passwordmgr(object):
57 56 def __init__(self, ui, passwddb):
58 57 self.ui = ui
59 58 self.passwddb = passwddb
60 59
61 60 def add_password(self, realm, uri, user, passwd):
62 61 return self.passwddb.add_password(realm, uri, user, passwd)
63 62
64 63 def find_user_password(self, realm, authuri):
65 64 assert isinstance(realm, (type(None), str))
66 65 assert isinstance(authuri, str)
67 66 authinfo = self.passwddb.find_user_password(realm, authuri)
68 67 user, passwd = authinfo
69 68 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
70 69 if user and passwd:
71 70 self._writedebug(user, passwd)
72 71 return (pycompat.strurl(user), pycompat.strurl(passwd))
73 72
74 73 if not user or not passwd:
75 74 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
76 75 if res:
77 76 group, auth = res
78 77 user, passwd = auth.get(b'username'), auth.get(b'password')
79 78 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
80 79 if not user or not passwd:
81 80 u = urlutil.url(pycompat.bytesurl(authuri))
82 81 u.query = None
83 82 if not self.ui.interactive():
84 83 raise error.Abort(
85 84 _(b'http authorization required for %s')
86 85 % urlutil.hidepassword(bytes(u))
87 86 )
88 87
89 88 self.ui.write(
90 89 _(b"http authorization required for %s\n")
91 90 % urlutil.hidepassword(bytes(u))
92 91 )
93 92 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
94 93 if user:
95 94 self.ui.write(_(b"user: %s\n") % user)
96 95 else:
97 96 user = self.ui.prompt(_(b"user:"), default=None)
98 97
99 98 if not passwd:
100 99 passwd = self.ui.getpass()
101 100
102 101 # As of Python 3.8, the default implementation of
103 102 # AbstractBasicAuthHandler.retry_http_basic_auth() assumes the user
104 103 # is set if pw is not None. This means (None, str) is not a valid
105 104 # return type of find_user_password().
106 105 if user is None:
107 106 return None, None
108 107
109 108 self.passwddb.add_password(realm, authuri, user, passwd)
110 109 self._writedebug(user, passwd)
111 110 return (pycompat.strurl(user), pycompat.strurl(passwd))
112 111
113 112 def _writedebug(self, user, passwd):
114 113 msg = _(b'http auth: user %s, password %s\n')
115 114 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
116 115
117 116 def find_stored_password(self, authuri):
118 117 return self.passwddb.find_user_password(None, authuri)
119 118
120 119
121 120 class proxyhandler(urlreq.proxyhandler):
122 121 def __init__(self, ui):
123 122 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
124 123 b'http_proxy'
125 124 )
126 125 # XXX proxyauthinfo = None
127 126
128 127 if proxyurl:
129 128 # proxy can be proper url or host[:port]
130 129 if not (
131 130 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
132 131 ):
133 132 proxyurl = b'http://' + proxyurl + b'/'
134 133 proxy = urlutil.url(proxyurl)
135 134 if not proxy.user:
136 135 proxy.user = ui.config(b"http_proxy", b"user")
137 136 proxy.passwd = ui.config(b"http_proxy", b"passwd")
138 137
139 138 # see if we should use a proxy for this url
140 139 no_list = [b"localhost", b"127.0.0.1"]
141 140 no_list.extend(
142 141 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
143 142 )
144 143 no_list.extend(
145 144 [
146 145 p.strip().lower()
147 146 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
148 147 if p.strip()
149 148 ]
150 149 )
151 150 # "http_proxy.always" config is for running tests on localhost
152 151 if ui.configbool(b"http_proxy", b"always"):
153 152 self.no_list = []
154 153 else:
155 154 self.no_list = no_list
156 155
157 156 # Keys and values need to be str because the standard library
158 157 # expects them to be.
159 158 proxyurl = str(proxy)
160 159 proxies = {'http': proxyurl, 'https': proxyurl}
161 160 ui.debug(
162 161 b'proxying through %s\n' % urlutil.hidepassword(bytes(proxy))
163 162 )
164 163 else:
165 164 proxies = {}
166 165
167 166 urlreq.proxyhandler.__init__(self, proxies)
168 167 self.ui = ui
169 168
170 169 def proxy_open(self, req, proxy, type_):
171 170 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
172 171 for e in self.no_list:
173 172 if host == e:
174 173 return None
175 174 if e.startswith(b'*.') and host.endswith(e[2:]):
176 175 return None
177 176 if e.startswith(b'.') and host.endswith(e[1:]):
178 177 return None
179 178
180 179 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
181 180
182 181
183 182 def _gen_sendfile(orgsend):
184 183 def _sendfile(self, data):
185 184 # send a file
186 185 if isinstance(data, httpconnectionmod.httpsendfile):
187 186 # if auth required, some data sent twice, so rewind here
188 187 data.seek(0)
189 188 for chunk in util.filechunkiter(data):
190 189 orgsend(self, chunk)
191 190 else:
192 191 orgsend(self, data)
193 192
194 193 return _sendfile
195 194
196 195
197 196 has_https = util.safehasattr(urlreq, b'httpshandler')
198 197
199 198
200 199 class httpconnection(keepalive.HTTPConnection):
201 200 # must be able to send big bundle as stream.
202 201 send = _gen_sendfile(keepalive.HTTPConnection.send)
203 202
204 203 def getresponse(self):
205 204 proxyres = getattr(self, 'proxyres', None)
206 205 if proxyres:
207 206 if proxyres.will_close:
208 207 self.close()
209 208 self.proxyres = None
210 209 return proxyres
211 210 return keepalive.HTTPConnection.getresponse(self)
212 211
213 212
214 213 # Large parts of this function have their origin from before Python 2.6
215 214 # and could potentially be removed.
216 215 def _generic_start_transaction(handler, h, req):
217 216 tunnel_host = req._tunnel_host
218 217 if tunnel_host:
219 218 if tunnel_host[:7] not in ['http://', 'https:/']:
220 219 tunnel_host = 'https://' + tunnel_host
221 220 new_tunnel = True
222 221 else:
223 222 tunnel_host = urllibcompat.getselector(req)
224 223 new_tunnel = False
225 224
226 225 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
227 226 u = urlutil.url(pycompat.bytesurl(tunnel_host))
228 227 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
229 228 h.realhostport = b':'.join([u.host, (u.port or b'443')])
230 229 h.headers = req.headers.copy()
231 230 h.headers.update(handler.parent.addheaders)
232 231 return
233 232
234 233 h.realhostport = None
235 234 h.headers = None
236 235
237 236
238 237 def _generic_proxytunnel(self):
239 238 proxyheaders = {
240 239 pycompat.bytestr(x): pycompat.bytestr(self.headers[x])
241 240 for x in self.headers
242 241 if x.lower().startswith('proxy-')
243 242 }
244 243 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
245 244 for header in pycompat.iteritems(proxyheaders):
246 245 self.send(b'%s: %s\r\n' % header)
247 246 self.send(b'\r\n')
248 247
249 248 # majority of the following code is duplicated from
250 249 # httplib.HTTPConnection as there are no adequate places to
251 250 # override functions to provide the needed functionality
252 251 # strict was removed in Python 3.4.
253 252 kwargs = {}
254 253 if not pycompat.ispy3:
255 254 kwargs[b'strict'] = self.strict
256 255
257 256 res = self.response_class(self.sock, method=self._method, **kwargs)
258 257
259 258 while True:
260 259 version, status, reason = res._read_status()
261 260 if status != httplib.CONTINUE:
262 261 break
263 262 # skip lines that are all whitespace
264 263 list(iter(lambda: res.fp.readline().strip(), b''))
265 264 res.status = status
266 265 res.reason = reason.strip()
267 266
268 267 if res.status == 200:
269 268 # skip lines until we find a blank line
270 269 list(iter(res.fp.readline, b'\r\n'))
271 270 return True
272 271
273 272 if version == b'HTTP/1.0':
274 273 res.version = 10
275 274 elif version.startswith(b'HTTP/1.'):
276 275 res.version = 11
277 276 elif version == b'HTTP/0.9':
278 277 res.version = 9
279 278 else:
280 279 raise httplib.UnknownProtocol(version)
281 280
282 281 if res.version == 9:
283 282 res.length = None
284 283 res.chunked = 0
285 284 res.will_close = 1
286 285 res.msg = httplib.HTTPMessage(stringio())
287 286 return False
288 287
289 288 res.msg = httplib.HTTPMessage(res.fp)
290 289 res.msg.fp = None
291 290
292 291 # are we using the chunked-style of transfer encoding?
293 292 trenc = res.msg.getheader(b'transfer-encoding')
294 293 if trenc and trenc.lower() == b"chunked":
295 294 res.chunked = 1
296 295 res.chunk_left = None
297 296 else:
298 297 res.chunked = 0
299 298
300 299 # will the connection close at the end of the response?
301 300 res.will_close = res._check_close()
302 301
303 302 # do we have a Content-Length?
304 303 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
305 304 # transfer-encoding is "chunked"
306 305 length = res.msg.getheader(b'content-length')
307 306 if length and not res.chunked:
308 307 try:
309 308 res.length = int(length)
310 309 except ValueError:
311 310 res.length = None
312 311 else:
313 312 if res.length < 0: # ignore nonsensical negative lengths
314 313 res.length = None
315 314 else:
316 315 res.length = None
317 316
318 317 # does the body have a fixed length? (of zero)
319 318 if (
320 319 status == httplib.NO_CONTENT
321 320 or status == httplib.NOT_MODIFIED
322 321 or 100 <= status < 200
323 322 or res._method == b'HEAD' # 1xx codes
324 323 ):
325 324 res.length = 0
326 325
327 326 # if the connection remains open, and we aren't using chunked, and
328 327 # a content-length was not provided, then assume that the connection
329 328 # WILL close.
330 329 if not res.will_close and not res.chunked and res.length is None:
331 330 res.will_close = 1
332 331
333 332 self.proxyres = res
334 333
335 334 return False
336 335
337 336
338 337 class httphandler(keepalive.HTTPHandler):
339 338 def http_open(self, req):
340 339 return self.do_open(httpconnection, req)
341 340
342 341 def _start_transaction(self, h, req):
343 342 _generic_start_transaction(self, h, req)
344 343 return keepalive.HTTPHandler._start_transaction(self, h, req)
345 344
346 345
347 346 class logginghttpconnection(keepalive.HTTPConnection):
348 347 def __init__(self, createconn, *args, **kwargs):
349 348 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
350 349 self._create_connection = createconn
351 350
352 351 if sys.version_info < (2, 7, 7):
353 352 # copied from 2.7.14, since old implementations directly call
354 353 # socket.create_connection()
355 354 def connect(self):
356 355 self.sock = self._create_connection(
357 356 (self.host, self.port), self.timeout, self.source_address
358 357 )
359 358 if self._tunnel_host:
360 359 self._tunnel()
361 360
362 361
363 362 class logginghttphandler(httphandler):
364 363 """HTTP handler that logs socket I/O."""
365 364
366 365 def __init__(self, logfh, name, observeropts, timeout=None):
367 366 super(logginghttphandler, self).__init__(timeout=timeout)
368 367
369 368 self._logfh = logfh
370 369 self._logname = name
371 370 self._observeropts = observeropts
372 371
373 372 # do_open() calls the passed class to instantiate an HTTPConnection. We
374 373 # pass in a callable method that creates a custom HTTPConnection instance
375 374 # whose callback to create the socket knows how to proxy the socket.
376 375 def http_open(self, req):
377 376 return self.do_open(self._makeconnection, req)
378 377
379 378 def _makeconnection(self, *args, **kwargs):
380 379 def createconnection(*args, **kwargs):
381 380 sock = socket.create_connection(*args, **kwargs)
382 381 return util.makeloggingsocket(
383 382 self._logfh, sock, self._logname, **self._observeropts
384 383 )
385 384
386 385 return logginghttpconnection(createconnection, *args, **kwargs)
387 386
388 387
389 388 if has_https:
390 389
391 390 class httpsconnection(keepalive.HTTPConnection):
392 391 response_class = keepalive.HTTPResponse
393 392 default_port = httplib.HTTPS_PORT
394 393 # must be able to send big bundle as stream.
395 394 send = _gen_sendfile(keepalive.safesend)
396 395 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
397 396
398 397 def __init__(
399 398 self,
400 399 host,
401 400 port=None,
402 401 key_file=None,
403 402 cert_file=None,
404 403 *args,
405 404 **kwargs
406 405 ):
407 406 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
408 407 self.key_file = key_file
409 408 self.cert_file = cert_file
410 409
411 410 def connect(self):
412 411 self.sock = socket.create_connection((self.host, self.port))
413 412
414 413 host = self.host
415 414 if self.realhostport: # use CONNECT proxy
416 415 _generic_proxytunnel(self)
417 416 host = self.realhostport.rsplit(b':', 1)[0]
418 417 self.sock = sslutil.wrapsocket(
419 418 self.sock,
420 419 self.key_file,
421 420 self.cert_file,
422 421 ui=self.ui,
423 422 serverhostname=host,
424 423 )
425 424 sslutil.validatesocket(self.sock)
426 425
427 426 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
428 427 def __init__(self, ui, timeout=None):
429 428 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
430 429 urlreq.httpshandler.__init__(self)
431 430 self.ui = ui
432 431 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
433 432
434 433 def _start_transaction(self, h, req):
435 434 _generic_start_transaction(self, h, req)
436 435 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
437 436
438 437 def https_open(self, req):
439 438 # urllibcompat.getfullurl() does not contain credentials
440 439 # and we may need them to match the certificates.
441 440 url = urllibcompat.getfullurl(req)
442 441 user, password = self.pwmgr.find_stored_password(url)
443 442 res = httpconnectionmod.readauthforuri(self.ui, url, user)
444 443 if res:
445 444 group, auth = res
446 445 self.auth = auth
447 446 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
448 447 else:
449 448 self.auth = None
450 449 return self.do_open(self._makeconnection, req)
451 450
452 451 def _makeconnection(self, host, port=None, *args, **kwargs):
453 452 keyfile = None
454 453 certfile = None
455 454
456 455 if len(args) >= 1: # key_file
457 456 keyfile = args[0]
458 457 if len(args) >= 2: # cert_file
459 458 certfile = args[1]
460 459 args = args[2:]
461 460
462 461 # if the user has specified different key/cert files in
463 462 # hgrc, we prefer these
464 463 if self.auth and b'key' in self.auth and b'cert' in self.auth:
465 464 keyfile = self.auth[b'key']
466 465 certfile = self.auth[b'cert']
467 466
468 467 conn = httpsconnection(
469 468 host, port, keyfile, certfile, *args, **kwargs
470 469 )
471 470 conn.ui = self.ui
472 471 return conn
473 472
474 473
475 474 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
476 475 def __init__(self, *args, **kwargs):
477 476 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
478 477 self.retried_req = None
479 478
480 479 def reset_retry_count(self):
481 480 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
482 481 # forever. We disable reset_retry_count completely and reset in
483 482 # http_error_auth_reqed instead.
484 483 pass
485 484
486 485 def http_error_auth_reqed(self, auth_header, host, req, headers):
487 486 # Reset the retry counter once for each request.
488 487 if req is not self.retried_req:
489 488 self.retried_req = req
490 489 self.retried = 0
491 490 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
492 491 self, auth_header, host, req, headers
493 492 )
494 493
495 494
496 495 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
497 496 def __init__(self, *args, **kwargs):
498 497 self.auth = None
499 498 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
500 499 self.retried_req = None
501 500
502 501 def http_request(self, request):
503 502 if self.auth:
504 503 request.add_unredirected_header(self.auth_header, self.auth)
505 504
506 505 return request
507 506
508 507 def https_request(self, request):
509 508 if self.auth:
510 509 request.add_unredirected_header(self.auth_header, self.auth)
511 510
512 511 return request
513 512
514 513 def reset_retry_count(self):
515 514 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
516 515 # forever. We disable reset_retry_count completely and reset in
517 516 # http_error_auth_reqed instead.
518 517 pass
519 518
520 519 def http_error_auth_reqed(self, auth_header, host, req, headers):
521 520 # Reset the retry counter once for each request.
522 521 if req is not self.retried_req:
523 522 self.retried_req = req
524 523 self.retried = 0
525 524 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
526 525 self, auth_header, host, req, headers
527 526 )
528 527
529 528 def retry_http_basic_auth(self, host, req, realm):
530 529 user, pw = self.passwd.find_user_password(
531 530 realm, urllibcompat.getfullurl(req)
532 531 )
533 532 if pw is not None:
534 533 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
535 534 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
536 535 if req.get_header(self.auth_header, None) == auth:
537 536 return None
538 537 self.auth = auth
539 538 req.add_unredirected_header(self.auth_header, auth)
540 539 return self.parent.open(req)
541 540 else:
542 541 return None
543 542
544 543
545 544 class cookiehandler(urlreq.basehandler):
546 545 def __init__(self, ui):
547 546 self.cookiejar = None
548 547
549 548 cookiefile = ui.config(b'auth', b'cookiefile')
550 549 if not cookiefile:
551 550 return
552 551
553 552 cookiefile = util.expandpath(cookiefile)
554 553 try:
555 554 cookiejar = util.cookielib.MozillaCookieJar(
556 555 pycompat.fsdecode(cookiefile)
557 556 )
558 557 cookiejar.load()
559 558 self.cookiejar = cookiejar
560 559 except util.cookielib.LoadError as e:
561 560 ui.warn(
562 561 _(
563 562 b'(error loading cookie file %s: %s; continuing without '
564 563 b'cookies)\n'
565 564 )
566 565 % (cookiefile, stringutil.forcebytestr(e))
567 566 )
568 567
569 568 def http_request(self, request):
570 569 if self.cookiejar:
571 570 self.cookiejar.add_cookie_header(request)
572 571
573 572 return request
574 573
575 574 def https_request(self, request):
576 575 if self.cookiejar:
577 576 self.cookiejar.add_cookie_header(request)
578 577
579 578 return request
580 579
581 580
582 581 handlerfuncs = []
583 582
584 583
585 584 def opener(
586 585 ui,
587 586 authinfo=None,
588 587 useragent=None,
589 588 loggingfh=None,
590 589 loggingname=b's',
591 590 loggingopts=None,
592 591 sendaccept=True,
593 592 ):
594 593 """
595 594 construct an opener suitable for urllib2
596 595 authinfo will be added to the password manager
597 596
598 597 The opener can be configured to log socket events if the various
599 598 ``logging*`` arguments are specified.
600 599
601 600 ``loggingfh`` denotes a file object to log events to.
602 601 ``loggingname`` denotes the name of the to print when logging.
603 602 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
604 603 ``util.socketobserver`` instance.
605 604
606 605 ``sendaccept`` allows controlling whether the ``Accept`` request header
607 606 is sent. The header is sent by default.
608 607 """
609 608 timeout = ui.configwith(float, b'http', b'timeout')
610 609 handlers = []
611 610
612 611 if loggingfh:
613 612 handlers.append(
614 613 logginghttphandler(
615 614 loggingfh, loggingname, loggingopts or {}, timeout=timeout
616 615 )
617 616 )
618 617 # We don't yet support HTTPS when logging I/O. If we attempt to open
619 618 # an HTTPS URL, we'll likely fail due to unknown protocol.
620 619
621 620 else:
622 621 handlers.append(httphandler(timeout=timeout))
623 622 if has_https:
624 623 handlers.append(httpshandler(ui, timeout=timeout))
625 624
626 625 handlers.append(proxyhandler(ui))
627 626
628 627 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
629 628 if authinfo is not None:
630 629 realm, uris, user, passwd = authinfo
631 630 saveduser, savedpass = passmgr.find_stored_password(uris[0])
632 631 if user != saveduser or passwd:
633 632 passmgr.add_password(realm, uris, user, passwd)
634 633 ui.debug(
635 634 b'http auth: user %s, password %s\n'
636 635 % (user, passwd and b'*' * len(passwd) or b'not set')
637 636 )
638 637
639 638 handlers.extend(
640 639 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
641 640 )
642 641 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
643 642 handlers.append(cookiehandler(ui))
644 643 opener = urlreq.buildopener(*handlers)
645 644
646 645 # keepalive.py's handlers will populate these attributes if they exist.
647 646 opener.requestscount = 0
648 647 opener.sentbytescount = 0
649 648 opener.receivedbytescount = 0
650 649
651 650 # The user agent should should *NOT* be used by servers for e.g.
652 651 # protocol detection or feature negotiation: there are other
653 652 # facilities for that.
654 653 #
655 654 # "mercurial/proto-1.0" was the original user agent string and
656 655 # exists for backwards compatibility reasons.
657 656 #
658 657 # The "(Mercurial %s)" string contains the distribution
659 658 # name and version. Other client implementations should choose their
660 659 # own distribution name. Since servers should not be using the user
661 660 # agent string for anything, clients should be able to define whatever
662 661 # user agent they deem appropriate.
663 662 #
664 663 # The custom user agent is for lfs, because unfortunately some servers
665 664 # do look at this value.
666 665 if not useragent:
667 666 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
668 667 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
669 668 else:
670 669 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
671 670
672 671 # This header should only be needed by wire protocol requests. But it has
673 672 # been sent on all requests since forever. We keep sending it for backwards
674 673 # compatibility reasons. Modern versions of the wire protocol use
675 674 # X-HgProto-<N> for advertising client support.
676 675 if sendaccept:
677 676 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
678 677
679 678 return opener
680 679
681 680
682 681 def open(ui, url_, data=None, sendaccept=True):
683 682 u = urlutil.url(url_)
684 683 if u.scheme:
685 684 u.scheme = u.scheme.lower()
686 685 url_, authinfo = u.authinfo()
687 686 else:
688 path = util.normpath(os.path.abspath(url_))
687 path = util.normpath(util.abspath(url_))
689 688 url_ = b'file://' + pycompat.bytesurl(
690 689 urlreq.pathname2url(pycompat.fsdecode(path))
691 690 )
692 691 authinfo = None
693 692 return opener(ui, authinfo, sendaccept=sendaccept).open(
694 693 pycompat.strurl(url_), data
695 694 )
696 695
697 696
698 697 def wrapresponse(resp):
699 698 """Wrap a response object with common error handlers.
700 699
701 700 This ensures that any I/O from any consumer raises the appropriate
702 701 error and messaging.
703 702 """
704 703 origread = resp.read
705 704
706 705 class readerproxy(resp.__class__):
707 706 def read(self, size=None):
708 707 try:
709 708 return origread(size)
710 709 except httplib.IncompleteRead as e:
711 710 # e.expected is an integer if length known or None otherwise.
712 711 if e.expected:
713 712 got = len(e.partial)
714 713 total = e.expected + got
715 714 msg = _(
716 715 b'HTTP request error (incomplete response; '
717 716 b'expected %d bytes got %d)'
718 717 ) % (total, got)
719 718 else:
720 719 msg = _(b'HTTP request error (incomplete response)')
721 720
722 721 raise error.PeerTransportError(
723 722 msg,
724 723 hint=_(
725 724 b'this may be an intermittent network failure; '
726 725 b'if the error persists, consider contacting the '
727 726 b'network or server operator'
728 727 ),
729 728 )
730 729 except httplib.HTTPException as e:
731 730 raise error.PeerTransportError(
732 731 _(b'HTTP request error (%s)') % e,
733 732 hint=_(
734 733 b'this may be an intermittent network failure; '
735 734 b'if the error persists, consider contacting the '
736 735 b'network or server operator'
737 736 ),
738 737 )
739 738
740 739 resp.__class__ = readerproxy
General Comments 0
You need to be logged in to leave comments. Login now