##// END OF EJS Templates
url: pass str to pathname2url...
Gregory Szorc -
r45131:e74af49a default
parent child Browse files
Show More
@@ -1,726 +1,728 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 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 .pycompat import getattr
18 from .pycompat import getattr
19 from . import (
19 from . import (
20 encoding,
20 encoding,
21 error,
21 error,
22 httpconnection as httpconnectionmod,
22 httpconnection as httpconnectionmod,
23 keepalive,
23 keepalive,
24 pycompat,
24 pycompat,
25 sslutil,
25 sslutil,
26 urllibcompat,
26 urllibcompat,
27 util,
27 util,
28 )
28 )
29 from .utils import stringutil
29 from .utils import stringutil
30
30
31 httplib = util.httplib
31 httplib = util.httplib
32 stringio = util.stringio
32 stringio = util.stringio
33 urlerr = util.urlerr
33 urlerr = util.urlerr
34 urlreq = util.urlreq
34 urlreq = util.urlreq
35
35
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
52
53 class passwordmgr(object):
53 class passwordmgr(object):
54 def __init__(self, ui, passwddb):
54 def __init__(self, ui, passwddb):
55 self.ui = ui
55 self.ui = ui
56 self.passwddb = passwddb
56 self.passwddb = passwddb
57
57
58 def add_password(self, realm, uri, user, passwd):
58 def add_password(self, realm, uri, user, passwd):
59 return self.passwddb.add_password(realm, uri, user, passwd)
59 return self.passwddb.add_password(realm, uri, user, passwd)
60
60
61 def find_user_password(self, realm, authuri):
61 def find_user_password(self, realm, authuri):
62 assert isinstance(realm, (type(None), str))
62 assert isinstance(realm, (type(None), str))
63 assert isinstance(authuri, str)
63 assert isinstance(authuri, str)
64 authinfo = self.passwddb.find_user_password(realm, authuri)
64 authinfo = self.passwddb.find_user_password(realm, authuri)
65 user, passwd = authinfo
65 user, passwd = authinfo
66 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
66 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
67 if user and passwd:
67 if user and passwd:
68 self._writedebug(user, passwd)
68 self._writedebug(user, passwd)
69 return (pycompat.strurl(user), pycompat.strurl(passwd))
69 return (pycompat.strurl(user), pycompat.strurl(passwd))
70
70
71 if not user or not passwd:
71 if not user or not passwd:
72 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
72 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
73 if res:
73 if res:
74 group, auth = res
74 group, auth = res
75 user, passwd = auth.get(b'username'), auth.get(b'password')
75 user, passwd = auth.get(b'username'), auth.get(b'password')
76 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
76 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
77 if not user or not passwd:
77 if not user or not passwd:
78 u = util.url(pycompat.bytesurl(authuri))
78 u = util.url(pycompat.bytesurl(authuri))
79 u.query = None
79 u.query = None
80 if not self.ui.interactive():
80 if not self.ui.interactive():
81 raise error.Abort(
81 raise error.Abort(
82 _(b'http authorization required for %s')
82 _(b'http authorization required for %s')
83 % util.hidepassword(bytes(u))
83 % util.hidepassword(bytes(u))
84 )
84 )
85
85
86 self.ui.write(
86 self.ui.write(
87 _(b"http authorization required for %s\n")
87 _(b"http authorization required for %s\n")
88 % util.hidepassword(bytes(u))
88 % util.hidepassword(bytes(u))
89 )
89 )
90 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
90 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
91 if user:
91 if user:
92 self.ui.write(_(b"user: %s\n") % user)
92 self.ui.write(_(b"user: %s\n") % user)
93 else:
93 else:
94 user = self.ui.prompt(_(b"user:"), default=None)
94 user = self.ui.prompt(_(b"user:"), default=None)
95
95
96 if not passwd:
96 if not passwd:
97 passwd = self.ui.getpass()
97 passwd = self.ui.getpass()
98
98
99 self.passwddb.add_password(realm, authuri, user, passwd)
99 self.passwddb.add_password(realm, authuri, user, passwd)
100 self._writedebug(user, passwd)
100 self._writedebug(user, passwd)
101 return (pycompat.strurl(user), pycompat.strurl(passwd))
101 return (pycompat.strurl(user), pycompat.strurl(passwd))
102
102
103 def _writedebug(self, user, passwd):
103 def _writedebug(self, user, passwd):
104 msg = _(b'http auth: user %s, password %s\n')
104 msg = _(b'http auth: user %s, password %s\n')
105 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
105 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
106
106
107 def find_stored_password(self, authuri):
107 def find_stored_password(self, authuri):
108 return self.passwddb.find_user_password(None, authuri)
108 return self.passwddb.find_user_password(None, authuri)
109
109
110
110
111 class proxyhandler(urlreq.proxyhandler):
111 class proxyhandler(urlreq.proxyhandler):
112 def __init__(self, ui):
112 def __init__(self, ui):
113 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
113 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
114 b'http_proxy'
114 b'http_proxy'
115 )
115 )
116 # XXX proxyauthinfo = None
116 # XXX proxyauthinfo = None
117
117
118 if proxyurl:
118 if proxyurl:
119 # proxy can be proper url or host[:port]
119 # proxy can be proper url or host[:port]
120 if not (
120 if not (
121 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
121 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
122 ):
122 ):
123 proxyurl = b'http://' + proxyurl + b'/'
123 proxyurl = b'http://' + proxyurl + b'/'
124 proxy = util.url(proxyurl)
124 proxy = util.url(proxyurl)
125 if not proxy.user:
125 if not proxy.user:
126 proxy.user = ui.config(b"http_proxy", b"user")
126 proxy.user = ui.config(b"http_proxy", b"user")
127 proxy.passwd = ui.config(b"http_proxy", b"passwd")
127 proxy.passwd = ui.config(b"http_proxy", b"passwd")
128
128
129 # see if we should use a proxy for this url
129 # see if we should use a proxy for this url
130 no_list = [b"localhost", b"127.0.0.1"]
130 no_list = [b"localhost", b"127.0.0.1"]
131 no_list.extend(
131 no_list.extend(
132 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
132 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
133 )
133 )
134 no_list.extend(
134 no_list.extend(
135 [
135 [
136 p.strip().lower()
136 p.strip().lower()
137 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
137 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
138 if p.strip()
138 if p.strip()
139 ]
139 ]
140 )
140 )
141 # "http_proxy.always" config is for running tests on localhost
141 # "http_proxy.always" config is for running tests on localhost
142 if ui.configbool(b"http_proxy", b"always"):
142 if ui.configbool(b"http_proxy", b"always"):
143 self.no_list = []
143 self.no_list = []
144 else:
144 else:
145 self.no_list = no_list
145 self.no_list = no_list
146
146
147 # Keys and values need to be str because the standard library
147 # Keys and values need to be str because the standard library
148 # expects them to be.
148 # expects them to be.
149 proxyurl = str(proxy)
149 proxyurl = str(proxy)
150 proxies = {'http': proxyurl, 'https': proxyurl}
150 proxies = {'http': proxyurl, 'https': proxyurl}
151 ui.debug(b'proxying through %s\n' % util.hidepassword(bytes(proxy)))
151 ui.debug(b'proxying through %s\n' % util.hidepassword(bytes(proxy)))
152 else:
152 else:
153 proxies = {}
153 proxies = {}
154
154
155 urlreq.proxyhandler.__init__(self, proxies)
155 urlreq.proxyhandler.__init__(self, proxies)
156 self.ui = ui
156 self.ui = ui
157
157
158 def proxy_open(self, req, proxy, type_):
158 def proxy_open(self, req, proxy, type_):
159 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
159 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
160 for e in self.no_list:
160 for e in self.no_list:
161 if host == e:
161 if host == e:
162 return None
162 return None
163 if e.startswith(b'*.') and host.endswith(e[2:]):
163 if e.startswith(b'*.') and host.endswith(e[2:]):
164 return None
164 return None
165 if e.startswith(b'.') and host.endswith(e[1:]):
165 if e.startswith(b'.') and host.endswith(e[1:]):
166 return None
166 return None
167
167
168 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
168 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
169
169
170
170
171 def _gen_sendfile(orgsend):
171 def _gen_sendfile(orgsend):
172 def _sendfile(self, data):
172 def _sendfile(self, data):
173 # send a file
173 # send a file
174 if isinstance(data, httpconnectionmod.httpsendfile):
174 if isinstance(data, httpconnectionmod.httpsendfile):
175 # if auth required, some data sent twice, so rewind here
175 # if auth required, some data sent twice, so rewind here
176 data.seek(0)
176 data.seek(0)
177 for chunk in util.filechunkiter(data):
177 for chunk in util.filechunkiter(data):
178 orgsend(self, chunk)
178 orgsend(self, chunk)
179 else:
179 else:
180 orgsend(self, data)
180 orgsend(self, data)
181
181
182 return _sendfile
182 return _sendfile
183
183
184
184
185 has_https = util.safehasattr(urlreq, b'httpshandler')
185 has_https = util.safehasattr(urlreq, b'httpshandler')
186
186
187
187
188 class httpconnection(keepalive.HTTPConnection):
188 class httpconnection(keepalive.HTTPConnection):
189 # must be able to send big bundle as stream.
189 # must be able to send big bundle as stream.
190 send = _gen_sendfile(keepalive.HTTPConnection.send)
190 send = _gen_sendfile(keepalive.HTTPConnection.send)
191
191
192 def getresponse(self):
192 def getresponse(self):
193 proxyres = getattr(self, 'proxyres', None)
193 proxyres = getattr(self, 'proxyres', None)
194 if proxyres:
194 if proxyres:
195 if proxyres.will_close:
195 if proxyres.will_close:
196 self.close()
196 self.close()
197 self.proxyres = None
197 self.proxyres = None
198 return proxyres
198 return proxyres
199 return keepalive.HTTPConnection.getresponse(self)
199 return keepalive.HTTPConnection.getresponse(self)
200
200
201
201
202 # Large parts of this function have their origin from before Python 2.6
202 # Large parts of this function have their origin from before Python 2.6
203 # and could potentially be removed.
203 # and could potentially be removed.
204 def _generic_start_transaction(handler, h, req):
204 def _generic_start_transaction(handler, h, req):
205 tunnel_host = req._tunnel_host
205 tunnel_host = req._tunnel_host
206 if tunnel_host:
206 if tunnel_host:
207 if tunnel_host[:7] not in ['http://', 'https:/']:
207 if tunnel_host[:7] not in ['http://', 'https:/']:
208 tunnel_host = 'https://' + tunnel_host
208 tunnel_host = 'https://' + tunnel_host
209 new_tunnel = True
209 new_tunnel = True
210 else:
210 else:
211 tunnel_host = urllibcompat.getselector(req)
211 tunnel_host = urllibcompat.getselector(req)
212 new_tunnel = False
212 new_tunnel = False
213
213
214 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
214 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
215 u = util.url(pycompat.bytesurl(tunnel_host))
215 u = util.url(pycompat.bytesurl(tunnel_host))
216 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
216 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
217 h.realhostport = b':'.join([u.host, (u.port or b'443')])
217 h.realhostport = b':'.join([u.host, (u.port or b'443')])
218 h.headers = req.headers.copy()
218 h.headers = req.headers.copy()
219 h.headers.update(handler.parent.addheaders)
219 h.headers.update(handler.parent.addheaders)
220 return
220 return
221
221
222 h.realhostport = None
222 h.realhostport = None
223 h.headers = None
223 h.headers = None
224
224
225
225
226 def _generic_proxytunnel(self):
226 def _generic_proxytunnel(self):
227 proxyheaders = {
227 proxyheaders = {
228 x: self.headers[x]
228 x: self.headers[x]
229 for x in self.headers
229 for x in self.headers
230 if x.lower().startswith('proxy-')
230 if x.lower().startswith('proxy-')
231 }
231 }
232 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
232 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
233 for header in pycompat.iteritems(proxyheaders):
233 for header in pycompat.iteritems(proxyheaders):
234 self.send(b'%s: %s\r\n' % header)
234 self.send(b'%s: %s\r\n' % header)
235 self.send(b'\r\n')
235 self.send(b'\r\n')
236
236
237 # majority of the following code is duplicated from
237 # majority of the following code is duplicated from
238 # httplib.HTTPConnection as there are no adequate places to
238 # httplib.HTTPConnection as there are no adequate places to
239 # override functions to provide the needed functionality
239 # override functions to provide the needed functionality
240 # strict was removed in Python 3.4.
240 # strict was removed in Python 3.4.
241 kwargs = {}
241 kwargs = {}
242 if not pycompat.ispy3:
242 if not pycompat.ispy3:
243 kwargs[b'strict'] = self.strict
243 kwargs[b'strict'] = self.strict
244
244
245 res = self.response_class(self.sock, method=self._method, **kwargs)
245 res = self.response_class(self.sock, method=self._method, **kwargs)
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 # skip lines that are all whitespace
251 # skip lines that are all whitespace
252 list(iter(lambda: res.fp.readline().strip(), b''))
252 list(iter(lambda: res.fp.readline().strip(), b''))
253 res.status = status
253 res.status = status
254 res.reason = reason.strip()
254 res.reason = reason.strip()
255
255
256 if res.status == 200:
256 if res.status == 200:
257 # skip lines until we find a blank line
257 # skip lines until we find a blank line
258 list(iter(res.fp.readline, b'\r\n'))
258 list(iter(res.fp.readline, b'\r\n'))
259 return True
259 return True
260
260
261 if version == b'HTTP/1.0':
261 if version == b'HTTP/1.0':
262 res.version = 10
262 res.version = 10
263 elif version.startswith(b'HTTP/1.'):
263 elif version.startswith(b'HTTP/1.'):
264 res.version = 11
264 res.version = 11
265 elif version == b'HTTP/0.9':
265 elif version == b'HTTP/0.9':
266 res.version = 9
266 res.version = 9
267 else:
267 else:
268 raise httplib.UnknownProtocol(version)
268 raise httplib.UnknownProtocol(version)
269
269
270 if res.version == 9:
270 if res.version == 9:
271 res.length = None
271 res.length = None
272 res.chunked = 0
272 res.chunked = 0
273 res.will_close = 1
273 res.will_close = 1
274 res.msg = httplib.HTTPMessage(stringio())
274 res.msg = httplib.HTTPMessage(stringio())
275 return False
275 return False
276
276
277 res.msg = httplib.HTTPMessage(res.fp)
277 res.msg = httplib.HTTPMessage(res.fp)
278 res.msg.fp = None
278 res.msg.fp = None
279
279
280 # are we using the chunked-style of transfer encoding?
280 # are we using the chunked-style of transfer encoding?
281 trenc = res.msg.getheader(b'transfer-encoding')
281 trenc = res.msg.getheader(b'transfer-encoding')
282 if trenc and trenc.lower() == b"chunked":
282 if trenc and trenc.lower() == b"chunked":
283 res.chunked = 1
283 res.chunked = 1
284 res.chunk_left = None
284 res.chunk_left = None
285 else:
285 else:
286 res.chunked = 0
286 res.chunked = 0
287
287
288 # will the connection close at the end of the response?
288 # will the connection close at the end of the response?
289 res.will_close = res._check_close()
289 res.will_close = res._check_close()
290
290
291 # do we have a Content-Length?
291 # do we have a Content-Length?
292 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
292 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
293 # transfer-encoding is "chunked"
293 # transfer-encoding is "chunked"
294 length = res.msg.getheader(b'content-length')
294 length = res.msg.getheader(b'content-length')
295 if length and not res.chunked:
295 if length and not res.chunked:
296 try:
296 try:
297 res.length = int(length)
297 res.length = int(length)
298 except ValueError:
298 except ValueError:
299 res.length = None
299 res.length = None
300 else:
300 else:
301 if res.length < 0: # ignore nonsensical negative lengths
301 if res.length < 0: # ignore nonsensical negative lengths
302 res.length = None
302 res.length = None
303 else:
303 else:
304 res.length = None
304 res.length = None
305
305
306 # does the body have a fixed length? (of zero)
306 # does the body have a fixed length? (of zero)
307 if (
307 if (
308 status == httplib.NO_CONTENT
308 status == httplib.NO_CONTENT
309 or status == httplib.NOT_MODIFIED
309 or status == httplib.NOT_MODIFIED
310 or 100 <= status < 200
310 or 100 <= status < 200
311 or res._method == b'HEAD' # 1xx codes
311 or res._method == b'HEAD' # 1xx codes
312 ):
312 ):
313 res.length = 0
313 res.length = 0
314
314
315 # if the connection remains open, and we aren't using chunked, and
315 # if the connection remains open, and we aren't using chunked, and
316 # a content-length was not provided, then assume that the connection
316 # a content-length was not provided, then assume that the connection
317 # WILL close.
317 # WILL close.
318 if not res.will_close and not res.chunked and res.length is None:
318 if not res.will_close and not res.chunked and res.length is None:
319 res.will_close = 1
319 res.will_close = 1
320
320
321 self.proxyres = res
321 self.proxyres = res
322
322
323 return False
323 return False
324
324
325
325
326 class httphandler(keepalive.HTTPHandler):
326 class httphandler(keepalive.HTTPHandler):
327 def http_open(self, req):
327 def http_open(self, req):
328 return self.do_open(httpconnection, req)
328 return self.do_open(httpconnection, req)
329
329
330 def _start_transaction(self, h, req):
330 def _start_transaction(self, h, req):
331 _generic_start_transaction(self, h, req)
331 _generic_start_transaction(self, h, req)
332 return keepalive.HTTPHandler._start_transaction(self, h, req)
332 return keepalive.HTTPHandler._start_transaction(self, h, req)
333
333
334
334
335 class logginghttpconnection(keepalive.HTTPConnection):
335 class logginghttpconnection(keepalive.HTTPConnection):
336 def __init__(self, createconn, *args, **kwargs):
336 def __init__(self, createconn, *args, **kwargs):
337 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
337 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
338 self._create_connection = createconn
338 self._create_connection = createconn
339
339
340 if sys.version_info < (2, 7, 7):
340 if sys.version_info < (2, 7, 7):
341 # copied from 2.7.14, since old implementations directly call
341 # copied from 2.7.14, since old implementations directly call
342 # socket.create_connection()
342 # socket.create_connection()
343 def connect(self):
343 def connect(self):
344 self.sock = self._create_connection(
344 self.sock = self._create_connection(
345 (self.host, self.port), self.timeout, self.source_address
345 (self.host, self.port), self.timeout, self.source_address
346 )
346 )
347 if self._tunnel_host:
347 if self._tunnel_host:
348 self._tunnel()
348 self._tunnel()
349
349
350
350
351 class logginghttphandler(httphandler):
351 class logginghttphandler(httphandler):
352 """HTTP handler that logs socket I/O."""
352 """HTTP handler that logs socket I/O."""
353
353
354 def __init__(self, logfh, name, observeropts, timeout=None):
354 def __init__(self, logfh, name, observeropts, timeout=None):
355 super(logginghttphandler, self).__init__(timeout=timeout)
355 super(logginghttphandler, self).__init__(timeout=timeout)
356
356
357 self._logfh = logfh
357 self._logfh = logfh
358 self._logname = name
358 self._logname = name
359 self._observeropts = observeropts
359 self._observeropts = observeropts
360
360
361 # do_open() calls the passed class to instantiate an HTTPConnection. We
361 # do_open() calls the passed class to instantiate an HTTPConnection. We
362 # pass in a callable method that creates a custom HTTPConnection instance
362 # pass in a callable method that creates a custom HTTPConnection instance
363 # whose callback to create the socket knows how to proxy the socket.
363 # whose callback to create the socket knows how to proxy the socket.
364 def http_open(self, req):
364 def http_open(self, req):
365 return self.do_open(self._makeconnection, req)
365 return self.do_open(self._makeconnection, req)
366
366
367 def _makeconnection(self, *args, **kwargs):
367 def _makeconnection(self, *args, **kwargs):
368 def createconnection(*args, **kwargs):
368 def createconnection(*args, **kwargs):
369 sock = socket.create_connection(*args, **kwargs)
369 sock = socket.create_connection(*args, **kwargs)
370 return util.makeloggingsocket(
370 return util.makeloggingsocket(
371 self._logfh, sock, self._logname, **self._observeropts
371 self._logfh, sock, self._logname, **self._observeropts
372 )
372 )
373
373
374 return logginghttpconnection(createconnection, *args, **kwargs)
374 return logginghttpconnection(createconnection, *args, **kwargs)
375
375
376
376
377 if has_https:
377 if has_https:
378
378
379 class httpsconnection(keepalive.HTTPConnection):
379 class httpsconnection(keepalive.HTTPConnection):
380 response_class = keepalive.HTTPResponse
380 response_class = keepalive.HTTPResponse
381 default_port = httplib.HTTPS_PORT
381 default_port = httplib.HTTPS_PORT
382 # must be able to send big bundle as stream.
382 # must be able to send big bundle as stream.
383 send = _gen_sendfile(keepalive.safesend)
383 send = _gen_sendfile(keepalive.safesend)
384 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
384 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
385
385
386 def __init__(
386 def __init__(
387 self,
387 self,
388 host,
388 host,
389 port=None,
389 port=None,
390 key_file=None,
390 key_file=None,
391 cert_file=None,
391 cert_file=None,
392 *args,
392 *args,
393 **kwargs
393 **kwargs
394 ):
394 ):
395 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
395 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
396 self.key_file = key_file
396 self.key_file = key_file
397 self.cert_file = cert_file
397 self.cert_file = cert_file
398
398
399 def connect(self):
399 def connect(self):
400 self.sock = socket.create_connection((self.host, self.port))
400 self.sock = socket.create_connection((self.host, self.port))
401
401
402 host = self.host
402 host = self.host
403 if self.realhostport: # use CONNECT proxy
403 if self.realhostport: # use CONNECT proxy
404 _generic_proxytunnel(self)
404 _generic_proxytunnel(self)
405 host = self.realhostport.rsplit(b':', 1)[0]
405 host = self.realhostport.rsplit(b':', 1)[0]
406 self.sock = sslutil.wrapsocket(
406 self.sock = sslutil.wrapsocket(
407 self.sock,
407 self.sock,
408 self.key_file,
408 self.key_file,
409 self.cert_file,
409 self.cert_file,
410 ui=self.ui,
410 ui=self.ui,
411 serverhostname=host,
411 serverhostname=host,
412 )
412 )
413 sslutil.validatesocket(self.sock)
413 sslutil.validatesocket(self.sock)
414
414
415 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
415 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
416 def __init__(self, ui, timeout=None):
416 def __init__(self, ui, timeout=None):
417 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
417 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
418 urlreq.httpshandler.__init__(self)
418 urlreq.httpshandler.__init__(self)
419 self.ui = ui
419 self.ui = ui
420 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
420 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
421
421
422 def _start_transaction(self, h, req):
422 def _start_transaction(self, h, req):
423 _generic_start_transaction(self, h, req)
423 _generic_start_transaction(self, h, req)
424 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
424 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
425
425
426 def https_open(self, req):
426 def https_open(self, req):
427 # urllibcompat.getfullurl() does not contain credentials
427 # urllibcompat.getfullurl() does not contain credentials
428 # and we may need them to match the certificates.
428 # and we may need them to match the certificates.
429 url = urllibcompat.getfullurl(req)
429 url = urllibcompat.getfullurl(req)
430 user, password = self.pwmgr.find_stored_password(url)
430 user, password = self.pwmgr.find_stored_password(url)
431 res = httpconnectionmod.readauthforuri(self.ui, url, user)
431 res = httpconnectionmod.readauthforuri(self.ui, url, user)
432 if res:
432 if res:
433 group, auth = res
433 group, auth = res
434 self.auth = auth
434 self.auth = auth
435 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
435 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
436 else:
436 else:
437 self.auth = None
437 self.auth = None
438 return self.do_open(self._makeconnection, req)
438 return self.do_open(self._makeconnection, req)
439
439
440 def _makeconnection(self, host, port=None, *args, **kwargs):
440 def _makeconnection(self, host, port=None, *args, **kwargs):
441 keyfile = None
441 keyfile = None
442 certfile = None
442 certfile = None
443
443
444 if len(args) >= 1: # key_file
444 if len(args) >= 1: # key_file
445 keyfile = args[0]
445 keyfile = args[0]
446 if len(args) >= 2: # cert_file
446 if len(args) >= 2: # cert_file
447 certfile = args[1]
447 certfile = args[1]
448 args = args[2:]
448 args = args[2:]
449
449
450 # if the user has specified different key/cert files in
450 # if the user has specified different key/cert files in
451 # hgrc, we prefer these
451 # hgrc, we prefer these
452 if self.auth and b'key' in self.auth and b'cert' in self.auth:
452 if self.auth and b'key' in self.auth and b'cert' in self.auth:
453 keyfile = self.auth[b'key']
453 keyfile = self.auth[b'key']
454 certfile = self.auth[b'cert']
454 certfile = self.auth[b'cert']
455
455
456 conn = httpsconnection(
456 conn = httpsconnection(
457 host, port, keyfile, certfile, *args, **kwargs
457 host, port, keyfile, certfile, *args, **kwargs
458 )
458 )
459 conn.ui = self.ui
459 conn.ui = self.ui
460 return conn
460 return conn
461
461
462
462
463 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
463 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
464 def __init__(self, *args, **kwargs):
464 def __init__(self, *args, **kwargs):
465 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
465 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
466 self.retried_req = None
466 self.retried_req = None
467
467
468 def reset_retry_count(self):
468 def reset_retry_count(self):
469 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
469 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
470 # forever. We disable reset_retry_count completely and reset in
470 # forever. We disable reset_retry_count completely and reset in
471 # http_error_auth_reqed instead.
471 # http_error_auth_reqed instead.
472 pass
472 pass
473
473
474 def http_error_auth_reqed(self, auth_header, host, req, headers):
474 def http_error_auth_reqed(self, auth_header, host, req, headers):
475 # Reset the retry counter once for each request.
475 # Reset the retry counter once for each request.
476 if req is not self.retried_req:
476 if req is not self.retried_req:
477 self.retried_req = req
477 self.retried_req = req
478 self.retried = 0
478 self.retried = 0
479 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
479 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
480 self, auth_header, host, req, headers
480 self, auth_header, host, req, headers
481 )
481 )
482
482
483
483
484 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
484 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
485 def __init__(self, *args, **kwargs):
485 def __init__(self, *args, **kwargs):
486 self.auth = None
486 self.auth = None
487 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
487 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
488 self.retried_req = None
488 self.retried_req = None
489
489
490 def http_request(self, request):
490 def http_request(self, request):
491 if self.auth:
491 if self.auth:
492 request.add_unredirected_header(self.auth_header, self.auth)
492 request.add_unredirected_header(self.auth_header, self.auth)
493
493
494 return request
494 return request
495
495
496 def https_request(self, request):
496 def https_request(self, request):
497 if self.auth:
497 if self.auth:
498 request.add_unredirected_header(self.auth_header, self.auth)
498 request.add_unredirected_header(self.auth_header, self.auth)
499
499
500 return request
500 return request
501
501
502 def reset_retry_count(self):
502 def reset_retry_count(self):
503 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
503 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
504 # forever. We disable reset_retry_count completely and reset in
504 # forever. We disable reset_retry_count completely and reset in
505 # http_error_auth_reqed instead.
505 # http_error_auth_reqed instead.
506 pass
506 pass
507
507
508 def http_error_auth_reqed(self, auth_header, host, req, headers):
508 def http_error_auth_reqed(self, auth_header, host, req, headers):
509 # Reset the retry counter once for each request.
509 # Reset the retry counter once for each request.
510 if req is not self.retried_req:
510 if req is not self.retried_req:
511 self.retried_req = req
511 self.retried_req = req
512 self.retried = 0
512 self.retried = 0
513 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
513 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
514 self, auth_header, host, req, headers
514 self, auth_header, host, req, headers
515 )
515 )
516
516
517 def retry_http_basic_auth(self, host, req, realm):
517 def retry_http_basic_auth(self, host, req, realm):
518 user, pw = self.passwd.find_user_password(
518 user, pw = self.passwd.find_user_password(
519 realm, urllibcompat.getfullurl(req)
519 realm, urllibcompat.getfullurl(req)
520 )
520 )
521 if pw is not None:
521 if pw is not None:
522 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
522 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
523 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
523 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
524 if req.get_header(self.auth_header, None) == auth:
524 if req.get_header(self.auth_header, None) == auth:
525 return None
525 return None
526 self.auth = auth
526 self.auth = auth
527 req.add_unredirected_header(self.auth_header, auth)
527 req.add_unredirected_header(self.auth_header, auth)
528 return self.parent.open(req)
528 return self.parent.open(req)
529 else:
529 else:
530 return None
530 return None
531
531
532
532
533 class cookiehandler(urlreq.basehandler):
533 class cookiehandler(urlreq.basehandler):
534 def __init__(self, ui):
534 def __init__(self, ui):
535 self.cookiejar = None
535 self.cookiejar = None
536
536
537 cookiefile = ui.config(b'auth', b'cookiefile')
537 cookiefile = ui.config(b'auth', b'cookiefile')
538 if not cookiefile:
538 if not cookiefile:
539 return
539 return
540
540
541 cookiefile = util.expandpath(cookiefile)
541 cookiefile = util.expandpath(cookiefile)
542 try:
542 try:
543 cookiejar = util.cookielib.MozillaCookieJar(
543 cookiejar = util.cookielib.MozillaCookieJar(
544 pycompat.fsdecode(cookiefile)
544 pycompat.fsdecode(cookiefile)
545 )
545 )
546 cookiejar.load()
546 cookiejar.load()
547 self.cookiejar = cookiejar
547 self.cookiejar = cookiejar
548 except util.cookielib.LoadError as e:
548 except util.cookielib.LoadError as e:
549 ui.warn(
549 ui.warn(
550 _(
550 _(
551 b'(error loading cookie file %s: %s; continuing without '
551 b'(error loading cookie file %s: %s; continuing without '
552 b'cookies)\n'
552 b'cookies)\n'
553 )
553 )
554 % (cookiefile, stringutil.forcebytestr(e))
554 % (cookiefile, stringutil.forcebytestr(e))
555 )
555 )
556
556
557 def http_request(self, request):
557 def http_request(self, request):
558 if self.cookiejar:
558 if self.cookiejar:
559 self.cookiejar.add_cookie_header(request)
559 self.cookiejar.add_cookie_header(request)
560
560
561 return request
561 return request
562
562
563 def https_request(self, request):
563 def https_request(self, request):
564 if self.cookiejar:
564 if self.cookiejar:
565 self.cookiejar.add_cookie_header(request)
565 self.cookiejar.add_cookie_header(request)
566
566
567 return request
567 return request
568
568
569
569
570 handlerfuncs = []
570 handlerfuncs = []
571
571
572
572
573 def opener(
573 def opener(
574 ui,
574 ui,
575 authinfo=None,
575 authinfo=None,
576 useragent=None,
576 useragent=None,
577 loggingfh=None,
577 loggingfh=None,
578 loggingname=b's',
578 loggingname=b's',
579 loggingopts=None,
579 loggingopts=None,
580 sendaccept=True,
580 sendaccept=True,
581 ):
581 ):
582 '''
582 '''
583 construct an opener suitable for urllib2
583 construct an opener suitable for urllib2
584 authinfo will be added to the password manager
584 authinfo will be added to the password manager
585
585
586 The opener can be configured to log socket events if the various
586 The opener can be configured to log socket events if the various
587 ``logging*`` arguments are specified.
587 ``logging*`` arguments are specified.
588
588
589 ``loggingfh`` denotes a file object to log events to.
589 ``loggingfh`` denotes a file object to log events to.
590 ``loggingname`` denotes the name of the to print when logging.
590 ``loggingname`` denotes the name of the to print when logging.
591 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
591 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
592 ``util.socketobserver`` instance.
592 ``util.socketobserver`` instance.
593
593
594 ``sendaccept`` allows controlling whether the ``Accept`` request header
594 ``sendaccept`` allows controlling whether the ``Accept`` request header
595 is sent. The header is sent by default.
595 is sent. The header is sent by default.
596 '''
596 '''
597 timeout = ui.configwith(float, b'http', b'timeout')
597 timeout = ui.configwith(float, b'http', b'timeout')
598 handlers = []
598 handlers = []
599
599
600 if loggingfh:
600 if loggingfh:
601 handlers.append(
601 handlers.append(
602 logginghttphandler(
602 logginghttphandler(
603 loggingfh, loggingname, loggingopts or {}, timeout=timeout
603 loggingfh, loggingname, loggingopts or {}, timeout=timeout
604 )
604 )
605 )
605 )
606 # We don't yet support HTTPS when logging I/O. If we attempt to open
606 # We don't yet support HTTPS when logging I/O. If we attempt to open
607 # an HTTPS URL, we'll likely fail due to unknown protocol.
607 # an HTTPS URL, we'll likely fail due to unknown protocol.
608
608
609 else:
609 else:
610 handlers.append(httphandler(timeout=timeout))
610 handlers.append(httphandler(timeout=timeout))
611 if has_https:
611 if has_https:
612 handlers.append(httpshandler(ui, timeout=timeout))
612 handlers.append(httpshandler(ui, timeout=timeout))
613
613
614 handlers.append(proxyhandler(ui))
614 handlers.append(proxyhandler(ui))
615
615
616 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
616 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
617 if authinfo is not None:
617 if authinfo is not None:
618 realm, uris, user, passwd = authinfo
618 realm, uris, user, passwd = authinfo
619 saveduser, savedpass = passmgr.find_stored_password(uris[0])
619 saveduser, savedpass = passmgr.find_stored_password(uris[0])
620 if user != saveduser or passwd:
620 if user != saveduser or passwd:
621 passmgr.add_password(realm, uris, user, passwd)
621 passmgr.add_password(realm, uris, user, passwd)
622 ui.debug(
622 ui.debug(
623 b'http auth: user %s, password %s\n'
623 b'http auth: user %s, password %s\n'
624 % (user, passwd and b'*' * len(passwd) or b'not set')
624 % (user, passwd and b'*' * len(passwd) or b'not set')
625 )
625 )
626
626
627 handlers.extend(
627 handlers.extend(
628 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
628 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
629 )
629 )
630 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
630 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
631 handlers.append(cookiehandler(ui))
631 handlers.append(cookiehandler(ui))
632 opener = urlreq.buildopener(*handlers)
632 opener = urlreq.buildopener(*handlers)
633
633
634 # keepalive.py's handlers will populate these attributes if they exist.
634 # keepalive.py's handlers will populate these attributes if they exist.
635 opener.requestscount = 0
635 opener.requestscount = 0
636 opener.sentbytescount = 0
636 opener.sentbytescount = 0
637 opener.receivedbytescount = 0
637 opener.receivedbytescount = 0
638
638
639 # The user agent should should *NOT* be used by servers for e.g.
639 # The user agent should should *NOT* be used by servers for e.g.
640 # protocol detection or feature negotiation: there are other
640 # protocol detection or feature negotiation: there are other
641 # facilities for that.
641 # facilities for that.
642 #
642 #
643 # "mercurial/proto-1.0" was the original user agent string and
643 # "mercurial/proto-1.0" was the original user agent string and
644 # exists for backwards compatibility reasons.
644 # exists for backwards compatibility reasons.
645 #
645 #
646 # The "(Mercurial %s)" string contains the distribution
646 # The "(Mercurial %s)" string contains the distribution
647 # name and version. Other client implementations should choose their
647 # name and version. Other client implementations should choose their
648 # own distribution name. Since servers should not be using the user
648 # own distribution name. Since servers should not be using the user
649 # agent string for anything, clients should be able to define whatever
649 # agent string for anything, clients should be able to define whatever
650 # user agent they deem appropriate.
650 # user agent they deem appropriate.
651 #
651 #
652 # The custom user agent is for lfs, because unfortunately some servers
652 # The custom user agent is for lfs, because unfortunately some servers
653 # do look at this value.
653 # do look at this value.
654 if not useragent:
654 if not useragent:
655 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
655 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
656 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
656 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
657 else:
657 else:
658 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
658 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
659
659
660 # This header should only be needed by wire protocol requests. But it has
660 # This header should only be needed by wire protocol requests. But it has
661 # been sent on all requests since forever. We keep sending it for backwards
661 # been sent on all requests since forever. We keep sending it for backwards
662 # compatibility reasons. Modern versions of the wire protocol use
662 # compatibility reasons. Modern versions of the wire protocol use
663 # X-HgProto-<N> for advertising client support.
663 # X-HgProto-<N> for advertising client support.
664 if sendaccept:
664 if sendaccept:
665 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
665 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
666
666
667 return opener
667 return opener
668
668
669
669
670 def open(ui, url_, data=None, sendaccept=True):
670 def open(ui, url_, data=None, sendaccept=True):
671 u = util.url(url_)
671 u = util.url(url_)
672 if u.scheme:
672 if u.scheme:
673 u.scheme = u.scheme.lower()
673 u.scheme = u.scheme.lower()
674 url_, authinfo = u.authinfo()
674 url_, authinfo = u.authinfo()
675 else:
675 else:
676 path = util.normpath(os.path.abspath(url_))
676 path = util.normpath(os.path.abspath(url_))
677 url_ = b'file://' + pycompat.bytesurl(urlreq.pathname2url(path))
677 url_ = b'file://' + pycompat.bytesurl(
678 urlreq.pathname2url(pycompat.fsdecode(path))
679 )
678 authinfo = None
680 authinfo = None
679 return opener(ui, authinfo, sendaccept=sendaccept).open(
681 return opener(ui, authinfo, sendaccept=sendaccept).open(
680 pycompat.strurl(url_), data
682 pycompat.strurl(url_), data
681 )
683 )
682
684
683
685
684 def wrapresponse(resp):
686 def wrapresponse(resp):
685 """Wrap a response object with common error handlers.
687 """Wrap a response object with common error handlers.
686
688
687 This ensures that any I/O from any consumer raises the appropriate
689 This ensures that any I/O from any consumer raises the appropriate
688 error and messaging.
690 error and messaging.
689 """
691 """
690 origread = resp.read
692 origread = resp.read
691
693
692 class readerproxy(resp.__class__):
694 class readerproxy(resp.__class__):
693 def read(self, size=None):
695 def read(self, size=None):
694 try:
696 try:
695 return origread(size)
697 return origread(size)
696 except httplib.IncompleteRead as e:
698 except httplib.IncompleteRead as e:
697 # e.expected is an integer if length known or None otherwise.
699 # e.expected is an integer if length known or None otherwise.
698 if e.expected:
700 if e.expected:
699 got = len(e.partial)
701 got = len(e.partial)
700 total = e.expected + got
702 total = e.expected + got
701 msg = _(
703 msg = _(
702 b'HTTP request error (incomplete response; '
704 b'HTTP request error (incomplete response; '
703 b'expected %d bytes got %d)'
705 b'expected %d bytes got %d)'
704 ) % (total, got)
706 ) % (total, got)
705 else:
707 else:
706 msg = _(b'HTTP request error (incomplete response)')
708 msg = _(b'HTTP request error (incomplete response)')
707
709
708 raise error.PeerTransportError(
710 raise error.PeerTransportError(
709 msg,
711 msg,
710 hint=_(
712 hint=_(
711 b'this may be an intermittent network failure; '
713 b'this may be an intermittent network failure; '
712 b'if the error persists, consider contacting the '
714 b'if the error persists, consider contacting the '
713 b'network or server operator'
715 b'network or server operator'
714 ),
716 ),
715 )
717 )
716 except httplib.HTTPException as e:
718 except httplib.HTTPException as e:
717 raise error.PeerTransportError(
719 raise error.PeerTransportError(
718 _(b'HTTP request error (%s)') % e,
720 _(b'HTTP request error (%s)') % e,
719 hint=_(
721 hint=_(
720 b'this may be an intermittent network failure; '
722 b'this may be an intermittent network failure; '
721 b'if the error persists, consider contacting the '
723 b'if the error persists, consider contacting the '
722 b'network or server operator'
724 b'network or server operator'
723 ),
725 ),
724 )
726 )
725
727
726 resp.__class__ = readerproxy
728 resp.__class__ = readerproxy
General Comments 0
You need to be logged in to leave comments. Login now