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