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