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