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