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