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