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