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