##// END OF EJS Templates
http2: mark experimental and developer options
Matt Mackall -
r25837:d343806d default
parent child Browse files
Show More
@@ -1,283 +1,284 b''
1 # httpconnection.py - urllib2 handler for new http support
1 # httpconnection.py - urllib2 handler for new http support
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 # Copyright 2011 Google, Inc.
6 # Copyright 2011 Google, Inc.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10 import logging
10 import logging
11 import socket
11 import socket
12 import urllib
12 import urllib
13 import urllib2
13 import urllib2
14 import os
14 import os
15
15
16 from mercurial import httpclient
16 from mercurial import httpclient
17 from mercurial import sslutil
17 from mercurial import sslutil
18 from mercurial import util
18 from mercurial import util
19 from mercurial.i18n import _
19 from mercurial.i18n import _
20
20
21 # moved here from url.py to avoid a cycle
21 # moved here from url.py to avoid a cycle
22 class httpsendfile(object):
22 class httpsendfile(object):
23 """This is a wrapper around the objects returned by python's "open".
23 """This is a wrapper around the objects returned by python's "open".
24
24
25 Its purpose is to send file-like objects via HTTP.
25 Its purpose is to send file-like objects via HTTP.
26 It do however not define a __len__ attribute because the length
26 It do however not define a __len__ attribute because the length
27 might be more than Py_ssize_t can handle.
27 might be more than Py_ssize_t can handle.
28 """
28 """
29
29
30 def __init__(self, ui, *args, **kwargs):
30 def __init__(self, ui, *args, **kwargs):
31 # We can't just "self._data = open(*args, **kwargs)" here because there
31 # We can't just "self._data = open(*args, **kwargs)" here because there
32 # is an "open" function defined in this module that shadows the global
32 # is an "open" function defined in this module that shadows the global
33 # one
33 # one
34 self.ui = ui
34 self.ui = ui
35 self._data = open(*args, **kwargs)
35 self._data = open(*args, **kwargs)
36 self.seek = self._data.seek
36 self.seek = self._data.seek
37 self.close = self._data.close
37 self.close = self._data.close
38 self.write = self._data.write
38 self.write = self._data.write
39 self.length = os.fstat(self._data.fileno()).st_size
39 self.length = os.fstat(self._data.fileno()).st_size
40 self._pos = 0
40 self._pos = 0
41 self._total = self.length // 1024 * 2
41 self._total = self.length // 1024 * 2
42
42
43 def read(self, *args, **kwargs):
43 def read(self, *args, **kwargs):
44 try:
44 try:
45 ret = self._data.read(*args, **kwargs)
45 ret = self._data.read(*args, **kwargs)
46 except EOFError:
46 except EOFError:
47 self.ui.progress(_('sending'), None)
47 self.ui.progress(_('sending'), None)
48 self._pos += len(ret)
48 self._pos += len(ret)
49 # We pass double the max for total because we currently have
49 # We pass double the max for total because we currently have
50 # to send the bundle twice in the case of a server that
50 # to send the bundle twice in the case of a server that
51 # requires authentication. Since we can't know until we try
51 # requires authentication. Since we can't know until we try
52 # once whether authentication will be required, just lie to
52 # once whether authentication will be required, just lie to
53 # the user and maybe the push succeeds suddenly at 50%.
53 # the user and maybe the push succeeds suddenly at 50%.
54 self.ui.progress(_('sending'), self._pos // 1024,
54 self.ui.progress(_('sending'), self._pos // 1024,
55 unit=_('kb'), total=self._total)
55 unit=_('kb'), total=self._total)
56 return ret
56 return ret
57
57
58 # moved here from url.py to avoid a cycle
58 # moved here from url.py to avoid a cycle
59 def readauthforuri(ui, uri, user):
59 def readauthforuri(ui, uri, user):
60 # Read configuration
60 # Read configuration
61 config = dict()
61 config = dict()
62 for key, val in ui.configitems('auth'):
62 for key, val in ui.configitems('auth'):
63 if '.' not in key:
63 if '.' not in key:
64 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
64 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
65 continue
65 continue
66 group, setting = key.rsplit('.', 1)
66 group, setting = key.rsplit('.', 1)
67 gdict = config.setdefault(group, dict())
67 gdict = config.setdefault(group, dict())
68 if setting in ('username', 'cert', 'key'):
68 if setting in ('username', 'cert', 'key'):
69 val = util.expandpath(val)
69 val = util.expandpath(val)
70 gdict[setting] = val
70 gdict[setting] = val
71
71
72 # Find the best match
72 # Find the best match
73 scheme, hostpath = uri.split('://', 1)
73 scheme, hostpath = uri.split('://', 1)
74 bestuser = None
74 bestuser = None
75 bestlen = 0
75 bestlen = 0
76 bestauth = None
76 bestauth = None
77 for group, auth in config.iteritems():
77 for group, auth in config.iteritems():
78 if user and user != auth.get('username', user):
78 if user and user != auth.get('username', user):
79 # If a username was set in the URI, the entry username
79 # If a username was set in the URI, the entry username
80 # must either match it or be unset
80 # must either match it or be unset
81 continue
81 continue
82 prefix = auth.get('prefix')
82 prefix = auth.get('prefix')
83 if not prefix:
83 if not prefix:
84 continue
84 continue
85 p = prefix.split('://', 1)
85 p = prefix.split('://', 1)
86 if len(p) > 1:
86 if len(p) > 1:
87 schemes, prefix = [p[0]], p[1]
87 schemes, prefix = [p[0]], p[1]
88 else:
88 else:
89 schemes = (auth.get('schemes') or 'https').split()
89 schemes = (auth.get('schemes') or 'https').split()
90 if (prefix == '*' or hostpath.startswith(prefix)) and \
90 if (prefix == '*' or hostpath.startswith(prefix)) and \
91 (len(prefix) > bestlen or (len(prefix) == bestlen and \
91 (len(prefix) > bestlen or (len(prefix) == bestlen and \
92 not bestuser and 'username' in auth)) \
92 not bestuser and 'username' in auth)) \
93 and scheme in schemes:
93 and scheme in schemes:
94 bestlen = len(prefix)
94 bestlen = len(prefix)
95 bestauth = group, auth
95 bestauth = group, auth
96 bestuser = auth.get('username')
96 bestuser = auth.get('username')
97 if user and not bestuser:
97 if user and not bestuser:
98 auth['username'] = user
98 auth['username'] = user
99 return bestauth
99 return bestauth
100
100
101 # Mercurial (at least until we can remove the old codepath) requires
101 # Mercurial (at least until we can remove the old codepath) requires
102 # that the http response object be sufficiently file-like, so we
102 # that the http response object be sufficiently file-like, so we
103 # provide a close() method here.
103 # provide a close() method here.
104 class HTTPResponse(httpclient.HTTPResponse):
104 class HTTPResponse(httpclient.HTTPResponse):
105 def close(self):
105 def close(self):
106 pass
106 pass
107
107
108 class HTTPConnection(httpclient.HTTPConnection):
108 class HTTPConnection(httpclient.HTTPConnection):
109 response_class = HTTPResponse
109 response_class = HTTPResponse
110 def request(self, method, uri, body=None, headers={}):
110 def request(self, method, uri, body=None, headers={}):
111 if isinstance(body, httpsendfile):
111 if isinstance(body, httpsendfile):
112 body.seek(0)
112 body.seek(0)
113 httpclient.HTTPConnection.request(self, method, uri, body=body,
113 httpclient.HTTPConnection.request(self, method, uri, body=body,
114 headers=headers)
114 headers=headers)
115
115
116
116
117 _configuredlogging = False
117 _configuredlogging = False
118 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
118 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
119 # Subclass BOTH of these because otherwise urllib2 "helpfully"
119 # Subclass BOTH of these because otherwise urllib2 "helpfully"
120 # reinserts them since it notices we don't include any subclasses of
120 # reinserts them since it notices we don't include any subclasses of
121 # them.
121 # them.
122 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
122 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
123 def __init__(self, ui, pwmgr):
123 def __init__(self, ui, pwmgr):
124 global _configuredlogging
124 global _configuredlogging
125 urllib2.AbstractHTTPHandler.__init__(self)
125 urllib2.AbstractHTTPHandler.__init__(self)
126 self.ui = ui
126 self.ui = ui
127 self.pwmgr = pwmgr
127 self.pwmgr = pwmgr
128 self._connections = {}
128 self._connections = {}
129 # developer config: ui.http2debuglevel
129 loglevel = ui.config('ui', 'http2debuglevel', default=None)
130 loglevel = ui.config('ui', 'http2debuglevel', default=None)
130 if loglevel and not _configuredlogging:
131 if loglevel and not _configuredlogging:
131 _configuredlogging = True
132 _configuredlogging = True
132 logger = logging.getLogger('mercurial.httpclient')
133 logger = logging.getLogger('mercurial.httpclient')
133 logger.setLevel(getattr(logging, loglevel.upper()))
134 logger.setLevel(getattr(logging, loglevel.upper()))
134 handler = logging.StreamHandler()
135 handler = logging.StreamHandler()
135 handler.setFormatter(logging.Formatter(LOGFMT))
136 handler.setFormatter(logging.Formatter(LOGFMT))
136 logger.addHandler(handler)
137 logger.addHandler(handler)
137
138
138 def close_all(self):
139 def close_all(self):
139 """Close and remove all connection objects being kept for reuse."""
140 """Close and remove all connection objects being kept for reuse."""
140 for openconns in self._connections.values():
141 for openconns in self._connections.values():
141 for conn in openconns:
142 for conn in openconns:
142 conn.close()
143 conn.close()
143 self._connections = {}
144 self._connections = {}
144
145
145 # shamelessly borrowed from urllib2.AbstractHTTPHandler
146 # shamelessly borrowed from urllib2.AbstractHTTPHandler
146 def do_open(self, http_class, req, use_ssl):
147 def do_open(self, http_class, req, use_ssl):
147 """Return an addinfourl object for the request, using http_class.
148 """Return an addinfourl object for the request, using http_class.
148
149
149 http_class must implement the HTTPConnection API from httplib.
150 http_class must implement the HTTPConnection API from httplib.
150 The addinfourl return value is a file-like object. It also
151 The addinfourl return value is a file-like object. It also
151 has methods and attributes including:
152 has methods and attributes including:
152 - info(): return a mimetools.Message object for the headers
153 - info(): return a mimetools.Message object for the headers
153 - geturl(): return the original request URL
154 - geturl(): return the original request URL
154 - code: HTTP status code
155 - code: HTTP status code
155 """
156 """
156 # If using a proxy, the host returned by get_host() is
157 # If using a proxy, the host returned by get_host() is
157 # actually the proxy. On Python 2.6.1, the real destination
158 # actually the proxy. On Python 2.6.1, the real destination
158 # hostname is encoded in the URI in the urllib2 request
159 # hostname is encoded in the URI in the urllib2 request
159 # object. On Python 2.6.5, it's stored in the _tunnel_host
160 # object. On Python 2.6.5, it's stored in the _tunnel_host
160 # attribute which has no accessor.
161 # attribute which has no accessor.
161 tunhost = getattr(req, '_tunnel_host', None)
162 tunhost = getattr(req, '_tunnel_host', None)
162 host = req.get_host()
163 host = req.get_host()
163 if tunhost:
164 if tunhost:
164 proxyhost = host
165 proxyhost = host
165 host = tunhost
166 host = tunhost
166 elif req.has_proxy():
167 elif req.has_proxy():
167 proxyhost = req.get_host()
168 proxyhost = req.get_host()
168 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
169 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
169 else:
170 else:
170 proxyhost = None
171 proxyhost = None
171
172
172 if proxyhost:
173 if proxyhost:
173 if ':' in proxyhost:
174 if ':' in proxyhost:
174 # Note: this means we'll explode if we try and use an
175 # Note: this means we'll explode if we try and use an
175 # IPv6 http proxy. This isn't a regression, so we
176 # IPv6 http proxy. This isn't a regression, so we
176 # won't worry about it for now.
177 # won't worry about it for now.
177 proxyhost, proxyport = proxyhost.rsplit(':', 1)
178 proxyhost, proxyport = proxyhost.rsplit(':', 1)
178 else:
179 else:
179 proxyport = 3128 # squid default
180 proxyport = 3128 # squid default
180 proxy = (proxyhost, proxyport)
181 proxy = (proxyhost, proxyport)
181 else:
182 else:
182 proxy = None
183 proxy = None
183
184
184 if not host:
185 if not host:
185 raise urllib2.URLError('no host given')
186 raise urllib2.URLError('no host given')
186
187
187 connkey = use_ssl, host, proxy
188 connkey = use_ssl, host, proxy
188 allconns = self._connections.get(connkey, [])
189 allconns = self._connections.get(connkey, [])
189 conns = [c for c in allconns if not c.busy()]
190 conns = [c for c in allconns if not c.busy()]
190 if conns:
191 if conns:
191 h = conns[0]
192 h = conns[0]
192 else:
193 else:
193 if allconns:
194 if allconns:
194 self.ui.debug('all connections for %s busy, making a new '
195 self.ui.debug('all connections for %s busy, making a new '
195 'one\n' % host)
196 'one\n' % host)
196 timeout = None
197 timeout = None
197 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
198 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
198 timeout = req.timeout
199 timeout = req.timeout
199 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
200 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
200 self._connections.setdefault(connkey, []).append(h)
201 self._connections.setdefault(connkey, []).append(h)
201
202
202 headers = dict(req.headers)
203 headers = dict(req.headers)
203 headers.update(req.unredirected_hdrs)
204 headers.update(req.unredirected_hdrs)
204 headers = dict(
205 headers = dict(
205 (name.title(), val) for name, val in headers.items())
206 (name.title(), val) for name, val in headers.items())
206 try:
207 try:
207 path = req.get_selector()
208 path = req.get_selector()
208 if '://' in path:
209 if '://' in path:
209 path = path.split('://', 1)[1].split('/', 1)[1]
210 path = path.split('://', 1)[1].split('/', 1)[1]
210 if path[0] != '/':
211 if path[0] != '/':
211 path = '/' + path
212 path = '/' + path
212 h.request(req.get_method(), path, req.data, headers)
213 h.request(req.get_method(), path, req.data, headers)
213 r = h.getresponse()
214 r = h.getresponse()
214 except socket.error as err: # XXX what error?
215 except socket.error as err: # XXX what error?
215 raise urllib2.URLError(err)
216 raise urllib2.URLError(err)
216
217
217 # Pick apart the HTTPResponse object to get the addinfourl
218 # Pick apart the HTTPResponse object to get the addinfourl
218 # object initialized properly.
219 # object initialized properly.
219 r.recv = r.read
220 r.recv = r.read
220
221
221 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
222 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
222 resp.code = r.status
223 resp.code = r.status
223 resp.msg = r.reason
224 resp.msg = r.reason
224 return resp
225 return resp
225
226
226 # httplib always uses the given host/port as the socket connect
227 # httplib always uses the given host/port as the socket connect
227 # target, and then allows full URIs in the request path, which it
228 # target, and then allows full URIs in the request path, which it
228 # then observes and treats as a signal to do proxying instead.
229 # then observes and treats as a signal to do proxying instead.
229 def http_open(self, req):
230 def http_open(self, req):
230 if req.get_full_url().startswith('https'):
231 if req.get_full_url().startswith('https'):
231 return self.https_open(req)
232 return self.https_open(req)
232 def makehttpcon(*args, **kwargs):
233 def makehttpcon(*args, **kwargs):
233 k2 = dict(kwargs)
234 k2 = dict(kwargs)
234 k2['use_ssl'] = False
235 k2['use_ssl'] = False
235 return HTTPConnection(*args, **k2)
236 return HTTPConnection(*args, **k2)
236 return self.do_open(makehttpcon, req, False)
237 return self.do_open(makehttpcon, req, False)
237
238
238 def https_open(self, req):
239 def https_open(self, req):
239 # req.get_full_url() does not contain credentials and we may
240 # req.get_full_url() does not contain credentials and we may
240 # need them to match the certificates.
241 # need them to match the certificates.
241 url = req.get_full_url()
242 url = req.get_full_url()
242 user, password = self.pwmgr.find_stored_password(url)
243 user, password = self.pwmgr.find_stored_password(url)
243 res = readauthforuri(self.ui, url, user)
244 res = readauthforuri(self.ui, url, user)
244 if res:
245 if res:
245 group, auth = res
246 group, auth = res
246 self.auth = auth
247 self.auth = auth
247 self.ui.debug("using auth.%s.* for authentication\n" % group)
248 self.ui.debug("using auth.%s.* for authentication\n" % group)
248 else:
249 else:
249 self.auth = None
250 self.auth = None
250 return self.do_open(self._makesslconnection, req, True)
251 return self.do_open(self._makesslconnection, req, True)
251
252
252 def _makesslconnection(self, host, port=443, *args, **kwargs):
253 def _makesslconnection(self, host, port=443, *args, **kwargs):
253 keyfile = None
254 keyfile = None
254 certfile = None
255 certfile = None
255
256
256 if args: # key_file
257 if args: # key_file
257 keyfile = args.pop(0)
258 keyfile = args.pop(0)
258 if args: # cert_file
259 if args: # cert_file
259 certfile = args.pop(0)
260 certfile = args.pop(0)
260
261
261 # if the user has specified different key/cert files in
262 # if the user has specified different key/cert files in
262 # hgrc, we prefer these
263 # hgrc, we prefer these
263 if self.auth and 'key' in self.auth and 'cert' in self.auth:
264 if self.auth and 'key' in self.auth and 'cert' in self.auth:
264 keyfile = self.auth['key']
265 keyfile = self.auth['key']
265 certfile = self.auth['cert']
266 certfile = self.auth['cert']
266
267
267 # let host port take precedence
268 # let host port take precedence
268 if ':' in host and '[' not in host or ']:' in host:
269 if ':' in host and '[' not in host or ']:' in host:
269 host, port = host.rsplit(':', 1)
270 host, port = host.rsplit(':', 1)
270 port = int(port)
271 port = int(port)
271 if '[' in host:
272 if '[' in host:
272 host = host[1:-1]
273 host = host[1:-1]
273
274
274 kwargs['keyfile'] = keyfile
275 kwargs['keyfile'] = keyfile
275 kwargs['certfile'] = certfile
276 kwargs['certfile'] = certfile
276
277
277 kwargs.update(sslutil.sslkwargs(self.ui, host))
278 kwargs.update(sslutil.sslkwargs(self.ui, host))
278
279
279 con = HTTPConnection(host, port, use_ssl=True,
280 con = HTTPConnection(host, port, use_ssl=True,
280 ssl_wrap_socket=sslutil.wrapsocket,
281 ssl_wrap_socket=sslutil.wrapsocket,
281 ssl_validator=sslutil.validator(self.ui, host),
282 ssl_validator=sslutil.validator(self.ui, host),
282 **kwargs)
283 **kwargs)
283 return con
284 return con
@@ -1,507 +1,508 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 import urllib, urllib2, httplib, os, socket, cStringIO, base64
10 import urllib, urllib2, httplib, os, socket, cStringIO, base64
11 from i18n import _
11 from i18n import _
12 import keepalive, util, sslutil
12 import keepalive, util, sslutil
13 import httpconnection as httpconnectionmod
13 import httpconnection as httpconnectionmod
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 user, passwd = authinfo
23 user, passwd = authinfo
24 if user and passwd:
24 if user and passwd:
25 self._writedebug(user, passwd)
25 self._writedebug(user, passwd)
26 return (user, passwd)
26 return (user, passwd)
27
27
28 if not user or not passwd:
28 if not user or not passwd:
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
30 if res:
30 if res:
31 group, auth = res
31 group, auth = res
32 user, passwd = auth.get('username'), auth.get('password')
32 user, passwd = auth.get('username'), auth.get('password')
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
34 if not user or not passwd:
34 if not user or not passwd:
35 u = util.url(authuri)
35 u = util.url(authuri)
36 u.query = None
36 u.query = None
37 if not self.ui.interactive():
37 if not self.ui.interactive():
38 raise util.Abort(_('http authorization required for %s') %
38 raise util.Abort(_('http authorization required for %s') %
39 util.hidepassword(str(u)))
39 util.hidepassword(str(u)))
40
40
41 self.ui.write(_("http authorization required for %s\n") %
41 self.ui.write(_("http authorization required for %s\n") %
42 util.hidepassword(str(u)))
42 util.hidepassword(str(u)))
43 self.ui.write(_("realm: %s\n") % realm)
43 self.ui.write(_("realm: %s\n") % realm)
44 if user:
44 if user:
45 self.ui.write(_("user: %s\n") % user)
45 self.ui.write(_("user: %s\n") % user)
46 else:
46 else:
47 user = self.ui.prompt(_("user:"), default=None)
47 user = self.ui.prompt(_("user:"), default=None)
48
48
49 if not passwd:
49 if not passwd:
50 passwd = self.ui.getpass()
50 passwd = self.ui.getpass()
51
51
52 self.add_password(realm, authuri, user, passwd)
52 self.add_password(realm, authuri, user, passwd)
53 self._writedebug(user, passwd)
53 self._writedebug(user, passwd)
54 return (user, passwd)
54 return (user, passwd)
55
55
56 def _writedebug(self, user, passwd):
56 def _writedebug(self, user, passwd):
57 msg = _('http auth: user %s, password %s\n')
57 msg = _('http auth: user %s, password %s\n')
58 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
58 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
59
59
60 def find_stored_password(self, authuri):
60 def find_stored_password(self, authuri):
61 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
61 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
62 self, None, authuri)
62 self, None, authuri)
63
63
64 class proxyhandler(urllib2.ProxyHandler):
64 class proxyhandler(urllib2.ProxyHandler):
65 def __init__(self, ui):
65 def __init__(self, ui):
66 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
66 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
67 # XXX proxyauthinfo = None
67 # XXX proxyauthinfo = None
68
68
69 if proxyurl:
69 if proxyurl:
70 # proxy can be proper url or host[:port]
70 # proxy can be proper url or host[:port]
71 if not (proxyurl.startswith('http:') or
71 if not (proxyurl.startswith('http:') or
72 proxyurl.startswith('https:')):
72 proxyurl.startswith('https:')):
73 proxyurl = 'http://' + proxyurl + '/'
73 proxyurl = 'http://' + proxyurl + '/'
74 proxy = util.url(proxyurl)
74 proxy = util.url(proxyurl)
75 if not proxy.user:
75 if not proxy.user:
76 proxy.user = ui.config("http_proxy", "user")
76 proxy.user = ui.config("http_proxy", "user")
77 proxy.passwd = ui.config("http_proxy", "passwd")
77 proxy.passwd = ui.config("http_proxy", "passwd")
78
78
79 # see if we should use a proxy for this url
79 # see if we should use a proxy for this url
80 no_list = ["localhost", "127.0.0.1"]
80 no_list = ["localhost", "127.0.0.1"]
81 no_list.extend([p.lower() for
81 no_list.extend([p.lower() for
82 p in ui.configlist("http_proxy", "no")])
82 p in ui.configlist("http_proxy", "no")])
83 no_list.extend([p.strip().lower() for
83 no_list.extend([p.strip().lower() for
84 p in os.getenv("no_proxy", '').split(',')
84 p in os.getenv("no_proxy", '').split(',')
85 if p.strip()])
85 if p.strip()])
86 # "http_proxy.always" config is for running tests on localhost
86 # "http_proxy.always" config is for running tests on localhost
87 if ui.configbool("http_proxy", "always"):
87 if ui.configbool("http_proxy", "always"):
88 self.no_list = []
88 self.no_list = []
89 else:
89 else:
90 self.no_list = no_list
90 self.no_list = no_list
91
91
92 proxyurl = str(proxy)
92 proxyurl = str(proxy)
93 proxies = {'http': proxyurl, 'https': proxyurl}
93 proxies = {'http': proxyurl, 'https': proxyurl}
94 ui.debug('proxying through http://%s:%s\n' %
94 ui.debug('proxying through http://%s:%s\n' %
95 (proxy.host, proxy.port))
95 (proxy.host, proxy.port))
96 else:
96 else:
97 proxies = {}
97 proxies = {}
98
98
99 # urllib2 takes proxy values from the environment and those
99 # urllib2 takes proxy values from the environment and those
100 # will take precedence if found. So, if there's a config entry
100 # will take precedence if found. So, if there's a config entry
101 # defining a proxy, drop the environment ones
101 # defining a proxy, drop the environment ones
102 if ui.config("http_proxy", "host"):
102 if ui.config("http_proxy", "host"):
103 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
103 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
104 try:
104 try:
105 if env in os.environ:
105 if env in os.environ:
106 del os.environ[env]
106 del os.environ[env]
107 except OSError:
107 except OSError:
108 pass
108 pass
109
109
110 urllib2.ProxyHandler.__init__(self, proxies)
110 urllib2.ProxyHandler.__init__(self, proxies)
111 self.ui = ui
111 self.ui = ui
112
112
113 def proxy_open(self, req, proxy, type_):
113 def proxy_open(self, req, proxy, type_):
114 host = req.get_host().split(':')[0]
114 host = req.get_host().split(':')[0]
115 for e in self.no_list:
115 for e in self.no_list:
116 if host == e:
116 if host == e:
117 return None
117 return None
118 if e.startswith('*.') and host.endswith(e[2:]):
118 if e.startswith('*.') and host.endswith(e[2:]):
119 return None
119 return None
120 if e.startswith('.') and host.endswith(e[1:]):
120 if e.startswith('.') and host.endswith(e[1:]):
121 return None
121 return None
122
122
123 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
123 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
124
124
125 def _gen_sendfile(orgsend):
125 def _gen_sendfile(orgsend):
126 def _sendfile(self, data):
126 def _sendfile(self, data):
127 # send a file
127 # send a file
128 if isinstance(data, httpconnectionmod.httpsendfile):
128 if isinstance(data, httpconnectionmod.httpsendfile):
129 # if auth required, some data sent twice, so rewind here
129 # if auth required, some data sent twice, so rewind here
130 data.seek(0)
130 data.seek(0)
131 for chunk in util.filechunkiter(data):
131 for chunk in util.filechunkiter(data):
132 orgsend(self, chunk)
132 orgsend(self, chunk)
133 else:
133 else:
134 orgsend(self, data)
134 orgsend(self, data)
135 return _sendfile
135 return _sendfile
136
136
137 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
137 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
138 if has_https:
138 if has_https:
139 try:
139 try:
140 _create_connection = socket.create_connection
140 _create_connection = socket.create_connection
141 except AttributeError:
141 except AttributeError:
142 _GLOBAL_DEFAULT_TIMEOUT = object()
142 _GLOBAL_DEFAULT_TIMEOUT = object()
143
143
144 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
144 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
145 source_address=None):
145 source_address=None):
146 # lifted from Python 2.6
146 # lifted from Python 2.6
147
147
148 msg = "getaddrinfo returns an empty list"
148 msg = "getaddrinfo returns an empty list"
149 host, port = address
149 host, port = address
150 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
150 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
151 af, socktype, proto, canonname, sa = res
151 af, socktype, proto, canonname, sa = res
152 sock = None
152 sock = None
153 try:
153 try:
154 sock = socket.socket(af, socktype, proto)
154 sock = socket.socket(af, socktype, proto)
155 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
155 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
156 sock.settimeout(timeout)
156 sock.settimeout(timeout)
157 if source_address:
157 if source_address:
158 sock.bind(source_address)
158 sock.bind(source_address)
159 sock.connect(sa)
159 sock.connect(sa)
160 return sock
160 return sock
161
161
162 except socket.error as msg:
162 except socket.error as msg:
163 if sock is not None:
163 if sock is not None:
164 sock.close()
164 sock.close()
165
165
166 raise socket.error(msg)
166 raise socket.error(msg)
167
167
168 class httpconnection(keepalive.HTTPConnection):
168 class httpconnection(keepalive.HTTPConnection):
169 # must be able to send big bundle as stream.
169 # must be able to send big bundle as stream.
170 send = _gen_sendfile(keepalive.HTTPConnection.send)
170 send = _gen_sendfile(keepalive.HTTPConnection.send)
171
171
172 def connect(self):
172 def connect(self):
173 if has_https and self.realhostport: # use CONNECT proxy
173 if has_https and self.realhostport: # use CONNECT proxy
174 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
174 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
175 self.sock.connect((self.host, self.port))
175 self.sock.connect((self.host, self.port))
176 if _generic_proxytunnel(self):
176 if _generic_proxytunnel(self):
177 # we do not support client X.509 certificates
177 # we do not support client X.509 certificates
178 self.sock = sslutil.wrapsocket(self.sock, None, None, None,
178 self.sock = sslutil.wrapsocket(self.sock, None, None, None,
179 serverhostname=self.host)
179 serverhostname=self.host)
180 else:
180 else:
181 keepalive.HTTPConnection.connect(self)
181 keepalive.HTTPConnection.connect(self)
182
182
183 def getresponse(self):
183 def getresponse(self):
184 proxyres = getattr(self, 'proxyres', None)
184 proxyres = getattr(self, 'proxyres', None)
185 if proxyres:
185 if proxyres:
186 if proxyres.will_close:
186 if proxyres.will_close:
187 self.close()
187 self.close()
188 self.proxyres = None
188 self.proxyres = None
189 return proxyres
189 return proxyres
190 return keepalive.HTTPConnection.getresponse(self)
190 return keepalive.HTTPConnection.getresponse(self)
191
191
192 # general transaction handler to support different ways to handle
192 # general transaction handler to support different ways to handle
193 # HTTPS proxying before and after Python 2.6.3.
193 # HTTPS proxying before and after Python 2.6.3.
194 def _generic_start_transaction(handler, h, req):
194 def _generic_start_transaction(handler, h, req):
195 tunnel_host = getattr(req, '_tunnel_host', None)
195 tunnel_host = getattr(req, '_tunnel_host', None)
196 if tunnel_host:
196 if tunnel_host:
197 if tunnel_host[:7] not in ['http://', 'https:/']:
197 if tunnel_host[:7] not in ['http://', 'https:/']:
198 tunnel_host = 'https://' + tunnel_host
198 tunnel_host = 'https://' + tunnel_host
199 new_tunnel = True
199 new_tunnel = True
200 else:
200 else:
201 tunnel_host = req.get_selector()
201 tunnel_host = req.get_selector()
202 new_tunnel = False
202 new_tunnel = False
203
203
204 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
204 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
205 u = util.url(tunnel_host)
205 u = util.url(tunnel_host)
206 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
206 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
207 h.realhostport = ':'.join([u.host, (u.port or '443')])
207 h.realhostport = ':'.join([u.host, (u.port or '443')])
208 h.headers = req.headers.copy()
208 h.headers = req.headers.copy()
209 h.headers.update(handler.parent.addheaders)
209 h.headers.update(handler.parent.addheaders)
210 return
210 return
211
211
212 h.realhostport = None
212 h.realhostport = None
213 h.headers = None
213 h.headers = None
214
214
215 def _generic_proxytunnel(self):
215 def _generic_proxytunnel(self):
216 proxyheaders = dict(
216 proxyheaders = dict(
217 [(x, self.headers[x]) for x in self.headers
217 [(x, self.headers[x]) for x in self.headers
218 if x.lower().startswith('proxy-')])
218 if x.lower().startswith('proxy-')])
219 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
219 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
220 for header in proxyheaders.iteritems():
220 for header in proxyheaders.iteritems():
221 self.send('%s: %s\r\n' % header)
221 self.send('%s: %s\r\n' % header)
222 self.send('\r\n')
222 self.send('\r\n')
223
223
224 # majority of the following code is duplicated from
224 # majority of the following code is duplicated from
225 # httplib.HTTPConnection as there are no adequate places to
225 # httplib.HTTPConnection as there are no adequate places to
226 # override functions to provide the needed functionality
226 # override functions to provide the needed functionality
227 res = self.response_class(self.sock,
227 res = self.response_class(self.sock,
228 strict=self.strict,
228 strict=self.strict,
229 method=self._method)
229 method=self._method)
230
230
231 while True:
231 while True:
232 version, status, reason = res._read_status()
232 version, status, reason = res._read_status()
233 if status != httplib.CONTINUE:
233 if status != httplib.CONTINUE:
234 break
234 break
235 while True:
235 while True:
236 skip = res.fp.readline().strip()
236 skip = res.fp.readline().strip()
237 if not skip:
237 if not skip:
238 break
238 break
239 res.status = status
239 res.status = status
240 res.reason = reason.strip()
240 res.reason = reason.strip()
241
241
242 if res.status == 200:
242 if res.status == 200:
243 while True:
243 while True:
244 line = res.fp.readline()
244 line = res.fp.readline()
245 if line == '\r\n':
245 if line == '\r\n':
246 break
246 break
247 return True
247 return True
248
248
249 if version == 'HTTP/1.0':
249 if version == 'HTTP/1.0':
250 res.version = 10
250 res.version = 10
251 elif version.startswith('HTTP/1.'):
251 elif version.startswith('HTTP/1.'):
252 res.version = 11
252 res.version = 11
253 elif version == 'HTTP/0.9':
253 elif version == 'HTTP/0.9':
254 res.version = 9
254 res.version = 9
255 else:
255 else:
256 raise httplib.UnknownProtocol(version)
256 raise httplib.UnknownProtocol(version)
257
257
258 if res.version == 9:
258 if res.version == 9:
259 res.length = None
259 res.length = None
260 res.chunked = 0
260 res.chunked = 0
261 res.will_close = 1
261 res.will_close = 1
262 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
262 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
263 return False
263 return False
264
264
265 res.msg = httplib.HTTPMessage(res.fp)
265 res.msg = httplib.HTTPMessage(res.fp)
266 res.msg.fp = None
266 res.msg.fp = None
267
267
268 # are we using the chunked-style of transfer encoding?
268 # are we using the chunked-style of transfer encoding?
269 trenc = res.msg.getheader('transfer-encoding')
269 trenc = res.msg.getheader('transfer-encoding')
270 if trenc and trenc.lower() == "chunked":
270 if trenc and trenc.lower() == "chunked":
271 res.chunked = 1
271 res.chunked = 1
272 res.chunk_left = None
272 res.chunk_left = None
273 else:
273 else:
274 res.chunked = 0
274 res.chunked = 0
275
275
276 # will the connection close at the end of the response?
276 # will the connection close at the end of the response?
277 res.will_close = res._check_close()
277 res.will_close = res._check_close()
278
278
279 # do we have a Content-Length?
279 # do we have a Content-Length?
280 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
280 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
281 # transfer-encoding is "chunked"
281 # transfer-encoding is "chunked"
282 length = res.msg.getheader('content-length')
282 length = res.msg.getheader('content-length')
283 if length and not res.chunked:
283 if length and not res.chunked:
284 try:
284 try:
285 res.length = int(length)
285 res.length = int(length)
286 except ValueError:
286 except ValueError:
287 res.length = None
287 res.length = None
288 else:
288 else:
289 if res.length < 0: # ignore nonsensical negative lengths
289 if res.length < 0: # ignore nonsensical negative lengths
290 res.length = None
290 res.length = None
291 else:
291 else:
292 res.length = None
292 res.length = None
293
293
294 # does the body have a fixed length? (of zero)
294 # does the body have a fixed length? (of zero)
295 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
295 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
296 100 <= status < 200 or # 1xx codes
296 100 <= status < 200 or # 1xx codes
297 res._method == 'HEAD'):
297 res._method == 'HEAD'):
298 res.length = 0
298 res.length = 0
299
299
300 # if the connection remains open, and we aren't using chunked, and
300 # if the connection remains open, and we aren't using chunked, and
301 # a content-length was not provided, then assume that the connection
301 # a content-length was not provided, then assume that the connection
302 # WILL close.
302 # WILL close.
303 if (not res.will_close and
303 if (not res.will_close and
304 not res.chunked and
304 not res.chunked and
305 res.length is None):
305 res.length is None):
306 res.will_close = 1
306 res.will_close = 1
307
307
308 self.proxyres = res
308 self.proxyres = res
309
309
310 return False
310 return False
311
311
312 class httphandler(keepalive.HTTPHandler):
312 class httphandler(keepalive.HTTPHandler):
313 def http_open(self, req):
313 def http_open(self, req):
314 return self.do_open(httpconnection, req)
314 return self.do_open(httpconnection, req)
315
315
316 def _start_transaction(self, h, req):
316 def _start_transaction(self, h, req):
317 _generic_start_transaction(self, h, req)
317 _generic_start_transaction(self, h, req)
318 return keepalive.HTTPHandler._start_transaction(self, h, req)
318 return keepalive.HTTPHandler._start_transaction(self, h, req)
319
319
320 if has_https:
320 if has_https:
321 class httpsconnection(httplib.HTTPConnection):
321 class httpsconnection(httplib.HTTPConnection):
322 response_class = keepalive.HTTPResponse
322 response_class = keepalive.HTTPResponse
323 default_port = httplib.HTTPS_PORT
323 default_port = httplib.HTTPS_PORT
324 # must be able to send big bundle as stream.
324 # must be able to send big bundle as stream.
325 send = _gen_sendfile(keepalive.safesend)
325 send = _gen_sendfile(keepalive.safesend)
326 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
326 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
327
327
328 def __init__(self, host, port=None, key_file=None, cert_file=None,
328 def __init__(self, host, port=None, key_file=None, cert_file=None,
329 *args, **kwargs):
329 *args, **kwargs):
330 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
330 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
331 self.key_file = key_file
331 self.key_file = key_file
332 self.cert_file = cert_file
332 self.cert_file = cert_file
333
333
334 def connect(self):
334 def connect(self):
335 self.sock = _create_connection((self.host, self.port))
335 self.sock = _create_connection((self.host, self.port))
336
336
337 host = self.host
337 host = self.host
338 if self.realhostport: # use CONNECT proxy
338 if self.realhostport: # use CONNECT proxy
339 _generic_proxytunnel(self)
339 _generic_proxytunnel(self)
340 host = self.realhostport.rsplit(':', 1)[0]
340 host = self.realhostport.rsplit(':', 1)[0]
341 self.sock = sslutil.wrapsocket(
341 self.sock = sslutil.wrapsocket(
342 self.sock, self.key_file, self.cert_file, serverhostname=host,
342 self.sock, self.key_file, self.cert_file, serverhostname=host,
343 **sslutil.sslkwargs(self.ui, host))
343 **sslutil.sslkwargs(self.ui, host))
344 sslutil.validator(self.ui, host)(self.sock)
344 sslutil.validator(self.ui, host)(self.sock)
345
345
346 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
346 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
347 def __init__(self, ui):
347 def __init__(self, ui):
348 keepalive.KeepAliveHandler.__init__(self)
348 keepalive.KeepAliveHandler.__init__(self)
349 urllib2.HTTPSHandler.__init__(self)
349 urllib2.HTTPSHandler.__init__(self)
350 self.ui = ui
350 self.ui = ui
351 self.pwmgr = passwordmgr(self.ui)
351 self.pwmgr = passwordmgr(self.ui)
352
352
353 def _start_transaction(self, h, req):
353 def _start_transaction(self, h, req):
354 _generic_start_transaction(self, h, req)
354 _generic_start_transaction(self, h, req)
355 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
355 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
356
356
357 def https_open(self, req):
357 def https_open(self, req):
358 # req.get_full_url() does not contain credentials and we may
358 # req.get_full_url() does not contain credentials and we may
359 # need them to match the certificates.
359 # need them to match the certificates.
360 url = req.get_full_url()
360 url = req.get_full_url()
361 user, password = self.pwmgr.find_stored_password(url)
361 user, password = self.pwmgr.find_stored_password(url)
362 res = httpconnectionmod.readauthforuri(self.ui, url, user)
362 res = httpconnectionmod.readauthforuri(self.ui, url, user)
363 if res:
363 if res:
364 group, auth = res
364 group, auth = res
365 self.auth = auth
365 self.auth = auth
366 self.ui.debug("using auth.%s.* for authentication\n" % group)
366 self.ui.debug("using auth.%s.* for authentication\n" % group)
367 else:
367 else:
368 self.auth = None
368 self.auth = None
369 return self.do_open(self._makeconnection, req)
369 return self.do_open(self._makeconnection, req)
370
370
371 def _makeconnection(self, host, port=None, *args, **kwargs):
371 def _makeconnection(self, host, port=None, *args, **kwargs):
372 keyfile = None
372 keyfile = None
373 certfile = None
373 certfile = None
374
374
375 if len(args) >= 1: # key_file
375 if len(args) >= 1: # key_file
376 keyfile = args[0]
376 keyfile = args[0]
377 if len(args) >= 2: # cert_file
377 if len(args) >= 2: # cert_file
378 certfile = args[1]
378 certfile = args[1]
379 args = args[2:]
379 args = args[2:]
380
380
381 # if the user has specified different key/cert files in
381 # if the user has specified different key/cert files in
382 # hgrc, we prefer these
382 # hgrc, we prefer these
383 if self.auth and 'key' in self.auth and 'cert' in self.auth:
383 if self.auth and 'key' in self.auth and 'cert' in self.auth:
384 keyfile = self.auth['key']
384 keyfile = self.auth['key']
385 certfile = self.auth['cert']
385 certfile = self.auth['cert']
386
386
387 conn = httpsconnection(host, port, keyfile, certfile, *args,
387 conn = httpsconnection(host, port, keyfile, certfile, *args,
388 **kwargs)
388 **kwargs)
389 conn.ui = self.ui
389 conn.ui = self.ui
390 return conn
390 return conn
391
391
392 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
392 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
393 def __init__(self, *args, **kwargs):
393 def __init__(self, *args, **kwargs):
394 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
394 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
395 self.retried_req = None
395 self.retried_req = None
396
396
397 def reset_retry_count(self):
397 def reset_retry_count(self):
398 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
398 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
399 # forever. We disable reset_retry_count completely and reset in
399 # forever. We disable reset_retry_count completely and reset in
400 # http_error_auth_reqed instead.
400 # http_error_auth_reqed instead.
401 pass
401 pass
402
402
403 def http_error_auth_reqed(self, auth_header, host, req, headers):
403 def http_error_auth_reqed(self, auth_header, host, req, headers):
404 # Reset the retry counter once for each request.
404 # Reset the retry counter once for each request.
405 if req is not self.retried_req:
405 if req is not self.retried_req:
406 self.retried_req = req
406 self.retried_req = req
407 self.retried = 0
407 self.retried = 0
408 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
408 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
409 # it doesn't know about the auth type requested. This can happen if
409 # it doesn't know about the auth type requested. This can happen if
410 # somebody is using BasicAuth and types a bad password.
410 # somebody is using BasicAuth and types a bad password.
411 try:
411 try:
412 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
412 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
413 self, auth_header, host, req, headers)
413 self, auth_header, host, req, headers)
414 except ValueError as inst:
414 except ValueError as inst:
415 arg = inst.args[0]
415 arg = inst.args[0]
416 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
416 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
417 return
417 return
418 raise
418 raise
419
419
420 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
420 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
421 def __init__(self, *args, **kwargs):
421 def __init__(self, *args, **kwargs):
422 self.auth = None
422 self.auth = None
423 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
423 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
424 self.retried_req = None
424 self.retried_req = None
425
425
426 def http_request(self, request):
426 def http_request(self, request):
427 if self.auth:
427 if self.auth:
428 request.add_unredirected_header(self.auth_header, self.auth)
428 request.add_unredirected_header(self.auth_header, self.auth)
429
429
430 return request
430 return request
431
431
432 def https_request(self, request):
432 def https_request(self, request):
433 if self.auth:
433 if self.auth:
434 request.add_unredirected_header(self.auth_header, self.auth)
434 request.add_unredirected_header(self.auth_header, self.auth)
435
435
436 return request
436 return request
437
437
438 def reset_retry_count(self):
438 def reset_retry_count(self):
439 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
439 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
440 # forever. We disable reset_retry_count completely and reset in
440 # forever. We disable reset_retry_count completely and reset in
441 # http_error_auth_reqed instead.
441 # http_error_auth_reqed instead.
442 pass
442 pass
443
443
444 def http_error_auth_reqed(self, auth_header, host, req, headers):
444 def http_error_auth_reqed(self, auth_header, host, req, headers):
445 # Reset the retry counter once for each request.
445 # Reset the retry counter once for each request.
446 if req is not self.retried_req:
446 if req is not self.retried_req:
447 self.retried_req = req
447 self.retried_req = req
448 self.retried = 0
448 self.retried = 0
449 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
449 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
450 self, auth_header, host, req, headers)
450 self, auth_header, host, req, headers)
451
451
452 def retry_http_basic_auth(self, host, req, realm):
452 def retry_http_basic_auth(self, host, req, realm):
453 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
453 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
454 if pw is not None:
454 if pw is not None:
455 raw = "%s:%s" % (user, pw)
455 raw = "%s:%s" % (user, pw)
456 auth = 'Basic %s' % base64.b64encode(raw).strip()
456 auth = 'Basic %s' % base64.b64encode(raw).strip()
457 if req.headers.get(self.auth_header, None) == auth:
457 if req.headers.get(self.auth_header, None) == auth:
458 return None
458 return None
459 self.auth = auth
459 self.auth = auth
460 req.add_unredirected_header(self.auth_header, auth)
460 req.add_unredirected_header(self.auth_header, auth)
461 return self.parent.open(req)
461 return self.parent.open(req)
462 else:
462 else:
463 return None
463 return None
464
464
465 handlerfuncs = []
465 handlerfuncs = []
466
466
467 def opener(ui, authinfo=None):
467 def opener(ui, authinfo=None):
468 '''
468 '''
469 construct an opener suitable for urllib2
469 construct an opener suitable for urllib2
470 authinfo will be added to the password manager
470 authinfo will be added to the password manager
471 '''
471 '''
472 # experimental config: ui.usehttp2
472 if ui.configbool('ui', 'usehttp2', False):
473 if ui.configbool('ui', 'usehttp2', False):
473 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
474 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
474 else:
475 else:
475 handlers = [httphandler()]
476 handlers = [httphandler()]
476 if has_https:
477 if has_https:
477 handlers.append(httpshandler(ui))
478 handlers.append(httpshandler(ui))
478
479
479 handlers.append(proxyhandler(ui))
480 handlers.append(proxyhandler(ui))
480
481
481 passmgr = passwordmgr(ui)
482 passmgr = passwordmgr(ui)
482 if authinfo is not None:
483 if authinfo is not None:
483 passmgr.add_password(*authinfo)
484 passmgr.add_password(*authinfo)
484 user, passwd = authinfo[2:4]
485 user, passwd = authinfo[2:4]
485 ui.debug('http auth: user %s, password %s\n' %
486 ui.debug('http auth: user %s, password %s\n' %
486 (user, passwd and '*' * len(passwd) or 'not set'))
487 (user, passwd and '*' * len(passwd) or 'not set'))
487
488
488 handlers.extend((httpbasicauthhandler(passmgr),
489 handlers.extend((httpbasicauthhandler(passmgr),
489 httpdigestauthhandler(passmgr)))
490 httpdigestauthhandler(passmgr)))
490 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
491 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
491 opener = urllib2.build_opener(*handlers)
492 opener = urllib2.build_opener(*handlers)
492
493
493 # 1.0 here is the _protocol_ version
494 # 1.0 here is the _protocol_ version
494 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
495 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
495 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
496 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
496 return opener
497 return opener
497
498
498 def open(ui, url_, data=None):
499 def open(ui, url_, data=None):
499 u = util.url(url_)
500 u = util.url(url_)
500 if u.scheme:
501 if u.scheme:
501 u.scheme = u.scheme.lower()
502 u.scheme = u.scheme.lower()
502 url_, authinfo = u.authinfo()
503 url_, authinfo = u.authinfo()
503 else:
504 else:
504 path = util.normpath(os.path.abspath(url_))
505 path = util.normpath(os.path.abspath(url_))
505 url_ = 'file://' + urllib.pathname2url(path)
506 url_ = 'file://' + urllib.pathname2url(path)
506 authinfo = None
507 authinfo = None
507 return opener(ui, authinfo).open(url_, data)
508 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now