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