##// END OF EJS Templates
https: do not inherit httplib.HTTPSConnection that creates unused SSLContext...
Yuya Nishihara -
r25414:f7ccbc27 default
parent child Browse files
Show More
@@ -1,500 +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, 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 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
123 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
124
124
125 def _gen_sendfile(orgsend):
125 def _gen_sendfile(orgsend):
126 def _sendfile(self, data):
126 def _sendfile(self, data):
127 # send a file
127 # send a file
128 if isinstance(data, httpconnectionmod.httpsendfile):
128 if isinstance(data, httpconnectionmod.httpsendfile):
129 # if auth required, some data sent twice, so rewind here
129 # if auth required, some data sent twice, so rewind here
130 data.seek(0)
130 data.seek(0)
131 for chunk in util.filechunkiter(data):
131 for chunk in util.filechunkiter(data):
132 orgsend(self, chunk)
132 orgsend(self, chunk)
133 else:
133 else:
134 orgsend(self, data)
134 orgsend(self, data)
135 return _sendfile
135 return _sendfile
136
136
137 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
137 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
138 if has_https:
138 if has_https:
139 try:
139 try:
140 _create_connection = socket.create_connection
140 _create_connection = socket.create_connection
141 except AttributeError:
141 except AttributeError:
142 _GLOBAL_DEFAULT_TIMEOUT = object()
142 _GLOBAL_DEFAULT_TIMEOUT = object()
143
143
144 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
144 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
145 source_address=None):
145 source_address=None):
146 # lifted from Python 2.6
146 # lifted from Python 2.6
147
147
148 msg = "getaddrinfo returns an empty list"
148 msg = "getaddrinfo returns an empty list"
149 host, port = address
149 host, port = address
150 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
150 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
151 af, socktype, proto, canonname, sa = res
151 af, socktype, proto, canonname, sa = res
152 sock = None
152 sock = None
153 try:
153 try:
154 sock = socket.socket(af, socktype, proto)
154 sock = socket.socket(af, socktype, proto)
155 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
155 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
156 sock.settimeout(timeout)
156 sock.settimeout(timeout)
157 if source_address:
157 if source_address:
158 sock.bind(source_address)
158 sock.bind(source_address)
159 sock.connect(sa)
159 sock.connect(sa)
160 return sock
160 return sock
161
161
162 except socket.error, msg:
162 except socket.error, msg:
163 if sock is not None:
163 if sock is not None:
164 sock.close()
164 sock.close()
165
165
166 raise socket.error(msg)
166 raise socket.error(msg)
167
167
168 class httpconnection(keepalive.HTTPConnection):
168 class httpconnection(keepalive.HTTPConnection):
169 # must be able to send big bundle as stream.
169 # must be able to send big bundle as stream.
170 send = _gen_sendfile(keepalive.HTTPConnection.send)
170 send = _gen_sendfile(keepalive.HTTPConnection.send)
171
171
172 def connect(self):
172 def connect(self):
173 if has_https and self.realhostport: # use CONNECT proxy
173 if has_https and self.realhostport: # use CONNECT proxy
174 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
174 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
175 self.sock.connect((self.host, self.port))
175 self.sock.connect((self.host, self.port))
176 if _generic_proxytunnel(self):
176 if _generic_proxytunnel(self):
177 # we do not support client X.509 certificates
177 # we do not support client X.509 certificates
178 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
178 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
179 serverhostname=self.host)
179 serverhostname=self.host)
180 else:
180 else:
181 keepalive.HTTPConnection.connect(self)
181 keepalive.HTTPConnection.connect(self)
182
182
183 def getresponse(self):
183 def getresponse(self):
184 proxyres = getattr(self, 'proxyres', None)
184 proxyres = getattr(self, 'proxyres', None)
185 if proxyres:
185 if proxyres:
186 if proxyres.will_close:
186 if proxyres.will_close:
187 self.close()
187 self.close()
188 self.proxyres = None
188 self.proxyres = None
189 return proxyres
189 return proxyres
190 return keepalive.HTTPConnection.getresponse(self)
190 return keepalive.HTTPConnection.getresponse(self)
191
191
192 # general transaction handler to support different ways to handle
192 # general transaction handler to support different ways to handle
193 # HTTPS proxying before and after Python 2.6.3.
193 # HTTPS proxying before and after Python 2.6.3.
194 def _generic_start_transaction(handler, h, req):
194 def _generic_start_transaction(handler, h, req):
195 tunnel_host = getattr(req, '_tunnel_host', None)
195 tunnel_host = getattr(req, '_tunnel_host', None)
196 if tunnel_host:
196 if tunnel_host:
197 if tunnel_host[:7] not in ['http://', 'https:/']:
197 if tunnel_host[:7] not in ['http://', 'https:/']:
198 tunnel_host = 'https://' + tunnel_host
198 tunnel_host = 'https://' + tunnel_host
199 new_tunnel = True
199 new_tunnel = True
200 else:
200 else:
201 tunnel_host = req.get_selector()
201 tunnel_host = req.get_selector()
202 new_tunnel = False
202 new_tunnel = False
203
203
204 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
205 u = util.url(tunnel_host)
205 u = util.url(tunnel_host)
206 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
207 h.realhostport = ':'.join([u.host, (u.port or '443')])
207 h.realhostport = ':'.join([u.host, (u.port or '443')])
208 h.headers = req.headers.copy()
208 h.headers = req.headers.copy()
209 h.headers.update(handler.parent.addheaders)
209 h.headers.update(handler.parent.addheaders)
210 return
210 return
211
211
212 h.realhostport = None
212 h.realhostport = None
213 h.headers = None
213 h.headers = None
214
214
215 def _generic_proxytunnel(self):
215 def _generic_proxytunnel(self):
216 proxyheaders = dict(
216 proxyheaders = dict(
217 [(x, self.headers[x]) for x in self.headers
217 [(x, self.headers[x]) for x in self.headers
218 if x.lower().startswith('proxy-')])
218 if x.lower().startswith('proxy-')])
219 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
219 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
220 for header in proxyheaders.iteritems():
220 for header in proxyheaders.iteritems():
221 self.send('%s: %s\r\n' % header)
221 self.send('%s: %s\r\n' % header)
222 self.send('\r\n')
222 self.send('\r\n')
223
223
224 # majority of the following code is duplicated from
224 # majority of the following code is duplicated from
225 # httplib.HTTPConnection as there are no adequate places to
225 # httplib.HTTPConnection as there are no adequate places to
226 # override functions to provide the needed functionality
226 # override functions to provide the needed functionality
227 res = self.response_class(self.sock,
227 res = self.response_class(self.sock,
228 strict=self.strict,
228 strict=self.strict,
229 method=self._method)
229 method=self._method)
230
230
231 while True:
231 while True:
232 version, status, reason = res._read_status()
232 version, status, reason = res._read_status()
233 if status != httplib.CONTINUE:
233 if status != httplib.CONTINUE:
234 break
234 break
235 while True:
235 while True:
236 skip = res.fp.readline().strip()
236 skip = res.fp.readline().strip()
237 if not skip:
237 if not skip:
238 break
238 break
239 res.status = status
239 res.status = status
240 res.reason = reason.strip()
240 res.reason = reason.strip()
241
241
242 if res.status == 200:
242 if res.status == 200:
243 while True:
243 while True:
244 line = res.fp.readline()
244 line = res.fp.readline()
245 if line == '\r\n':
245 if line == '\r\n':
246 break
246 break
247 return True
247 return True
248
248
249 if version == 'HTTP/1.0':
249 if version == 'HTTP/1.0':
250 res.version = 10
250 res.version = 10
251 elif version.startswith('HTTP/1.'):
251 elif version.startswith('HTTP/1.'):
252 res.version = 11
252 res.version = 11
253 elif version == 'HTTP/0.9':
253 elif version == 'HTTP/0.9':
254 res.version = 9
254 res.version = 9
255 else:
255 else:
256 raise httplib.UnknownProtocol(version)
256 raise httplib.UnknownProtocol(version)
257
257
258 if res.version == 9:
258 if res.version == 9:
259 res.length = None
259 res.length = None
260 res.chunked = 0
260 res.chunked = 0
261 res.will_close = 1
261 res.will_close = 1
262 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
262 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
263 return False
263 return False
264
264
265 res.msg = httplib.HTTPMessage(res.fp)
265 res.msg = httplib.HTTPMessage(res.fp)
266 res.msg.fp = None
266 res.msg.fp = None
267
267
268 # are we using the chunked-style of transfer encoding?
268 # are we using the chunked-style of transfer encoding?
269 trenc = res.msg.getheader('transfer-encoding')
269 trenc = res.msg.getheader('transfer-encoding')
270 if trenc and trenc.lower() == "chunked":
270 if trenc and trenc.lower() == "chunked":
271 res.chunked = 1
271 res.chunked = 1
272 res.chunk_left = None
272 res.chunk_left = None
273 else:
273 else:
274 res.chunked = 0
274 res.chunked = 0
275
275
276 # will the connection close at the end of the response?
276 # will the connection close at the end of the response?
277 res.will_close = res._check_close()
277 res.will_close = res._check_close()
278
278
279 # do we have a Content-Length?
279 # do we have a Content-Length?
280 # 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
281 # transfer-encoding is "chunked"
281 # transfer-encoding is "chunked"
282 length = res.msg.getheader('content-length')
282 length = res.msg.getheader('content-length')
283 if length and not res.chunked:
283 if length and not res.chunked:
284 try:
284 try:
285 res.length = int(length)
285 res.length = int(length)
286 except ValueError:
286 except ValueError:
287 res.length = None
287 res.length = None
288 else:
288 else:
289 if res.length < 0: # ignore nonsensical negative lengths
289 if res.length < 0: # ignore nonsensical negative lengths
290 res.length = None
290 res.length = None
291 else:
291 else:
292 res.length = None
292 res.length = None
293
293
294 # does the body have a fixed length? (of zero)
294 # does the body have a fixed length? (of zero)
295 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
295 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
296 100 <= status < 200 or # 1xx codes
296 100 <= status < 200 or # 1xx codes
297 res._method == 'HEAD'):
297 res._method == 'HEAD'):
298 res.length = 0
298 res.length = 0
299
299
300 # 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
301 # a content-length was not provided, then assume that the connection
301 # a content-length was not provided, then assume that the connection
302 # WILL close.
302 # WILL close.
303 if (not res.will_close and
303 if (not res.will_close and
304 not res.chunked and
304 not res.chunked and
305 res.length is None):
305 res.length is None):
306 res.will_close = 1
306 res.will_close = 1
307
307
308 self.proxyres = res
308 self.proxyres = res
309
309
310 return False
310 return False
311
311
312 class httphandler(keepalive.HTTPHandler):
312 class httphandler(keepalive.HTTPHandler):
313 def http_open(self, req):
313 def http_open(self, req):
314 return self.do_open(httpconnection, req)
314 return self.do_open(httpconnection, req)
315
315
316 def _start_transaction(self, h, req):
316 def _start_transaction(self, h, req):
317 _generic_start_transaction(self, h, req)
317 _generic_start_transaction(self, h, req)
318 return keepalive.HTTPHandler._start_transaction(self, h, req)
318 return keepalive.HTTPHandler._start_transaction(self, h, req)
319
319
320 if has_https:
320 if has_https:
321 class httpsconnection(httplib.HTTPSConnection):
321 class httpsconnection(httplib.HTTPConnection):
322 response_class = keepalive.HTTPResponse
322 response_class = keepalive.HTTPResponse
323 default_port = httplib.HTTPS_PORT
323 # must be able to send big bundle as stream.
324 # must be able to send big bundle as stream.
324 send = _gen_sendfile(keepalive.safesend)
325 send = _gen_sendfile(keepalive.safesend)
325 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
326 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
327
328 def __init__(self, host, port=None, key_file=None, cert_file=None,
329 *args, **kwargs):
330 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
331 self.key_file = key_file
332 self.cert_file = cert_file
326
333
327 def connect(self):
334 def connect(self):
328 self.sock = _create_connection((self.host, self.port))
335 self.sock = _create_connection((self.host, self.port))
329
336
330 host = self.host
337 host = self.host
331 if self.realhostport: # use CONNECT proxy
338 if self.realhostport: # use CONNECT proxy
332 _generic_proxytunnel(self)
339 _generic_proxytunnel(self)
333 host = self.realhostport.rsplit(':', 1)[0]
340 host = self.realhostport.rsplit(':', 1)[0]
334 self.sock = sslutil.ssl_wrap_socket(
341 self.sock = sslutil.ssl_wrap_socket(
335 self.sock, self.key_file, self.cert_file, serverhostname=host,
342 self.sock, self.key_file, self.cert_file, serverhostname=host,
336 **sslutil.sslkwargs(self.ui, host))
343 **sslutil.sslkwargs(self.ui, host))
337 sslutil.validator(self.ui, host)(self.sock)
344 sslutil.validator(self.ui, host)(self.sock)
338
345
339 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
346 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
340 def __init__(self, ui):
347 def __init__(self, ui):
341 keepalive.KeepAliveHandler.__init__(self)
348 keepalive.KeepAliveHandler.__init__(self)
342 urllib2.HTTPSHandler.__init__(self)
349 urllib2.HTTPSHandler.__init__(self)
343 self.ui = ui
350 self.ui = ui
344 self.pwmgr = passwordmgr(self.ui)
351 self.pwmgr = passwordmgr(self.ui)
345
352
346 def _start_transaction(self, h, req):
353 def _start_transaction(self, h, req):
347 _generic_start_transaction(self, h, req)
354 _generic_start_transaction(self, h, req)
348 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
355 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
349
356
350 def https_open(self, req):
357 def https_open(self, req):
351 # req.get_full_url() does not contain credentials and we may
358 # req.get_full_url() does not contain credentials and we may
352 # need them to match the certificates.
359 # need them to match the certificates.
353 url = req.get_full_url()
360 url = req.get_full_url()
354 user, password = self.pwmgr.find_stored_password(url)
361 user, password = self.pwmgr.find_stored_password(url)
355 res = httpconnectionmod.readauthforuri(self.ui, url, user)
362 res = httpconnectionmod.readauthforuri(self.ui, url, user)
356 if res:
363 if res:
357 group, auth = res
364 group, auth = res
358 self.auth = auth
365 self.auth = auth
359 self.ui.debug("using auth.%s.* for authentication\n" % group)
366 self.ui.debug("using auth.%s.* for authentication\n" % group)
360 else:
367 else:
361 self.auth = None
368 self.auth = None
362 return self.do_open(self._makeconnection, req)
369 return self.do_open(self._makeconnection, req)
363
370
364 def _makeconnection(self, host, port=None, *args, **kwargs):
371 def _makeconnection(self, host, port=None, *args, **kwargs):
365 keyfile = None
372 keyfile = None
366 certfile = None
373 certfile = None
367
374
368 if len(args) >= 1: # key_file
375 if len(args) >= 1: # key_file
369 keyfile = args[0]
376 keyfile = args[0]
370 if len(args) >= 2: # cert_file
377 if len(args) >= 2: # cert_file
371 certfile = args[1]
378 certfile = args[1]
372 args = args[2:]
379 args = args[2:]
373
380
374 # if the user has specified different key/cert files in
381 # if the user has specified different key/cert files in
375 # hgrc, we prefer these
382 # hgrc, we prefer these
376 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:
377 keyfile = self.auth['key']
384 keyfile = self.auth['key']
378 certfile = self.auth['cert']
385 certfile = self.auth['cert']
379
386
380 conn = httpsconnection(host, port, keyfile, certfile, *args,
387 conn = httpsconnection(host, port, keyfile, certfile, *args,
381 **kwargs)
388 **kwargs)
382 conn.ui = self.ui
389 conn.ui = self.ui
383 return conn
390 return conn
384
391
385 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
392 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
386 def __init__(self, *args, **kwargs):
393 def __init__(self, *args, **kwargs):
387 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
394 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
388 self.retried_req = None
395 self.retried_req = None
389
396
390 def reset_retry_count(self):
397 def reset_retry_count(self):
391 # 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
392 # forever. We disable reset_retry_count completely and reset in
399 # forever. We disable reset_retry_count completely and reset in
393 # http_error_auth_reqed instead.
400 # http_error_auth_reqed instead.
394 pass
401 pass
395
402
396 def http_error_auth_reqed(self, auth_header, host, req, headers):
403 def http_error_auth_reqed(self, auth_header, host, req, headers):
397 # Reset the retry counter once for each request.
404 # Reset the retry counter once for each request.
398 if req is not self.retried_req:
405 if req is not self.retried_req:
399 self.retried_req = req
406 self.retried_req = req
400 self.retried = 0
407 self.retried = 0
401 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
408 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
402 # 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
403 # somebody is using BasicAuth and types a bad password.
410 # somebody is using BasicAuth and types a bad password.
404 try:
411 try:
405 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
412 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
406 self, auth_header, host, req, headers)
413 self, auth_header, host, req, headers)
407 except ValueError, inst:
414 except ValueError, inst:
408 arg = inst.args[0]
415 arg = inst.args[0]
409 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
416 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
410 return
417 return
411 raise
418 raise
412
419
413 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
420 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
414 def __init__(self, *args, **kwargs):
421 def __init__(self, *args, **kwargs):
415 self.auth = None
422 self.auth = None
416 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
423 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
417 self.retried_req = None
424 self.retried_req = None
418
425
419 def http_request(self, request):
426 def http_request(self, request):
420 if self.auth:
427 if self.auth:
421 request.add_unredirected_header(self.auth_header, self.auth)
428 request.add_unredirected_header(self.auth_header, self.auth)
422
429
423 return request
430 return request
424
431
425 def https_request(self, request):
432 def https_request(self, request):
426 if self.auth:
433 if self.auth:
427 request.add_unredirected_header(self.auth_header, self.auth)
434 request.add_unredirected_header(self.auth_header, self.auth)
428
435
429 return request
436 return request
430
437
431 def reset_retry_count(self):
438 def reset_retry_count(self):
432 # 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
433 # forever. We disable reset_retry_count completely and reset in
440 # forever. We disable reset_retry_count completely and reset in
434 # http_error_auth_reqed instead.
441 # http_error_auth_reqed instead.
435 pass
442 pass
436
443
437 def http_error_auth_reqed(self, auth_header, host, req, headers):
444 def http_error_auth_reqed(self, auth_header, host, req, headers):
438 # Reset the retry counter once for each request.
445 # Reset the retry counter once for each request.
439 if req is not self.retried_req:
446 if req is not self.retried_req:
440 self.retried_req = req
447 self.retried_req = req
441 self.retried = 0
448 self.retried = 0
442 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
449 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
443 self, auth_header, host, req, headers)
450 self, auth_header, host, req, headers)
444
451
445 def retry_http_basic_auth(self, host, req, realm):
452 def retry_http_basic_auth(self, host, req, realm):
446 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
453 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
447 if pw is not None:
454 if pw is not None:
448 raw = "%s:%s" % (user, pw)
455 raw = "%s:%s" % (user, pw)
449 auth = 'Basic %s' % base64.b64encode(raw).strip()
456 auth = 'Basic %s' % base64.b64encode(raw).strip()
450 if req.headers.get(self.auth_header, None) == auth:
457 if req.headers.get(self.auth_header, None) == auth:
451 return None
458 return None
452 self.auth = auth
459 self.auth = auth
453 req.add_unredirected_header(self.auth_header, auth)
460 req.add_unredirected_header(self.auth_header, auth)
454 return self.parent.open(req)
461 return self.parent.open(req)
455 else:
462 else:
456 return None
463 return None
457
464
458 handlerfuncs = []
465 handlerfuncs = []
459
466
460 def opener(ui, authinfo=None):
467 def opener(ui, authinfo=None):
461 '''
468 '''
462 construct an opener suitable for urllib2
469 construct an opener suitable for urllib2
463 authinfo will be added to the password manager
470 authinfo will be added to the password manager
464 '''
471 '''
465 if ui.configbool('ui', 'usehttp2', False):
472 if ui.configbool('ui', 'usehttp2', False):
466 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
473 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
467 else:
474 else:
468 handlers = [httphandler()]
475 handlers = [httphandler()]
469 if has_https:
476 if has_https:
470 handlers.append(httpshandler(ui))
477 handlers.append(httpshandler(ui))
471
478
472 handlers.append(proxyhandler(ui))
479 handlers.append(proxyhandler(ui))
473
480
474 passmgr = passwordmgr(ui)
481 passmgr = passwordmgr(ui)
475 if authinfo is not None:
482 if authinfo is not None:
476 passmgr.add_password(*authinfo)
483 passmgr.add_password(*authinfo)
477 user, passwd = authinfo[2:4]
484 user, passwd = authinfo[2:4]
478 ui.debug('http auth: user %s, password %s\n' %
485 ui.debug('http auth: user %s, password %s\n' %
479 (user, passwd and '*' * len(passwd) or 'not set'))
486 (user, passwd and '*' * len(passwd) or 'not set'))
480
487
481 handlers.extend((httpbasicauthhandler(passmgr),
488 handlers.extend((httpbasicauthhandler(passmgr),
482 httpdigestauthhandler(passmgr)))
489 httpdigestauthhandler(passmgr)))
483 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
490 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
484 opener = urllib2.build_opener(*handlers)
491 opener = urllib2.build_opener(*handlers)
485
492
486 # 1.0 here is the _protocol_ version
493 # 1.0 here is the _protocol_ version
487 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
494 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
488 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
495 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
489 return opener
496 return opener
490
497
491 def open(ui, url_, data=None):
498 def open(ui, url_, data=None):
492 u = util.url(url_)
499 u = util.url(url_)
493 if u.scheme:
500 if u.scheme:
494 u.scheme = u.scheme.lower()
501 u.scheme = u.scheme.lower()
495 url_, authinfo = u.authinfo()
502 url_, authinfo = u.authinfo()
496 else:
503 else:
497 path = util.normpath(os.path.abspath(url_))
504 path = util.normpath(os.path.abspath(url_))
498 url_ = 'file://' + urllib.pathname2url(path)
505 url_ = 'file://' + urllib.pathname2url(path)
499 authinfo = None
506 authinfo = None
500 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