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