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