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