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