##// END OF EJS Templates
url: show full url of proxy server in debug message...
Yuya Nishihara -
r36325:37a1501c default
parent child Browse files
Show More
@@ -1,540 +1,539 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import base64
12 import base64
13 import os
13 import os
14 import socket
14 import socket
15
15
16 from .i18n import _
16 from .i18n import _
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
27
28 httplib = util.httplib
28 httplib = util.httplib
29 stringio = util.stringio
29 stringio = util.stringio
30 urlerr = util.urlerr
30 urlerr = util.urlerr
31 urlreq = util.urlreq
31 urlreq = util.urlreq
32
32
33 def escape(s, quote=None):
33 def escape(s, quote=None):
34 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
34 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
35 If the optional flag quote is true, the quotation mark character (")
35 If the optional flag quote is true, the quotation mark character (")
36 is also translated.
36 is also translated.
37
37
38 This is the same as cgi.escape in Python, but always operates on
38 This is the same as cgi.escape in Python, but always operates on
39 bytes, whereas cgi.escape in Python 3 only works on unicodes.
39 bytes, whereas cgi.escape in Python 3 only works on unicodes.
40 '''
40 '''
41 s = s.replace(b"&", b"&amp;")
41 s = s.replace(b"&", b"&amp;")
42 s = s.replace(b"<", b"&lt;")
42 s = s.replace(b"<", b"&lt;")
43 s = s.replace(b">", b"&gt;")
43 s = s.replace(b">", b"&gt;")
44 if quote:
44 if quote:
45 s = s.replace(b'"', b"&quot;")
45 s = s.replace(b'"', b"&quot;")
46 return s
46 return s
47
47
48 class passwordmgr(object):
48 class passwordmgr(object):
49 def __init__(self, ui, passwddb):
49 def __init__(self, ui, passwddb):
50 self.ui = ui
50 self.ui = ui
51 self.passwddb = passwddb
51 self.passwddb = passwddb
52
52
53 def add_password(self, realm, uri, user, passwd):
53 def add_password(self, realm, uri, user, passwd):
54 return self.passwddb.add_password(realm, uri, user, passwd)
54 return self.passwddb.add_password(realm, uri, user, passwd)
55
55
56 def find_user_password(self, realm, authuri):
56 def find_user_password(self, realm, authuri):
57 authinfo = self.passwddb.find_user_password(realm, authuri)
57 authinfo = self.passwddb.find_user_password(realm, authuri)
58 user, passwd = authinfo
58 user, passwd = authinfo
59 if user and passwd:
59 if user and passwd:
60 self._writedebug(user, passwd)
60 self._writedebug(user, passwd)
61 return (user, passwd)
61 return (user, passwd)
62
62
63 if not user or not passwd:
63 if not user or not passwd:
64 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
64 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
65 if res:
65 if res:
66 group, auth = res
66 group, auth = res
67 user, passwd = auth.get('username'), auth.get('password')
67 user, passwd = auth.get('username'), auth.get('password')
68 self.ui.debug("using auth.%s.* for authentication\n" % group)
68 self.ui.debug("using auth.%s.* for authentication\n" % group)
69 if not user or not passwd:
69 if not user or not passwd:
70 u = util.url(authuri)
70 u = util.url(authuri)
71 u.query = None
71 u.query = None
72 if not self.ui.interactive():
72 if not self.ui.interactive():
73 raise error.Abort(_('http authorization required for %s') %
73 raise error.Abort(_('http authorization required for %s') %
74 util.hidepassword(str(u)))
74 util.hidepassword(str(u)))
75
75
76 self.ui.write(_("http authorization required for %s\n") %
76 self.ui.write(_("http authorization required for %s\n") %
77 util.hidepassword(str(u)))
77 util.hidepassword(str(u)))
78 self.ui.write(_("realm: %s\n") % realm)
78 self.ui.write(_("realm: %s\n") % realm)
79 if user:
79 if user:
80 self.ui.write(_("user: %s\n") % user)
80 self.ui.write(_("user: %s\n") % user)
81 else:
81 else:
82 user = self.ui.prompt(_("user:"), default=None)
82 user = self.ui.prompt(_("user:"), default=None)
83
83
84 if not passwd:
84 if not passwd:
85 passwd = self.ui.getpass()
85 passwd = self.ui.getpass()
86
86
87 self.passwddb.add_password(realm, authuri, user, passwd)
87 self.passwddb.add_password(realm, authuri, user, passwd)
88 self._writedebug(user, passwd)
88 self._writedebug(user, passwd)
89 return (user, passwd)
89 return (user, passwd)
90
90
91 def _writedebug(self, user, passwd):
91 def _writedebug(self, user, passwd):
92 msg = _('http auth: user %s, password %s\n')
92 msg = _('http auth: user %s, password %s\n')
93 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
93 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
94
94
95 def find_stored_password(self, authuri):
95 def find_stored_password(self, authuri):
96 return self.passwddb.find_user_password(None, authuri)
96 return self.passwddb.find_user_password(None, authuri)
97
97
98 class proxyhandler(urlreq.proxyhandler):
98 class proxyhandler(urlreq.proxyhandler):
99 def __init__(self, ui):
99 def __init__(self, ui):
100 proxyurl = (ui.config("http_proxy", "host") or
100 proxyurl = (ui.config("http_proxy", "host") or
101 encoding.environ.get('http_proxy'))
101 encoding.environ.get('http_proxy'))
102 # XXX proxyauthinfo = None
102 # XXX proxyauthinfo = None
103
103
104 if proxyurl:
104 if proxyurl:
105 # proxy can be proper url or host[:port]
105 # proxy can be proper url or host[:port]
106 if not (proxyurl.startswith('http:') or
106 if not (proxyurl.startswith('http:') or
107 proxyurl.startswith('https:')):
107 proxyurl.startswith('https:')):
108 proxyurl = 'http://' + proxyurl + '/'
108 proxyurl = 'http://' + proxyurl + '/'
109 proxy = util.url(proxyurl)
109 proxy = util.url(proxyurl)
110 if not proxy.user:
110 if not proxy.user:
111 proxy.user = ui.config("http_proxy", "user")
111 proxy.user = ui.config("http_proxy", "user")
112 proxy.passwd = ui.config("http_proxy", "passwd")
112 proxy.passwd = ui.config("http_proxy", "passwd")
113
113
114 # see if we should use a proxy for this url
114 # see if we should use a proxy for this url
115 no_list = ["localhost", "127.0.0.1"]
115 no_list = ["localhost", "127.0.0.1"]
116 no_list.extend([p.lower() for
116 no_list.extend([p.lower() for
117 p in ui.configlist("http_proxy", "no")])
117 p in ui.configlist("http_proxy", "no")])
118 no_list.extend([p.strip().lower() for
118 no_list.extend([p.strip().lower() for
119 p in encoding.environ.get("no_proxy", '').split(',')
119 p in encoding.environ.get("no_proxy", '').split(',')
120 if p.strip()])
120 if p.strip()])
121 # "http_proxy.always" config is for running tests on localhost
121 # "http_proxy.always" config is for running tests on localhost
122 if ui.configbool("http_proxy", "always"):
122 if ui.configbool("http_proxy", "always"):
123 self.no_list = []
123 self.no_list = []
124 else:
124 else:
125 self.no_list = no_list
125 self.no_list = no_list
126
126
127 proxyurl = str(proxy)
127 proxyurl = str(proxy)
128 proxies = {'http': proxyurl, 'https': proxyurl}
128 proxies = {'http': proxyurl, 'https': proxyurl}
129 ui.debug('proxying through http://%s:%s\n' %
129 ui.debug('proxying through %s\n' % util.hidepassword(proxyurl))
130 (proxy.host, proxy.port))
131 else:
130 else:
132 proxies = {}
131 proxies = {}
133
132
134 urlreq.proxyhandler.__init__(self, proxies)
133 urlreq.proxyhandler.__init__(self, proxies)
135 self.ui = ui
134 self.ui = ui
136
135
137 def proxy_open(self, req, proxy, type_):
136 def proxy_open(self, req, proxy, type_):
138 host = urllibcompat.gethost(req).split(':')[0]
137 host = urllibcompat.gethost(req).split(':')[0]
139 for e in self.no_list:
138 for e in self.no_list:
140 if host == e:
139 if host == e:
141 return None
140 return None
142 if e.startswith('*.') and host.endswith(e[2:]):
141 if e.startswith('*.') and host.endswith(e[2:]):
143 return None
142 return None
144 if e.startswith('.') and host.endswith(e[1:]):
143 if e.startswith('.') and host.endswith(e[1:]):
145 return None
144 return None
146
145
147 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
146 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
148
147
149 def _gen_sendfile(orgsend):
148 def _gen_sendfile(orgsend):
150 def _sendfile(self, data):
149 def _sendfile(self, data):
151 # send a file
150 # send a file
152 if isinstance(data, httpconnectionmod.httpsendfile):
151 if isinstance(data, httpconnectionmod.httpsendfile):
153 # if auth required, some data sent twice, so rewind here
152 # if auth required, some data sent twice, so rewind here
154 data.seek(0)
153 data.seek(0)
155 for chunk in util.filechunkiter(data):
154 for chunk in util.filechunkiter(data):
156 orgsend(self, chunk)
155 orgsend(self, chunk)
157 else:
156 else:
158 orgsend(self, data)
157 orgsend(self, data)
159 return _sendfile
158 return _sendfile
160
159
161 has_https = util.safehasattr(urlreq, 'httpshandler')
160 has_https = util.safehasattr(urlreq, 'httpshandler')
162
161
163 class httpconnection(keepalive.HTTPConnection):
162 class httpconnection(keepalive.HTTPConnection):
164 # must be able to send big bundle as stream.
163 # must be able to send big bundle as stream.
165 send = _gen_sendfile(keepalive.HTTPConnection.send)
164 send = _gen_sendfile(keepalive.HTTPConnection.send)
166
165
167 def getresponse(self):
166 def getresponse(self):
168 proxyres = getattr(self, 'proxyres', None)
167 proxyres = getattr(self, 'proxyres', None)
169 if proxyres:
168 if proxyres:
170 if proxyres.will_close:
169 if proxyres.will_close:
171 self.close()
170 self.close()
172 self.proxyres = None
171 self.proxyres = None
173 return proxyres
172 return proxyres
174 return keepalive.HTTPConnection.getresponse(self)
173 return keepalive.HTTPConnection.getresponse(self)
175
174
176 # general transaction handler to support different ways to handle
175 # general transaction handler to support different ways to handle
177 # HTTPS proxying before and after Python 2.6.3.
176 # HTTPS proxying before and after Python 2.6.3.
178 def _generic_start_transaction(handler, h, req):
177 def _generic_start_transaction(handler, h, req):
179 tunnel_host = getattr(req, '_tunnel_host', None)
178 tunnel_host = getattr(req, '_tunnel_host', None)
180 if tunnel_host:
179 if tunnel_host:
181 if tunnel_host[:7] not in ['http://', 'https:/']:
180 if tunnel_host[:7] not in ['http://', 'https:/']:
182 tunnel_host = 'https://' + tunnel_host
181 tunnel_host = 'https://' + tunnel_host
183 new_tunnel = True
182 new_tunnel = True
184 else:
183 else:
185 tunnel_host = urllibcompat.getselector(req)
184 tunnel_host = urllibcompat.getselector(req)
186 new_tunnel = False
185 new_tunnel = False
187
186
188 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
187 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
189 u = util.url(tunnel_host)
188 u = util.url(tunnel_host)
190 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
189 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
191 h.realhostport = ':'.join([u.host, (u.port or '443')])
190 h.realhostport = ':'.join([u.host, (u.port or '443')])
192 h.headers = req.headers.copy()
191 h.headers = req.headers.copy()
193 h.headers.update(handler.parent.addheaders)
192 h.headers.update(handler.parent.addheaders)
194 return
193 return
195
194
196 h.realhostport = None
195 h.realhostport = None
197 h.headers = None
196 h.headers = None
198
197
199 def _generic_proxytunnel(self):
198 def _generic_proxytunnel(self):
200 proxyheaders = dict(
199 proxyheaders = dict(
201 [(x, self.headers[x]) for x in self.headers
200 [(x, self.headers[x]) for x in self.headers
202 if x.lower().startswith('proxy-')])
201 if x.lower().startswith('proxy-')])
203 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
202 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
204 for header in proxyheaders.iteritems():
203 for header in proxyheaders.iteritems():
205 self.send('%s: %s\r\n' % header)
204 self.send('%s: %s\r\n' % header)
206 self.send('\r\n')
205 self.send('\r\n')
207
206
208 # majority of the following code is duplicated from
207 # majority of the following code is duplicated from
209 # httplib.HTTPConnection as there are no adequate places to
208 # httplib.HTTPConnection as there are no adequate places to
210 # override functions to provide the needed functionality
209 # override functions to provide the needed functionality
211 res = self.response_class(self.sock,
210 res = self.response_class(self.sock,
212 strict=self.strict,
211 strict=self.strict,
213 method=self._method)
212 method=self._method)
214
213
215 while True:
214 while True:
216 version, status, reason = res._read_status()
215 version, status, reason = res._read_status()
217 if status != httplib.CONTINUE:
216 if status != httplib.CONTINUE:
218 break
217 break
219 # skip lines that are all whitespace
218 # skip lines that are all whitespace
220 list(iter(lambda: res.fp.readline().strip(), ''))
219 list(iter(lambda: res.fp.readline().strip(), ''))
221 res.status = status
220 res.status = status
222 res.reason = reason.strip()
221 res.reason = reason.strip()
223
222
224 if res.status == 200:
223 if res.status == 200:
225 # skip lines until we find a blank line
224 # skip lines until we find a blank line
226 list(iter(res.fp.readline, '\r\n'))
225 list(iter(res.fp.readline, '\r\n'))
227 return True
226 return True
228
227
229 if version == 'HTTP/1.0':
228 if version == 'HTTP/1.0':
230 res.version = 10
229 res.version = 10
231 elif version.startswith('HTTP/1.'):
230 elif version.startswith('HTTP/1.'):
232 res.version = 11
231 res.version = 11
233 elif version == 'HTTP/0.9':
232 elif version == 'HTTP/0.9':
234 res.version = 9
233 res.version = 9
235 else:
234 else:
236 raise httplib.UnknownProtocol(version)
235 raise httplib.UnknownProtocol(version)
237
236
238 if res.version == 9:
237 if res.version == 9:
239 res.length = None
238 res.length = None
240 res.chunked = 0
239 res.chunked = 0
241 res.will_close = 1
240 res.will_close = 1
242 res.msg = httplib.HTTPMessage(stringio())
241 res.msg = httplib.HTTPMessage(stringio())
243 return False
242 return False
244
243
245 res.msg = httplib.HTTPMessage(res.fp)
244 res.msg = httplib.HTTPMessage(res.fp)
246 res.msg.fp = None
245 res.msg.fp = None
247
246
248 # are we using the chunked-style of transfer encoding?
247 # are we using the chunked-style of transfer encoding?
249 trenc = res.msg.getheader('transfer-encoding')
248 trenc = res.msg.getheader('transfer-encoding')
250 if trenc and trenc.lower() == "chunked":
249 if trenc and trenc.lower() == "chunked":
251 res.chunked = 1
250 res.chunked = 1
252 res.chunk_left = None
251 res.chunk_left = None
253 else:
252 else:
254 res.chunked = 0
253 res.chunked = 0
255
254
256 # will the connection close at the end of the response?
255 # will the connection close at the end of the response?
257 res.will_close = res._check_close()
256 res.will_close = res._check_close()
258
257
259 # do we have a Content-Length?
258 # do we have a Content-Length?
260 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
259 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
261 # transfer-encoding is "chunked"
260 # transfer-encoding is "chunked"
262 length = res.msg.getheader('content-length')
261 length = res.msg.getheader('content-length')
263 if length and not res.chunked:
262 if length and not res.chunked:
264 try:
263 try:
265 res.length = int(length)
264 res.length = int(length)
266 except ValueError:
265 except ValueError:
267 res.length = None
266 res.length = None
268 else:
267 else:
269 if res.length < 0: # ignore nonsensical negative lengths
268 if res.length < 0: # ignore nonsensical negative lengths
270 res.length = None
269 res.length = None
271 else:
270 else:
272 res.length = None
271 res.length = None
273
272
274 # does the body have a fixed length? (of zero)
273 # does the body have a fixed length? (of zero)
275 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
274 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
276 100 <= status < 200 or # 1xx codes
275 100 <= status < 200 or # 1xx codes
277 res._method == 'HEAD'):
276 res._method == 'HEAD'):
278 res.length = 0
277 res.length = 0
279
278
280 # if the connection remains open, and we aren't using chunked, and
279 # if the connection remains open, and we aren't using chunked, and
281 # a content-length was not provided, then assume that the connection
280 # a content-length was not provided, then assume that the connection
282 # WILL close.
281 # WILL close.
283 if (not res.will_close and
282 if (not res.will_close and
284 not res.chunked and
283 not res.chunked and
285 res.length is None):
284 res.length is None):
286 res.will_close = 1
285 res.will_close = 1
287
286
288 self.proxyres = res
287 self.proxyres = res
289
288
290 return False
289 return False
291
290
292 class httphandler(keepalive.HTTPHandler):
291 class httphandler(keepalive.HTTPHandler):
293 def http_open(self, req):
292 def http_open(self, req):
294 return self.do_open(httpconnection, req)
293 return self.do_open(httpconnection, req)
295
294
296 def _start_transaction(self, h, req):
295 def _start_transaction(self, h, req):
297 _generic_start_transaction(self, h, req)
296 _generic_start_transaction(self, h, req)
298 return keepalive.HTTPHandler._start_transaction(self, h, req)
297 return keepalive.HTTPHandler._start_transaction(self, h, req)
299
298
300 if has_https:
299 if has_https:
301 class httpsconnection(httplib.HTTPConnection):
300 class httpsconnection(httplib.HTTPConnection):
302 response_class = keepalive.HTTPResponse
301 response_class = keepalive.HTTPResponse
303 default_port = httplib.HTTPS_PORT
302 default_port = httplib.HTTPS_PORT
304 # must be able to send big bundle as stream.
303 # must be able to send big bundle as stream.
305 send = _gen_sendfile(keepalive.safesend)
304 send = _gen_sendfile(keepalive.safesend)
306 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
305 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
307
306
308 def __init__(self, host, port=None, key_file=None, cert_file=None,
307 def __init__(self, host, port=None, key_file=None, cert_file=None,
309 *args, **kwargs):
308 *args, **kwargs):
310 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
309 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
311 self.key_file = key_file
310 self.key_file = key_file
312 self.cert_file = cert_file
311 self.cert_file = cert_file
313
312
314 def connect(self):
313 def connect(self):
315 self.sock = socket.create_connection((self.host, self.port))
314 self.sock = socket.create_connection((self.host, self.port))
316
315
317 host = self.host
316 host = self.host
318 if self.realhostport: # use CONNECT proxy
317 if self.realhostport: # use CONNECT proxy
319 _generic_proxytunnel(self)
318 _generic_proxytunnel(self)
320 host = self.realhostport.rsplit(':', 1)[0]
319 host = self.realhostport.rsplit(':', 1)[0]
321 self.sock = sslutil.wrapsocket(
320 self.sock = sslutil.wrapsocket(
322 self.sock, self.key_file, self.cert_file, ui=self.ui,
321 self.sock, self.key_file, self.cert_file, ui=self.ui,
323 serverhostname=host)
322 serverhostname=host)
324 sslutil.validatesocket(self.sock)
323 sslutil.validatesocket(self.sock)
325
324
326 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
325 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
327 def __init__(self, ui):
326 def __init__(self, ui):
328 keepalive.KeepAliveHandler.__init__(self)
327 keepalive.KeepAliveHandler.__init__(self)
329 urlreq.httpshandler.__init__(self)
328 urlreq.httpshandler.__init__(self)
330 self.ui = ui
329 self.ui = ui
331 self.pwmgr = passwordmgr(self.ui,
330 self.pwmgr = passwordmgr(self.ui,
332 self.ui.httppasswordmgrdb)
331 self.ui.httppasswordmgrdb)
333
332
334 def _start_transaction(self, h, req):
333 def _start_transaction(self, h, req):
335 _generic_start_transaction(self, h, req)
334 _generic_start_transaction(self, h, req)
336 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
335 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
337
336
338 def https_open(self, req):
337 def https_open(self, req):
339 # urllibcompat.getfullurl() does not contain credentials
338 # urllibcompat.getfullurl() does not contain credentials
340 # and we may need them to match the certificates.
339 # and we may need them to match the certificates.
341 url = urllibcompat.getfullurl(req)
340 url = urllibcompat.getfullurl(req)
342 user, password = self.pwmgr.find_stored_password(url)
341 user, password = self.pwmgr.find_stored_password(url)
343 res = httpconnectionmod.readauthforuri(self.ui, url, user)
342 res = httpconnectionmod.readauthforuri(self.ui, url, user)
344 if res:
343 if res:
345 group, auth = res
344 group, auth = res
346 self.auth = auth
345 self.auth = auth
347 self.ui.debug("using auth.%s.* for authentication\n" % group)
346 self.ui.debug("using auth.%s.* for authentication\n" % group)
348 else:
347 else:
349 self.auth = None
348 self.auth = None
350 return self.do_open(self._makeconnection, req)
349 return self.do_open(self._makeconnection, req)
351
350
352 def _makeconnection(self, host, port=None, *args, **kwargs):
351 def _makeconnection(self, host, port=None, *args, **kwargs):
353 keyfile = None
352 keyfile = None
354 certfile = None
353 certfile = None
355
354
356 if len(args) >= 1: # key_file
355 if len(args) >= 1: # key_file
357 keyfile = args[0]
356 keyfile = args[0]
358 if len(args) >= 2: # cert_file
357 if len(args) >= 2: # cert_file
359 certfile = args[1]
358 certfile = args[1]
360 args = args[2:]
359 args = args[2:]
361
360
362 # if the user has specified different key/cert files in
361 # if the user has specified different key/cert files in
363 # hgrc, we prefer these
362 # hgrc, we prefer these
364 if self.auth and 'key' in self.auth and 'cert' in self.auth:
363 if self.auth and 'key' in self.auth and 'cert' in self.auth:
365 keyfile = self.auth['key']
364 keyfile = self.auth['key']
366 certfile = self.auth['cert']
365 certfile = self.auth['cert']
367
366
368 conn = httpsconnection(host, port, keyfile, certfile, *args,
367 conn = httpsconnection(host, port, keyfile, certfile, *args,
369 **kwargs)
368 **kwargs)
370 conn.ui = self.ui
369 conn.ui = self.ui
371 return conn
370 return conn
372
371
373 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
372 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
374 def __init__(self, *args, **kwargs):
373 def __init__(self, *args, **kwargs):
375 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
374 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
376 self.retried_req = None
375 self.retried_req = None
377
376
378 def reset_retry_count(self):
377 def reset_retry_count(self):
379 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
378 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
380 # forever. We disable reset_retry_count completely and reset in
379 # forever. We disable reset_retry_count completely and reset in
381 # http_error_auth_reqed instead.
380 # http_error_auth_reqed instead.
382 pass
381 pass
383
382
384 def http_error_auth_reqed(self, auth_header, host, req, headers):
383 def http_error_auth_reqed(self, auth_header, host, req, headers):
385 # Reset the retry counter once for each request.
384 # Reset the retry counter once for each request.
386 if req is not self.retried_req:
385 if req is not self.retried_req:
387 self.retried_req = req
386 self.retried_req = req
388 self.retried = 0
387 self.retried = 0
389 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
388 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
390 self, auth_header, host, req, headers)
389 self, auth_header, host, req, headers)
391
390
392 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
391 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
393 def __init__(self, *args, **kwargs):
392 def __init__(self, *args, **kwargs):
394 self.auth = None
393 self.auth = None
395 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
394 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
396 self.retried_req = None
395 self.retried_req = None
397
396
398 def http_request(self, request):
397 def http_request(self, request):
399 if self.auth:
398 if self.auth:
400 request.add_unredirected_header(self.auth_header, self.auth)
399 request.add_unredirected_header(self.auth_header, self.auth)
401
400
402 return request
401 return request
403
402
404 def https_request(self, request):
403 def https_request(self, request):
405 if self.auth:
404 if self.auth:
406 request.add_unredirected_header(self.auth_header, self.auth)
405 request.add_unredirected_header(self.auth_header, self.auth)
407
406
408 return request
407 return request
409
408
410 def reset_retry_count(self):
409 def reset_retry_count(self):
411 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
410 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
412 # forever. We disable reset_retry_count completely and reset in
411 # forever. We disable reset_retry_count completely and reset in
413 # http_error_auth_reqed instead.
412 # http_error_auth_reqed instead.
414 pass
413 pass
415
414
416 def http_error_auth_reqed(self, auth_header, host, req, headers):
415 def http_error_auth_reqed(self, auth_header, host, req, headers):
417 # Reset the retry counter once for each request.
416 # Reset the retry counter once for each request.
418 if req is not self.retried_req:
417 if req is not self.retried_req:
419 self.retried_req = req
418 self.retried_req = req
420 self.retried = 0
419 self.retried = 0
421 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
420 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
422 self, auth_header, host, req, headers)
421 self, auth_header, host, req, headers)
423
422
424 def retry_http_basic_auth(self, host, req, realm):
423 def retry_http_basic_auth(self, host, req, realm):
425 user, pw = self.passwd.find_user_password(
424 user, pw = self.passwd.find_user_password(
426 realm, urllibcompat.getfullurl(req))
425 realm, urllibcompat.getfullurl(req))
427 if pw is not None:
426 if pw is not None:
428 raw = "%s:%s" % (user, pw)
427 raw = "%s:%s" % (user, pw)
429 auth = 'Basic %s' % base64.b64encode(raw).strip()
428 auth = 'Basic %s' % base64.b64encode(raw).strip()
430 if req.get_header(self.auth_header, None) == auth:
429 if req.get_header(self.auth_header, None) == auth:
431 return None
430 return None
432 self.auth = auth
431 self.auth = auth
433 req.add_unredirected_header(self.auth_header, auth)
432 req.add_unredirected_header(self.auth_header, auth)
434 return self.parent.open(req)
433 return self.parent.open(req)
435 else:
434 else:
436 return None
435 return None
437
436
438 class cookiehandler(urlreq.basehandler):
437 class cookiehandler(urlreq.basehandler):
439 def __init__(self, ui):
438 def __init__(self, ui):
440 self.cookiejar = None
439 self.cookiejar = None
441
440
442 cookiefile = ui.config('auth', 'cookiefile')
441 cookiefile = ui.config('auth', 'cookiefile')
443 if not cookiefile:
442 if not cookiefile:
444 return
443 return
445
444
446 cookiefile = util.expandpath(cookiefile)
445 cookiefile = util.expandpath(cookiefile)
447 try:
446 try:
448 cookiejar = util.cookielib.MozillaCookieJar(cookiefile)
447 cookiejar = util.cookielib.MozillaCookieJar(cookiefile)
449 cookiejar.load()
448 cookiejar.load()
450 self.cookiejar = cookiejar
449 self.cookiejar = cookiejar
451 except util.cookielib.LoadError as e:
450 except util.cookielib.LoadError as e:
452 ui.warn(_('(error loading cookie file %s: %s; continuing without '
451 ui.warn(_('(error loading cookie file %s: %s; continuing without '
453 'cookies)\n') % (cookiefile, str(e)))
452 'cookies)\n') % (cookiefile, str(e)))
454
453
455 def http_request(self, request):
454 def http_request(self, request):
456 if self.cookiejar:
455 if self.cookiejar:
457 self.cookiejar.add_cookie_header(request)
456 self.cookiejar.add_cookie_header(request)
458
457
459 return request
458 return request
460
459
461 def https_request(self, request):
460 def https_request(self, request):
462 if self.cookiejar:
461 if self.cookiejar:
463 self.cookiejar.add_cookie_header(request)
462 self.cookiejar.add_cookie_header(request)
464
463
465 return request
464 return request
466
465
467 handlerfuncs = []
466 handlerfuncs = []
468
467
469 def opener(ui, authinfo=None, useragent=None):
468 def opener(ui, authinfo=None, useragent=None):
470 '''
469 '''
471 construct an opener suitable for urllib2
470 construct an opener suitable for urllib2
472 authinfo will be added to the password manager
471 authinfo will be added to the password manager
473 '''
472 '''
474 # experimental config: ui.usehttp2
473 # experimental config: ui.usehttp2
475 if ui.configbool('ui', 'usehttp2'):
474 if ui.configbool('ui', 'usehttp2'):
476 handlers = [
475 handlers = [
477 httpconnectionmod.http2handler(
476 httpconnectionmod.http2handler(
478 ui,
477 ui,
479 passwordmgr(ui, ui.httppasswordmgrdb))
478 passwordmgr(ui, ui.httppasswordmgrdb))
480 ]
479 ]
481 else:
480 else:
482 handlers = [httphandler()]
481 handlers = [httphandler()]
483 if has_https:
482 if has_https:
484 handlers.append(httpshandler(ui))
483 handlers.append(httpshandler(ui))
485
484
486 handlers.append(proxyhandler(ui))
485 handlers.append(proxyhandler(ui))
487
486
488 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
487 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
489 if authinfo is not None:
488 if authinfo is not None:
490 realm, uris, user, passwd = authinfo
489 realm, uris, user, passwd = authinfo
491 saveduser, savedpass = passmgr.find_stored_password(uris[0])
490 saveduser, savedpass = passmgr.find_stored_password(uris[0])
492 if user != saveduser or passwd:
491 if user != saveduser or passwd:
493 passmgr.add_password(realm, uris, user, passwd)
492 passmgr.add_password(realm, uris, user, passwd)
494 ui.debug('http auth: user %s, password %s\n' %
493 ui.debug('http auth: user %s, password %s\n' %
495 (user, passwd and '*' * len(passwd) or 'not set'))
494 (user, passwd and '*' * len(passwd) or 'not set'))
496
495
497 handlers.extend((httpbasicauthhandler(passmgr),
496 handlers.extend((httpbasicauthhandler(passmgr),
498 httpdigestauthhandler(passmgr)))
497 httpdigestauthhandler(passmgr)))
499 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
498 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
500 handlers.append(cookiehandler(ui))
499 handlers.append(cookiehandler(ui))
501 opener = urlreq.buildopener(*handlers)
500 opener = urlreq.buildopener(*handlers)
502
501
503 # The user agent should should *NOT* be used by servers for e.g.
502 # The user agent should should *NOT* be used by servers for e.g.
504 # protocol detection or feature negotiation: there are other
503 # protocol detection or feature negotiation: there are other
505 # facilities for that.
504 # facilities for that.
506 #
505 #
507 # "mercurial/proto-1.0" was the original user agent string and
506 # "mercurial/proto-1.0" was the original user agent string and
508 # exists for backwards compatibility reasons.
507 # exists for backwards compatibility reasons.
509 #
508 #
510 # The "(Mercurial %s)" string contains the distribution
509 # The "(Mercurial %s)" string contains the distribution
511 # name and version. Other client implementations should choose their
510 # name and version. Other client implementations should choose their
512 # own distribution name. Since servers should not be using the user
511 # own distribution name. Since servers should not be using the user
513 # agent string for anything, clients should be able to define whatever
512 # agent string for anything, clients should be able to define whatever
514 # user agent they deem appropriate.
513 # user agent they deem appropriate.
515 #
514 #
516 # The custom user agent is for lfs, because unfortunately some servers
515 # The custom user agent is for lfs, because unfortunately some servers
517 # do look at this value.
516 # do look at this value.
518 if not useragent:
517 if not useragent:
519 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
518 agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
520 opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
519 opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
521 else:
520 else:
522 opener.addheaders = [(r'User-agent', pycompat.sysstr(useragent))]
521 opener.addheaders = [(r'User-agent', pycompat.sysstr(useragent))]
523
522
524 # This header should only be needed by wire protocol requests. But it has
523 # This header should only be needed by wire protocol requests. But it has
525 # been sent on all requests since forever. We keep sending it for backwards
524 # been sent on all requests since forever. We keep sending it for backwards
526 # compatibility reasons. Modern versions of the wire protocol use
525 # compatibility reasons. Modern versions of the wire protocol use
527 # X-HgProto-<N> for advertising client support.
526 # X-HgProto-<N> for advertising client support.
528 opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
527 opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
529 return opener
528 return opener
530
529
531 def open(ui, url_, data=None):
530 def open(ui, url_, data=None):
532 u = util.url(url_)
531 u = util.url(url_)
533 if u.scheme:
532 if u.scheme:
534 u.scheme = u.scheme.lower()
533 u.scheme = u.scheme.lower()
535 url_, authinfo = u.authinfo()
534 url_, authinfo = u.authinfo()
536 else:
535 else:
537 path = util.normpath(os.path.abspath(url_))
536 path = util.normpath(os.path.abspath(url_))
538 url_ = 'file://' + urlreq.pathname2url(path)
537 url_ = 'file://' + urlreq.pathname2url(path)
539 authinfo = None
538 authinfo = None
540 return opener(ui, authinfo).open(url_, data)
539 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now