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