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