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