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