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