##// END OF EJS Templates
url: use `iter(callable, sentinel)` instead of while True...
Augie Fackler -
r29729:44ea1275 default
parent child Browse files
Show More
@@ -1,494 +1,490 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
154
155 class httpconnection(keepalive.HTTPConnection):
155 class httpconnection(keepalive.HTTPConnection):
156 # must be able to send big bundle as stream.
156 # must be able to send big bundle as stream.
157 send = _gen_sendfile(keepalive.HTTPConnection.send)
157 send = _gen_sendfile(keepalive.HTTPConnection.send)
158
158
159 def getresponse(self):
159 def getresponse(self):
160 proxyres = getattr(self, 'proxyres', None)
160 proxyres = getattr(self, 'proxyres', None)
161 if proxyres:
161 if proxyres:
162 if proxyres.will_close:
162 if proxyres.will_close:
163 self.close()
163 self.close()
164 self.proxyres = None
164 self.proxyres = None
165 return proxyres
165 return proxyres
166 return keepalive.HTTPConnection.getresponse(self)
166 return keepalive.HTTPConnection.getresponse(self)
167
167
168 # general transaction handler to support different ways to handle
168 # general transaction handler to support different ways to handle
169 # HTTPS proxying before and after Python 2.6.3.
169 # HTTPS proxying before and after Python 2.6.3.
170 def _generic_start_transaction(handler, h, req):
170 def _generic_start_transaction(handler, h, req):
171 tunnel_host = getattr(req, '_tunnel_host', None)
171 tunnel_host = getattr(req, '_tunnel_host', None)
172 if tunnel_host:
172 if tunnel_host:
173 if tunnel_host[:7] not in ['http://', 'https:/']:
173 if tunnel_host[:7] not in ['http://', 'https:/']:
174 tunnel_host = 'https://' + tunnel_host
174 tunnel_host = 'https://' + tunnel_host
175 new_tunnel = True
175 new_tunnel = True
176 else:
176 else:
177 tunnel_host = req.get_selector()
177 tunnel_host = req.get_selector()
178 new_tunnel = False
178 new_tunnel = False
179
179
180 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
180 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
181 u = util.url(tunnel_host)
181 u = util.url(tunnel_host)
182 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
182 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
183 h.realhostport = ':'.join([u.host, (u.port or '443')])
183 h.realhostport = ':'.join([u.host, (u.port or '443')])
184 h.headers = req.headers.copy()
184 h.headers = req.headers.copy()
185 h.headers.update(handler.parent.addheaders)
185 h.headers.update(handler.parent.addheaders)
186 return
186 return
187
187
188 h.realhostport = None
188 h.realhostport = None
189 h.headers = None
189 h.headers = None
190
190
191 def _generic_proxytunnel(self):
191 def _generic_proxytunnel(self):
192 proxyheaders = dict(
192 proxyheaders = dict(
193 [(x, self.headers[x]) for x in self.headers
193 [(x, self.headers[x]) for x in self.headers
194 if x.lower().startswith('proxy-')])
194 if x.lower().startswith('proxy-')])
195 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
195 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
196 for header in proxyheaders.iteritems():
196 for header in proxyheaders.iteritems():
197 self.send('%s: %s\r\n' % header)
197 self.send('%s: %s\r\n' % header)
198 self.send('\r\n')
198 self.send('\r\n')
199
199
200 # majority of the following code is duplicated from
200 # majority of the following code is duplicated from
201 # httplib.HTTPConnection as there are no adequate places to
201 # httplib.HTTPConnection as there are no adequate places to
202 # override functions to provide the needed functionality
202 # override functions to provide the needed functionality
203 res = self.response_class(self.sock,
203 res = self.response_class(self.sock,
204 strict=self.strict,
204 strict=self.strict,
205 method=self._method)
205 method=self._method)
206
206
207 while True:
207 while True:
208 version, status, reason = res._read_status()
208 version, status, reason = res._read_status()
209 if status != httplib.CONTINUE:
209 if status != httplib.CONTINUE:
210 break
210 break
211 while True:
211 # skip lines that are all whitespace
212 skip = res.fp.readline().strip()
212 list(iter(lambda: res.fp.readline().strip(), ''))
213 if not skip:
214 break
215 res.status = status
213 res.status = status
216 res.reason = reason.strip()
214 res.reason = reason.strip()
217
215
218 if res.status == 200:
216 if res.status == 200:
219 while True:
217 # skip lines until we find a blank line
220 line = res.fp.readline()
218 list(iter(res.fp.readline, '\r\n'))
221 if line == '\r\n':
222 break
223 return True
219 return True
224
220
225 if version == 'HTTP/1.0':
221 if version == 'HTTP/1.0':
226 res.version = 10
222 res.version = 10
227 elif version.startswith('HTTP/1.'):
223 elif version.startswith('HTTP/1.'):
228 res.version = 11
224 res.version = 11
229 elif version == 'HTTP/0.9':
225 elif version == 'HTTP/0.9':
230 res.version = 9
226 res.version = 9
231 else:
227 else:
232 raise httplib.UnknownProtocol(version)
228 raise httplib.UnknownProtocol(version)
233
229
234 if res.version == 9:
230 if res.version == 9:
235 res.length = None
231 res.length = None
236 res.chunked = 0
232 res.chunked = 0
237 res.will_close = 1
233 res.will_close = 1
238 res.msg = httplib.HTTPMessage(stringio())
234 res.msg = httplib.HTTPMessage(stringio())
239 return False
235 return False
240
236
241 res.msg = httplib.HTTPMessage(res.fp)
237 res.msg = httplib.HTTPMessage(res.fp)
242 res.msg.fp = None
238 res.msg.fp = None
243
239
244 # are we using the chunked-style of transfer encoding?
240 # are we using the chunked-style of transfer encoding?
245 trenc = res.msg.getheader('transfer-encoding')
241 trenc = res.msg.getheader('transfer-encoding')
246 if trenc and trenc.lower() == "chunked":
242 if trenc and trenc.lower() == "chunked":
247 res.chunked = 1
243 res.chunked = 1
248 res.chunk_left = None
244 res.chunk_left = None
249 else:
245 else:
250 res.chunked = 0
246 res.chunked = 0
251
247
252 # will the connection close at the end of the response?
248 # will the connection close at the end of the response?
253 res.will_close = res._check_close()
249 res.will_close = res._check_close()
254
250
255 # do we have a Content-Length?
251 # do we have a Content-Length?
256 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
252 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
257 # transfer-encoding is "chunked"
253 # transfer-encoding is "chunked"
258 length = res.msg.getheader('content-length')
254 length = res.msg.getheader('content-length')
259 if length and not res.chunked:
255 if length and not res.chunked:
260 try:
256 try:
261 res.length = int(length)
257 res.length = int(length)
262 except ValueError:
258 except ValueError:
263 res.length = None
259 res.length = None
264 else:
260 else:
265 if res.length < 0: # ignore nonsensical negative lengths
261 if res.length < 0: # ignore nonsensical negative lengths
266 res.length = None
262 res.length = None
267 else:
263 else:
268 res.length = None
264 res.length = None
269
265
270 # does the body have a fixed length? (of zero)
266 # does the body have a fixed length? (of zero)
271 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
267 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
272 100 <= status < 200 or # 1xx codes
268 100 <= status < 200 or # 1xx codes
273 res._method == 'HEAD'):
269 res._method == 'HEAD'):
274 res.length = 0
270 res.length = 0
275
271
276 # if the connection remains open, and we aren't using chunked, and
272 # if the connection remains open, and we aren't using chunked, and
277 # a content-length was not provided, then assume that the connection
273 # a content-length was not provided, then assume that the connection
278 # WILL close.
274 # WILL close.
279 if (not res.will_close and
275 if (not res.will_close and
280 not res.chunked and
276 not res.chunked and
281 res.length is None):
277 res.length is None):
282 res.will_close = 1
278 res.will_close = 1
283
279
284 self.proxyres = res
280 self.proxyres = res
285
281
286 return False
282 return False
287
283
288 class httphandler(keepalive.HTTPHandler):
284 class httphandler(keepalive.HTTPHandler):
289 def http_open(self, req):
285 def http_open(self, req):
290 return self.do_open(httpconnection, req)
286 return self.do_open(httpconnection, req)
291
287
292 def _start_transaction(self, h, req):
288 def _start_transaction(self, h, req):
293 _generic_start_transaction(self, h, req)
289 _generic_start_transaction(self, h, req)
294 return keepalive.HTTPHandler._start_transaction(self, h, req)
290 return keepalive.HTTPHandler._start_transaction(self, h, req)
295
291
296 if has_https:
292 if has_https:
297 class httpsconnection(httplib.HTTPConnection):
293 class httpsconnection(httplib.HTTPConnection):
298 response_class = keepalive.HTTPResponse
294 response_class = keepalive.HTTPResponse
299 default_port = httplib.HTTPS_PORT
295 default_port = httplib.HTTPS_PORT
300 # must be able to send big bundle as stream.
296 # must be able to send big bundle as stream.
301 send = _gen_sendfile(keepalive.safesend)
297 send = _gen_sendfile(keepalive.safesend)
302 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
298 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
303
299
304 def __init__(self, host, port=None, key_file=None, cert_file=None,
300 def __init__(self, host, port=None, key_file=None, cert_file=None,
305 *args, **kwargs):
301 *args, **kwargs):
306 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
302 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
307 self.key_file = key_file
303 self.key_file = key_file
308 self.cert_file = cert_file
304 self.cert_file = cert_file
309
305
310 def connect(self):
306 def connect(self):
311 self.sock = socket.create_connection((self.host, self.port))
307 self.sock = socket.create_connection((self.host, self.port))
312
308
313 host = self.host
309 host = self.host
314 if self.realhostport: # use CONNECT proxy
310 if self.realhostport: # use CONNECT proxy
315 _generic_proxytunnel(self)
311 _generic_proxytunnel(self)
316 host = self.realhostport.rsplit(':', 1)[0]
312 host = self.realhostport.rsplit(':', 1)[0]
317 self.sock = sslutil.wrapsocket(
313 self.sock = sslutil.wrapsocket(
318 self.sock, self.key_file, self.cert_file, ui=self.ui,
314 self.sock, self.key_file, self.cert_file, ui=self.ui,
319 serverhostname=host)
315 serverhostname=host)
320 sslutil.validatesocket(self.sock)
316 sslutil.validatesocket(self.sock)
321
317
322 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
318 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
323 def __init__(self, ui):
319 def __init__(self, ui):
324 keepalive.KeepAliveHandler.__init__(self)
320 keepalive.KeepAliveHandler.__init__(self)
325 urlreq.httpshandler.__init__(self)
321 urlreq.httpshandler.__init__(self)
326 self.ui = ui
322 self.ui = ui
327 self.pwmgr = passwordmgr(self.ui,
323 self.pwmgr = passwordmgr(self.ui,
328 self.ui.httppasswordmgrdb)
324 self.ui.httppasswordmgrdb)
329
325
330 def _start_transaction(self, h, req):
326 def _start_transaction(self, h, req):
331 _generic_start_transaction(self, h, req)
327 _generic_start_transaction(self, h, req)
332 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
328 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
333
329
334 def https_open(self, req):
330 def https_open(self, req):
335 # req.get_full_url() does not contain credentials and we may
331 # req.get_full_url() does not contain credentials and we may
336 # need them to match the certificates.
332 # need them to match the certificates.
337 url = req.get_full_url()
333 url = req.get_full_url()
338 user, password = self.pwmgr.find_stored_password(url)
334 user, password = self.pwmgr.find_stored_password(url)
339 res = httpconnectionmod.readauthforuri(self.ui, url, user)
335 res = httpconnectionmod.readauthforuri(self.ui, url, user)
340 if res:
336 if res:
341 group, auth = res
337 group, auth = res
342 self.auth = auth
338 self.auth = auth
343 self.ui.debug("using auth.%s.* for authentication\n" % group)
339 self.ui.debug("using auth.%s.* for authentication\n" % group)
344 else:
340 else:
345 self.auth = None
341 self.auth = None
346 return self.do_open(self._makeconnection, req)
342 return self.do_open(self._makeconnection, req)
347
343
348 def _makeconnection(self, host, port=None, *args, **kwargs):
344 def _makeconnection(self, host, port=None, *args, **kwargs):
349 keyfile = None
345 keyfile = None
350 certfile = None
346 certfile = None
351
347
352 if len(args) >= 1: # key_file
348 if len(args) >= 1: # key_file
353 keyfile = args[0]
349 keyfile = args[0]
354 if len(args) >= 2: # cert_file
350 if len(args) >= 2: # cert_file
355 certfile = args[1]
351 certfile = args[1]
356 args = args[2:]
352 args = args[2:]
357
353
358 # if the user has specified different key/cert files in
354 # if the user has specified different key/cert files in
359 # hgrc, we prefer these
355 # hgrc, we prefer these
360 if self.auth and 'key' in self.auth and 'cert' in self.auth:
356 if self.auth and 'key' in self.auth and 'cert' in self.auth:
361 keyfile = self.auth['key']
357 keyfile = self.auth['key']
362 certfile = self.auth['cert']
358 certfile = self.auth['cert']
363
359
364 conn = httpsconnection(host, port, keyfile, certfile, *args,
360 conn = httpsconnection(host, port, keyfile, certfile, *args,
365 **kwargs)
361 **kwargs)
366 conn.ui = self.ui
362 conn.ui = self.ui
367 return conn
363 return conn
368
364
369 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
365 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
370 def __init__(self, *args, **kwargs):
366 def __init__(self, *args, **kwargs):
371 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
367 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
372 self.retried_req = None
368 self.retried_req = None
373
369
374 def reset_retry_count(self):
370 def reset_retry_count(self):
375 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
371 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
376 # forever. We disable reset_retry_count completely and reset in
372 # forever. We disable reset_retry_count completely and reset in
377 # http_error_auth_reqed instead.
373 # http_error_auth_reqed instead.
378 pass
374 pass
379
375
380 def http_error_auth_reqed(self, auth_header, host, req, headers):
376 def http_error_auth_reqed(self, auth_header, host, req, headers):
381 # Reset the retry counter once for each request.
377 # Reset the retry counter once for each request.
382 if req is not self.retried_req:
378 if req is not self.retried_req:
383 self.retried_req = req
379 self.retried_req = req
384 self.retried = 0
380 self.retried = 0
385 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
381 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
386 self, auth_header, host, req, headers)
382 self, auth_header, host, req, headers)
387
383
388 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
384 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
389 def __init__(self, *args, **kwargs):
385 def __init__(self, *args, **kwargs):
390 self.auth = None
386 self.auth = None
391 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
387 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
392 self.retried_req = None
388 self.retried_req = None
393
389
394 def http_request(self, request):
390 def http_request(self, request):
395 if self.auth:
391 if self.auth:
396 request.add_unredirected_header(self.auth_header, self.auth)
392 request.add_unredirected_header(self.auth_header, self.auth)
397
393
398 return request
394 return request
399
395
400 def https_request(self, request):
396 def https_request(self, request):
401 if self.auth:
397 if self.auth:
402 request.add_unredirected_header(self.auth_header, self.auth)
398 request.add_unredirected_header(self.auth_header, self.auth)
403
399
404 return request
400 return request
405
401
406 def reset_retry_count(self):
402 def reset_retry_count(self):
407 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
403 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
408 # forever. We disable reset_retry_count completely and reset in
404 # forever. We disable reset_retry_count completely and reset in
409 # http_error_auth_reqed instead.
405 # http_error_auth_reqed instead.
410 pass
406 pass
411
407
412 def http_error_auth_reqed(self, auth_header, host, req, headers):
408 def http_error_auth_reqed(self, auth_header, host, req, headers):
413 # Reset the retry counter once for each request.
409 # Reset the retry counter once for each request.
414 if req is not self.retried_req:
410 if req is not self.retried_req:
415 self.retried_req = req
411 self.retried_req = req
416 self.retried = 0
412 self.retried = 0
417 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
413 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
418 self, auth_header, host, req, headers)
414 self, auth_header, host, req, headers)
419
415
420 def retry_http_basic_auth(self, host, req, realm):
416 def retry_http_basic_auth(self, host, req, realm):
421 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
417 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
422 if pw is not None:
418 if pw is not None:
423 raw = "%s:%s" % (user, pw)
419 raw = "%s:%s" % (user, pw)
424 auth = 'Basic %s' % base64.b64encode(raw).strip()
420 auth = 'Basic %s' % base64.b64encode(raw).strip()
425 if req.get_header(self.auth_header, None) == auth:
421 if req.get_header(self.auth_header, None) == auth:
426 return None
422 return None
427 self.auth = auth
423 self.auth = auth
428 req.add_unredirected_header(self.auth_header, auth)
424 req.add_unredirected_header(self.auth_header, auth)
429 return self.parent.open(req)
425 return self.parent.open(req)
430 else:
426 else:
431 return None
427 return None
432
428
433 handlerfuncs = []
429 handlerfuncs = []
434
430
435 def opener(ui, authinfo=None):
431 def opener(ui, authinfo=None):
436 '''
432 '''
437 construct an opener suitable for urllib2
433 construct an opener suitable for urllib2
438 authinfo will be added to the password manager
434 authinfo will be added to the password manager
439 '''
435 '''
440 # experimental config: ui.usehttp2
436 # experimental config: ui.usehttp2
441 if ui.configbool('ui', 'usehttp2', False):
437 if ui.configbool('ui', 'usehttp2', False):
442 handlers = [
438 handlers = [
443 httpconnectionmod.http2handler(
439 httpconnectionmod.http2handler(
444 ui,
440 ui,
445 passwordmgr(ui, ui.httppasswordmgrdb))
441 passwordmgr(ui, ui.httppasswordmgrdb))
446 ]
442 ]
447 else:
443 else:
448 handlers = [httphandler()]
444 handlers = [httphandler()]
449 if has_https:
445 if has_https:
450 handlers.append(httpshandler(ui))
446 handlers.append(httpshandler(ui))
451
447
452 handlers.append(proxyhandler(ui))
448 handlers.append(proxyhandler(ui))
453
449
454 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
450 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
455 if authinfo is not None:
451 if authinfo is not None:
456 realm, uris, user, passwd = authinfo
452 realm, uris, user, passwd = authinfo
457 saveduser, savedpass = passmgr.find_stored_password(uris[0])
453 saveduser, savedpass = passmgr.find_stored_password(uris[0])
458 if user != saveduser or passwd:
454 if user != saveduser or passwd:
459 passmgr.add_password(realm, uris, user, passwd)
455 passmgr.add_password(realm, uris, user, passwd)
460 ui.debug('http auth: user %s, password %s\n' %
456 ui.debug('http auth: user %s, password %s\n' %
461 (user, passwd and '*' * len(passwd) or 'not set'))
457 (user, passwd and '*' * len(passwd) or 'not set'))
462
458
463 handlers.extend((httpbasicauthhandler(passmgr),
459 handlers.extend((httpbasicauthhandler(passmgr),
464 httpdigestauthhandler(passmgr)))
460 httpdigestauthhandler(passmgr)))
465 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
461 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
466 opener = urlreq.buildopener(*handlers)
462 opener = urlreq.buildopener(*handlers)
467
463
468 # The user agent should should *NOT* be used by servers for e.g.
464 # The user agent should should *NOT* be used by servers for e.g.
469 # protocol detection or feature negotiation: there are other
465 # protocol detection or feature negotiation: there are other
470 # facilities for that.
466 # facilities for that.
471 #
467 #
472 # "mercurial/proto-1.0" was the original user agent string and
468 # "mercurial/proto-1.0" was the original user agent string and
473 # exists for backwards compatibility reasons.
469 # exists for backwards compatibility reasons.
474 #
470 #
475 # The "(Mercurial %s)" string contains the distribution
471 # The "(Mercurial %s)" string contains the distribution
476 # name and version. Other client implementations should choose their
472 # name and version. Other client implementations should choose their
477 # own distribution name. Since servers should not be using the user
473 # own distribution name. Since servers should not be using the user
478 # agent string for anything, clients should be able to define whatever
474 # agent string for anything, clients should be able to define whatever
479 # user agent they deem appropriate.
475 # user agent they deem appropriate.
480 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
476 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
481 opener.addheaders = [('User-agent', agent)]
477 opener.addheaders = [('User-agent', agent)]
482 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
478 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
483 return opener
479 return opener
484
480
485 def open(ui, url_, data=None):
481 def open(ui, url_, data=None):
486 u = util.url(url_)
482 u = util.url(url_)
487 if u.scheme:
483 if u.scheme:
488 u.scheme = u.scheme.lower()
484 u.scheme = u.scheme.lower()
489 url_, authinfo = u.authinfo()
485 url_, authinfo = u.authinfo()
490 else:
486 else:
491 path = util.normpath(os.path.abspath(url_))
487 path = util.normpath(os.path.abspath(url_))
492 url_ = 'file://' + urlreq.pathname2url(path)
488 url_ = 'file://' + urlreq.pathname2url(path)
493 authinfo = None
489 authinfo = None
494 return opener(ui, authinfo).open(url_, data)
490 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now