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