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