##// END OF EJS Templates
py3: use bytes() to byte-stringify url object in url.py...
Yuya Nishihara -
r36326:54831656 default
parent child Browse files
Show More
@@ -1,539 +1,539 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
16 16 from .i18n import _
17 17 from . import (
18 18 encoding,
19 19 error,
20 20 httpconnection as httpconnectionmod,
21 21 keepalive,
22 22 pycompat,
23 23 sslutil,
24 24 urllibcompat,
25 25 util,
26 26 )
27 27
28 28 httplib = util.httplib
29 29 stringio = util.stringio
30 30 urlerr = util.urlerr
31 31 urlreq = util.urlreq
32 32
33 33 def escape(s, quote=None):
34 34 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
35 35 If the optional flag quote is true, the quotation mark character (")
36 36 is also translated.
37 37
38 38 This is the same as cgi.escape in Python, but always operates on
39 39 bytes, whereas cgi.escape in Python 3 only works on unicodes.
40 40 '''
41 41 s = s.replace(b"&", b"&amp;")
42 42 s = s.replace(b"<", b"&lt;")
43 43 s = s.replace(b">", b"&gt;")
44 44 if quote:
45 45 s = s.replace(b'"', b"&quot;")
46 46 return s
47 47
48 48 class passwordmgr(object):
49 49 def __init__(self, ui, passwddb):
50 50 self.ui = ui
51 51 self.passwddb = passwddb
52 52
53 53 def add_password(self, realm, uri, user, passwd):
54 54 return self.passwddb.add_password(realm, uri, user, passwd)
55 55
56 56 def find_user_password(self, realm, authuri):
57 57 authinfo = self.passwddb.find_user_password(realm, authuri)
58 58 user, passwd = authinfo
59 59 if user and passwd:
60 60 self._writedebug(user, passwd)
61 61 return (user, passwd)
62 62
63 63 if not user or not passwd:
64 64 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
65 65 if res:
66 66 group, auth = res
67 67 user, passwd = auth.get('username'), auth.get('password')
68 68 self.ui.debug("using auth.%s.* for authentication\n" % group)
69 69 if not user or not passwd:
70 70 u = util.url(authuri)
71 71 u.query = None
72 72 if not self.ui.interactive():
73 73 raise error.Abort(_('http authorization required for %s') %
74 util.hidepassword(str(u)))
74 util.hidepassword(bytes(u)))
75 75
76 76 self.ui.write(_("http authorization required for %s\n") %
77 util.hidepassword(str(u)))
77 util.hidepassword(bytes(u)))
78 78 self.ui.write(_("realm: %s\n") % realm)
79 79 if user:
80 80 self.ui.write(_("user: %s\n") % user)
81 81 else:
82 82 user = self.ui.prompt(_("user:"), default=None)
83 83
84 84 if not passwd:
85 85 passwd = self.ui.getpass()
86 86
87 87 self.passwddb.add_password(realm, authuri, user, passwd)
88 88 self._writedebug(user, passwd)
89 89 return (user, passwd)
90 90
91 91 def _writedebug(self, user, passwd):
92 92 msg = _('http auth: user %s, password %s\n')
93 93 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
94 94
95 95 def find_stored_password(self, authuri):
96 96 return self.passwddb.find_user_password(None, authuri)
97 97
98 98 class proxyhandler(urlreq.proxyhandler):
99 99 def __init__(self, ui):
100 100 proxyurl = (ui.config("http_proxy", "host") or
101 101 encoding.environ.get('http_proxy'))
102 102 # XXX proxyauthinfo = None
103 103
104 104 if proxyurl:
105 105 # proxy can be proper url or host[:port]
106 106 if not (proxyurl.startswith('http:') or
107 107 proxyurl.startswith('https:')):
108 108 proxyurl = 'http://' + proxyurl + '/'
109 109 proxy = util.url(proxyurl)
110 110 if not proxy.user:
111 111 proxy.user = ui.config("http_proxy", "user")
112 112 proxy.passwd = ui.config("http_proxy", "passwd")
113 113
114 114 # see if we should use a proxy for this url
115 115 no_list = ["localhost", "127.0.0.1"]
116 116 no_list.extend([p.lower() for
117 117 p in ui.configlist("http_proxy", "no")])
118 118 no_list.extend([p.strip().lower() for
119 119 p in encoding.environ.get("no_proxy", '').split(',')
120 120 if p.strip()])
121 121 # "http_proxy.always" config is for running tests on localhost
122 122 if ui.configbool("http_proxy", "always"):
123 123 self.no_list = []
124 124 else:
125 125 self.no_list = no_list
126 126
127 proxyurl = str(proxy)
127 proxyurl = bytes(proxy)
128 128 proxies = {'http': proxyurl, 'https': proxyurl}
129 129 ui.debug('proxying through %s\n' % util.hidepassword(proxyurl))
130 130 else:
131 131 proxies = {}
132 132
133 133 urlreq.proxyhandler.__init__(self, proxies)
134 134 self.ui = ui
135 135
136 136 def proxy_open(self, req, proxy, type_):
137 137 host = urllibcompat.gethost(req).split(':')[0]
138 138 for e in self.no_list:
139 139 if host == e:
140 140 return None
141 141 if e.startswith('*.') and host.endswith(e[2:]):
142 142 return None
143 143 if e.startswith('.') and host.endswith(e[1:]):
144 144 return None
145 145
146 146 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
147 147
148 148 def _gen_sendfile(orgsend):
149 149 def _sendfile(self, data):
150 150 # send a file
151 151 if isinstance(data, httpconnectionmod.httpsendfile):
152 152 # if auth required, some data sent twice, so rewind here
153 153 data.seek(0)
154 154 for chunk in util.filechunkiter(data):
155 155 orgsend(self, chunk)
156 156 else:
157 157 orgsend(self, data)
158 158 return _sendfile
159 159
160 160 has_https = util.safehasattr(urlreq, 'httpshandler')
161 161
162 162 class httpconnection(keepalive.HTTPConnection):
163 163 # must be able to send big bundle as stream.
164 164 send = _gen_sendfile(keepalive.HTTPConnection.send)
165 165
166 166 def getresponse(self):
167 167 proxyres = getattr(self, 'proxyres', None)
168 168 if proxyres:
169 169 if proxyres.will_close:
170 170 self.close()
171 171 self.proxyres = None
172 172 return proxyres
173 173 return keepalive.HTTPConnection.getresponse(self)
174 174
175 175 # general transaction handler to support different ways to handle
176 176 # HTTPS proxying before and after Python 2.6.3.
177 177 def _generic_start_transaction(handler, h, req):
178 178 tunnel_host = getattr(req, '_tunnel_host', None)
179 179 if tunnel_host:
180 180 if tunnel_host[:7] not in ['http://', 'https:/']:
181 181 tunnel_host = 'https://' + tunnel_host
182 182 new_tunnel = True
183 183 else:
184 184 tunnel_host = urllibcompat.getselector(req)
185 185 new_tunnel = False
186 186
187 187 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
188 188 u = util.url(tunnel_host)
189 189 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
190 190 h.realhostport = ':'.join([u.host, (u.port or '443')])
191 191 h.headers = req.headers.copy()
192 192 h.headers.update(handler.parent.addheaders)
193 193 return
194 194
195 195 h.realhostport = None
196 196 h.headers = None
197 197
198 198 def _generic_proxytunnel(self):
199 199 proxyheaders = dict(
200 200 [(x, self.headers[x]) for x in self.headers
201 201 if x.lower().startswith('proxy-')])
202 202 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
203 203 for header in proxyheaders.iteritems():
204 204 self.send('%s: %s\r\n' % header)
205 205 self.send('\r\n')
206 206
207 207 # majority of the following code is duplicated from
208 208 # httplib.HTTPConnection as there are no adequate places to
209 209 # override functions to provide the needed functionality
210 210 res = self.response_class(self.sock,
211 211 strict=self.strict,
212 212 method=self._method)
213 213
214 214 while True:
215 215 version, status, reason = res._read_status()
216 216 if status != httplib.CONTINUE:
217 217 break
218 218 # skip lines that are all whitespace
219 219 list(iter(lambda: res.fp.readline().strip(), ''))
220 220 res.status = status
221 221 res.reason = reason.strip()
222 222
223 223 if res.status == 200:
224 224 # skip lines until we find a blank line
225 225 list(iter(res.fp.readline, '\r\n'))
226 226 return True
227 227
228 228 if version == 'HTTP/1.0':
229 229 res.version = 10
230 230 elif version.startswith('HTTP/1.'):
231 231 res.version = 11
232 232 elif version == 'HTTP/0.9':
233 233 res.version = 9
234 234 else:
235 235 raise httplib.UnknownProtocol(version)
236 236
237 237 if res.version == 9:
238 238 res.length = None
239 239 res.chunked = 0
240 240 res.will_close = 1
241 241 res.msg = httplib.HTTPMessage(stringio())
242 242 return False
243 243
244 244 res.msg = httplib.HTTPMessage(res.fp)
245 245 res.msg.fp = None
246 246
247 247 # are we using the chunked-style of transfer encoding?
248 248 trenc = res.msg.getheader('transfer-encoding')
249 249 if trenc and trenc.lower() == "chunked":
250 250 res.chunked = 1
251 251 res.chunk_left = None
252 252 else:
253 253 res.chunked = 0
254 254
255 255 # will the connection close at the end of the response?
256 256 res.will_close = res._check_close()
257 257
258 258 # do we have a Content-Length?
259 259 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
260 260 # transfer-encoding is "chunked"
261 261 length = res.msg.getheader('content-length')
262 262 if length and not res.chunked:
263 263 try:
264 264 res.length = int(length)
265 265 except ValueError:
266 266 res.length = None
267 267 else:
268 268 if res.length < 0: # ignore nonsensical negative lengths
269 269 res.length = None
270 270 else:
271 271 res.length = None
272 272
273 273 # does the body have a fixed length? (of zero)
274 274 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
275 275 100 <= status < 200 or # 1xx codes
276 276 res._method == 'HEAD'):
277 277 res.length = 0
278 278
279 279 # if the connection remains open, and we aren't using chunked, and
280 280 # a content-length was not provided, then assume that the connection
281 281 # WILL close.
282 282 if (not res.will_close and
283 283 not res.chunked and
284 284 res.length is None):
285 285 res.will_close = 1
286 286
287 287 self.proxyres = res
288 288
289 289 return False
290 290
291 291 class httphandler(keepalive.HTTPHandler):
292 292 def http_open(self, req):
293 293 return self.do_open(httpconnection, req)
294 294
295 295 def _start_transaction(self, h, req):
296 296 _generic_start_transaction(self, h, req)
297 297 return keepalive.HTTPHandler._start_transaction(self, h, req)
298 298
299 299 if has_https:
300 300 class httpsconnection(httplib.HTTPConnection):
301 301 response_class = keepalive.HTTPResponse
302 302 default_port = httplib.HTTPS_PORT
303 303 # must be able to send big bundle as stream.
304 304 send = _gen_sendfile(keepalive.safesend)
305 305 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
306 306
307 307 def __init__(self, host, port=None, key_file=None, cert_file=None,
308 308 *args, **kwargs):
309 309 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
310 310 self.key_file = key_file
311 311 self.cert_file = cert_file
312 312
313 313 def connect(self):
314 314 self.sock = socket.create_connection((self.host, self.port))
315 315
316 316 host = self.host
317 317 if self.realhostport: # use CONNECT proxy
318 318 _generic_proxytunnel(self)
319 319 host = self.realhostport.rsplit(':', 1)[0]
320 320 self.sock = sslutil.wrapsocket(
321 321 self.sock, self.key_file, self.cert_file, ui=self.ui,
322 322 serverhostname=host)
323 323 sslutil.validatesocket(self.sock)
324 324
325 325 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
326 326 def __init__(self, ui):
327 327 keepalive.KeepAliveHandler.__init__(self)
328 328 urlreq.httpshandler.__init__(self)
329 329 self.ui = ui
330 330 self.pwmgr = passwordmgr(self.ui,
331 331 self.ui.httppasswordmgrdb)
332 332
333 333 def _start_transaction(self, h, req):
334 334 _generic_start_transaction(self, h, req)
335 335 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
336 336
337 337 def https_open(self, req):
338 338 # urllibcompat.getfullurl() does not contain credentials
339 339 # and we may need them to match the certificates.
340 340 url = urllibcompat.getfullurl(req)
341 341 user, password = self.pwmgr.find_stored_password(url)
342 342 res = httpconnectionmod.readauthforuri(self.ui, url, user)
343 343 if res:
344 344 group, auth = res
345 345 self.auth = auth
346 346 self.ui.debug("using auth.%s.* for authentication\n" % group)
347 347 else:
348 348 self.auth = None
349 349 return self.do_open(self._makeconnection, req)
350 350
351 351 def _makeconnection(self, host, port=None, *args, **kwargs):
352 352 keyfile = None
353 353 certfile = None
354 354
355 355 if len(args) >= 1: # key_file
356 356 keyfile = args[0]
357 357 if len(args) >= 2: # cert_file
358 358 certfile = args[1]
359 359 args = args[2:]
360 360
361 361 # if the user has specified different key/cert files in
362 362 # hgrc, we prefer these
363 363 if self.auth and 'key' in self.auth and 'cert' in self.auth:
364 364 keyfile = self.auth['key']
365 365 certfile = self.auth['cert']
366 366
367 367 conn = httpsconnection(host, port, keyfile, certfile, *args,
368 368 **kwargs)
369 369 conn.ui = self.ui
370 370 return conn
371 371
372 372 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
373 373 def __init__(self, *args, **kwargs):
374 374 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
375 375 self.retried_req = None
376 376
377 377 def reset_retry_count(self):
378 378 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
379 379 # forever. We disable reset_retry_count completely and reset in
380 380 # http_error_auth_reqed instead.
381 381 pass
382 382
383 383 def http_error_auth_reqed(self, auth_header, host, req, headers):
384 384 # Reset the retry counter once for each request.
385 385 if req is not self.retried_req:
386 386 self.retried_req = req
387 387 self.retried = 0
388 388 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
389 389 self, auth_header, host, req, headers)
390 390
391 391 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
392 392 def __init__(self, *args, **kwargs):
393 393 self.auth = None
394 394 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
395 395 self.retried_req = None
396 396
397 397 def http_request(self, request):
398 398 if self.auth:
399 399 request.add_unredirected_header(self.auth_header, self.auth)
400 400
401 401 return request
402 402
403 403 def https_request(self, request):
404 404 if self.auth:
405 405 request.add_unredirected_header(self.auth_header, self.auth)
406 406
407 407 return request
408 408
409 409 def reset_retry_count(self):
410 410 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
411 411 # forever. We disable reset_retry_count completely and reset in
412 412 # http_error_auth_reqed instead.
413 413 pass
414 414
415 415 def http_error_auth_reqed(self, auth_header, host, req, headers):
416 416 # Reset the retry counter once for each request.
417 417 if req is not self.retried_req:
418 418 self.retried_req = req
419 419 self.retried = 0
420 420 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
421 421 self, auth_header, host, req, headers)
422 422
423 423 def retry_http_basic_auth(self, host, req, realm):
424 424 user, pw = self.passwd.find_user_password(
425 425 realm, urllibcompat.getfullurl(req))
426 426 if pw is not None:
427 427 raw = "%s:%s" % (user, pw)
428 428 auth = 'Basic %s' % base64.b64encode(raw).strip()
429 429 if req.get_header(self.auth_header, None) == auth:
430 430 return None
431 431 self.auth = auth
432 432 req.add_unredirected_header(self.auth_header, auth)
433 433 return self.parent.open(req)
434 434 else:
435 435 return None
436 436
437 437 class cookiehandler(urlreq.basehandler):
438 438 def __init__(self, ui):
439 439 self.cookiejar = None
440 440
441 441 cookiefile = ui.config('auth', 'cookiefile')
442 442 if not cookiefile:
443 443 return
444 444
445 445 cookiefile = util.expandpath(cookiefile)
446 446 try:
447 447 cookiejar = util.cookielib.MozillaCookieJar(cookiefile)
448 448 cookiejar.load()
449 449 self.cookiejar = cookiejar
450 450 except util.cookielib.LoadError as e:
451 451 ui.warn(_('(error loading cookie file %s: %s; continuing without '
452 452 'cookies)\n') % (cookiefile, str(e)))
453 453
454 454 def http_request(self, request):
455 455 if self.cookiejar:
456 456 self.cookiejar.add_cookie_header(request)
457 457
458 458 return request
459 459
460 460 def https_request(self, request):
461 461 if self.cookiejar:
462 462 self.cookiejar.add_cookie_header(request)
463 463
464 464 return request
465 465
466 466 handlerfuncs = []
467 467
468 468 def opener(ui, authinfo=None, useragent=None):
469 469 '''
470 470 construct an opener suitable for urllib2
471 471 authinfo will be added to the password manager
472 472 '''
473 473 # experimental config: ui.usehttp2
474 474 if ui.configbool('ui', 'usehttp2'):
475 475 handlers = [
476 476 httpconnectionmod.http2handler(
477 477 ui,
478 478 passwordmgr(ui, ui.httppasswordmgrdb))
479 479 ]
480 480 else:
481 481 handlers = [httphandler()]
482 482 if has_https:
483 483 handlers.append(httpshandler(ui))
484 484
485 485 handlers.append(proxyhandler(ui))
486 486
487 487 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
488 488 if authinfo is not None:
489 489 realm, uris, user, passwd = authinfo
490 490 saveduser, savedpass = passmgr.find_stored_password(uris[0])
491 491 if user != saveduser or passwd:
492 492 passmgr.add_password(realm, uris, user, passwd)
493 493 ui.debug('http auth: user %s, password %s\n' %
494 494 (user, passwd and '*' * len(passwd) or 'not set'))
495 495
496 496 handlers.extend((httpbasicauthhandler(passmgr),
497 497 httpdigestauthhandler(passmgr)))
498 498 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
499 499 handlers.append(cookiehandler(ui))
500 500 opener = urlreq.buildopener(*handlers)
501 501
502 502 # The user agent should should *NOT* be used by servers for e.g.
503 503 # protocol detection or feature negotiation: there are other
504 504 # facilities for that.
505 505 #
506 506 # "mercurial/proto-1.0" was the original user agent string and
507 507 # exists for backwards compatibility reasons.
508 508 #
509 509 # The "(Mercurial %s)" string contains the distribution
510 510 # name and version. Other client implementations should choose their
511 511 # own distribution name. Since servers should not be using the user
512 512 # agent string for anything, clients should be able to define whatever
513 513 # user agent they deem appropriate.
514 514 #
515 515 # The custom user agent is for lfs, because unfortunately some servers
516 516 # do look at this value.
517 517 if not useragent:
518 518 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
519 519 opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
520 520 else:
521 521 opener.addheaders = [(r'User-agent', pycompat.sysstr(useragent))]
522 522
523 523 # This header should only be needed by wire protocol requests. But it has
524 524 # been sent on all requests since forever. We keep sending it for backwards
525 525 # compatibility reasons. Modern versions of the wire protocol use
526 526 # X-HgProto-<N> for advertising client support.
527 527 opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
528 528 return opener
529 529
530 530 def open(ui, url_, data=None):
531 531 u = util.url(url_)
532 532 if u.scheme:
533 533 u.scheme = u.scheme.lower()
534 534 url_, authinfo = u.authinfo()
535 535 else:
536 536 path = util.normpath(os.path.abspath(url_))
537 537 url_ = 'file://' + urlreq.pathname2url(path)
538 538 authinfo = None
539 539 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now