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