##// END OF EJS Templates
http digest auth: reset redirect counter on new requests (issue2255)...
Mads Kiilerich -
r11457:2ec34616 stable
parent child Browse files
Show More
@@ -1,634 +1,641 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, urlparse, httplib, os, re, socket, cStringIO
10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 from i18n import _
11 from i18n import _
12 import keepalive, util
12 import keepalive, util
13
13
14 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
14 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
15 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
15 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
16 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
16 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
17 if (scheme and
17 if (scheme and
18 result.startswith(scheme + ':') and
18 result.startswith(scheme + ':') and
19 not result.startswith(scheme + '://') and
19 not result.startswith(scheme + '://') and
20 url.startswith(scheme + '://')
20 url.startswith(scheme + '://')
21 ):
21 ):
22 result = scheme + '://' + result[len(scheme + ':'):]
22 result = scheme + '://' + result[len(scheme + ':'):]
23 return result
23 return result
24
24
25 def hidepassword(url):
25 def hidepassword(url):
26 '''hide user credential in a url string'''
26 '''hide user credential in a url string'''
27 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
27 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
28 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
28 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
29 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
29 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
30
30
31 def removeauth(url):
31 def removeauth(url):
32 '''remove all authentication information from a url string'''
32 '''remove all authentication information from a url string'''
33 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
33 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
34 netloc = netloc[netloc.find('@')+1:]
34 netloc = netloc[netloc.find('@')+1:]
35 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
35 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
36
36
37 def netlocsplit(netloc):
37 def netlocsplit(netloc):
38 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
38 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39
39
40 a = netloc.find('@')
40 a = netloc.find('@')
41 if a == -1:
41 if a == -1:
42 user, passwd = None, None
42 user, passwd = None, None
43 else:
43 else:
44 userpass, netloc = netloc[:a], netloc[a + 1:]
44 userpass, netloc = netloc[:a], netloc[a + 1:]
45 c = userpass.find(':')
45 c = userpass.find(':')
46 if c == -1:
46 if c == -1:
47 user, passwd = urllib.unquote(userpass), None
47 user, passwd = urllib.unquote(userpass), None
48 else:
48 else:
49 user = urllib.unquote(userpass[:c])
49 user = urllib.unquote(userpass[:c])
50 passwd = urllib.unquote(userpass[c + 1:])
50 passwd = urllib.unquote(userpass[c + 1:])
51 c = netloc.find(':')
51 c = netloc.find(':')
52 if c == -1:
52 if c == -1:
53 host, port = netloc, None
53 host, port = netloc, None
54 else:
54 else:
55 host, port = netloc[:c], netloc[c + 1:]
55 host, port = netloc[:c], netloc[c + 1:]
56 return host, port, user, passwd
56 return host, port, user, passwd
57
57
58 def netlocunsplit(host, port, user=None, passwd=None):
58 def netlocunsplit(host, port, user=None, passwd=None):
59 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
59 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 if port:
60 if port:
61 hostport = host + ':' + port
61 hostport = host + ':' + port
62 else:
62 else:
63 hostport = host
63 hostport = host
64 if user:
64 if user:
65 quote = lambda s: urllib.quote(s, safe='')
65 quote = lambda s: urllib.quote(s, safe='')
66 if passwd:
66 if passwd:
67 userpass = quote(user) + ':' + quote(passwd)
67 userpass = quote(user) + ':' + quote(passwd)
68 else:
68 else:
69 userpass = quote(user)
69 userpass = quote(user)
70 return userpass + '@' + hostport
70 return userpass + '@' + hostport
71 return hostport
71 return hostport
72
72
73 _safe = ('abcdefghijklmnopqrstuvwxyz'
73 _safe = ('abcdefghijklmnopqrstuvwxyz'
74 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
74 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
75 '0123456789' '_.-/')
75 '0123456789' '_.-/')
76 _safeset = None
76 _safeset = None
77 _hex = None
77 _hex = None
78 def quotepath(path):
78 def quotepath(path):
79 '''quote the path part of a URL
79 '''quote the path part of a URL
80
80
81 This is similar to urllib.quote, but it also tries to avoid
81 This is similar to urllib.quote, but it also tries to avoid
82 quoting things twice (inspired by wget):
82 quoting things twice (inspired by wget):
83
83
84 >>> quotepath('abc def')
84 >>> quotepath('abc def')
85 'abc%20def'
85 'abc%20def'
86 >>> quotepath('abc%20def')
86 >>> quotepath('abc%20def')
87 'abc%20def'
87 'abc%20def'
88 >>> quotepath('abc%20 def')
88 >>> quotepath('abc%20 def')
89 'abc%20%20def'
89 'abc%20%20def'
90 >>> quotepath('abc def%20')
90 >>> quotepath('abc def%20')
91 'abc%20def%20'
91 'abc%20def%20'
92 >>> quotepath('abc def%2')
92 >>> quotepath('abc def%2')
93 'abc%20def%252'
93 'abc%20def%252'
94 >>> quotepath('abc def%')
94 >>> quotepath('abc def%')
95 'abc%20def%25'
95 'abc%20def%25'
96 '''
96 '''
97 global _safeset, _hex
97 global _safeset, _hex
98 if _safeset is None:
98 if _safeset is None:
99 _safeset = set(_safe)
99 _safeset = set(_safe)
100 _hex = set('abcdefABCDEF0123456789')
100 _hex = set('abcdefABCDEF0123456789')
101 l = list(path)
101 l = list(path)
102 for i in xrange(len(l)):
102 for i in xrange(len(l)):
103 c = l[i]
103 c = l[i]
104 if (c == '%' and i + 2 < len(l) and
104 if (c == '%' and i + 2 < len(l) and
105 l[i + 1] in _hex and l[i + 2] in _hex):
105 l[i + 1] in _hex and l[i + 2] in _hex):
106 pass
106 pass
107 elif c not in _safeset:
107 elif c not in _safeset:
108 l[i] = '%%%02X' % ord(c)
108 l[i] = '%%%02X' % ord(c)
109 return ''.join(l)
109 return ''.join(l)
110
110
111 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
111 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
112 def __init__(self, ui):
112 def __init__(self, ui):
113 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
113 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
114 self.ui = ui
114 self.ui = ui
115
115
116 def find_user_password(self, realm, authuri):
116 def find_user_password(self, realm, authuri):
117 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
117 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
118 self, realm, authuri)
118 self, realm, authuri)
119 user, passwd = authinfo
119 user, passwd = authinfo
120 if user and passwd:
120 if user and passwd:
121 self._writedebug(user, passwd)
121 self._writedebug(user, passwd)
122 return (user, passwd)
122 return (user, passwd)
123
123
124 if not user:
124 if not user:
125 auth = self.readauthtoken(authuri)
125 auth = self.readauthtoken(authuri)
126 if auth:
126 if auth:
127 user, passwd = auth.get('username'), auth.get('password')
127 user, passwd = auth.get('username'), auth.get('password')
128 if not user or not passwd:
128 if not user or not passwd:
129 if not self.ui.interactive():
129 if not self.ui.interactive():
130 raise util.Abort(_('http authorization required'))
130 raise util.Abort(_('http authorization required'))
131
131
132 self.ui.write(_("http authorization required\n"))
132 self.ui.write(_("http authorization required\n"))
133 self.ui.status(_("realm: %s\n") % realm)
133 self.ui.status(_("realm: %s\n") % realm)
134 if user:
134 if user:
135 self.ui.status(_("user: %s\n") % user)
135 self.ui.status(_("user: %s\n") % user)
136 else:
136 else:
137 user = self.ui.prompt(_("user:"), default=None)
137 user = self.ui.prompt(_("user:"), default=None)
138
138
139 if not passwd:
139 if not passwd:
140 passwd = self.ui.getpass()
140 passwd = self.ui.getpass()
141
141
142 self.add_password(realm, authuri, user, passwd)
142 self.add_password(realm, authuri, user, passwd)
143 self._writedebug(user, passwd)
143 self._writedebug(user, passwd)
144 return (user, passwd)
144 return (user, passwd)
145
145
146 def _writedebug(self, user, passwd):
146 def _writedebug(self, user, passwd):
147 msg = _('http auth: user %s, password %s\n')
147 msg = _('http auth: user %s, password %s\n')
148 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
148 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
149
149
150 def readauthtoken(self, uri):
150 def readauthtoken(self, uri):
151 # Read configuration
151 # Read configuration
152 config = dict()
152 config = dict()
153 for key, val in self.ui.configitems('auth'):
153 for key, val in self.ui.configitems('auth'):
154 if '.' not in key:
154 if '.' not in key:
155 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
155 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
156 continue
156 continue
157 group, setting = key.split('.', 1)
157 group, setting = key.split('.', 1)
158 gdict = config.setdefault(group, dict())
158 gdict = config.setdefault(group, dict())
159 if setting in ('cert', 'key'):
159 if setting in ('cert', 'key'):
160 val = util.expandpath(val)
160 val = util.expandpath(val)
161 gdict[setting] = val
161 gdict[setting] = val
162
162
163 # Find the best match
163 # Find the best match
164 scheme, hostpath = uri.split('://', 1)
164 scheme, hostpath = uri.split('://', 1)
165 bestlen = 0
165 bestlen = 0
166 bestauth = None
166 bestauth = None
167 for auth in config.itervalues():
167 for auth in config.itervalues():
168 prefix = auth.get('prefix')
168 prefix = auth.get('prefix')
169 if not prefix:
169 if not prefix:
170 continue
170 continue
171 p = prefix.split('://', 1)
171 p = prefix.split('://', 1)
172 if len(p) > 1:
172 if len(p) > 1:
173 schemes, prefix = [p[0]], p[1]
173 schemes, prefix = [p[0]], p[1]
174 else:
174 else:
175 schemes = (auth.get('schemes') or 'https').split()
175 schemes = (auth.get('schemes') or 'https').split()
176 if (prefix == '*' or hostpath.startswith(prefix)) and \
176 if (prefix == '*' or hostpath.startswith(prefix)) and \
177 len(prefix) > bestlen and scheme in schemes:
177 len(prefix) > bestlen and scheme in schemes:
178 bestlen = len(prefix)
178 bestlen = len(prefix)
179 bestauth = auth
179 bestauth = auth
180 return bestauth
180 return bestauth
181
181
182 class proxyhandler(urllib2.ProxyHandler):
182 class proxyhandler(urllib2.ProxyHandler):
183 def __init__(self, ui):
183 def __init__(self, ui):
184 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
184 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
185 # XXX proxyauthinfo = None
185 # XXX proxyauthinfo = None
186
186
187 if proxyurl:
187 if proxyurl:
188 # proxy can be proper url or host[:port]
188 # proxy can be proper url or host[:port]
189 if not (proxyurl.startswith('http:') or
189 if not (proxyurl.startswith('http:') or
190 proxyurl.startswith('https:')):
190 proxyurl.startswith('https:')):
191 proxyurl = 'http://' + proxyurl + '/'
191 proxyurl = 'http://' + proxyurl + '/'
192 snpqf = urlparse.urlsplit(proxyurl)
192 snpqf = urlparse.urlsplit(proxyurl)
193 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
193 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
194 hpup = netlocsplit(proxynetloc)
194 hpup = netlocsplit(proxynetloc)
195
195
196 proxyhost, proxyport, proxyuser, proxypasswd = hpup
196 proxyhost, proxyport, proxyuser, proxypasswd = hpup
197 if not proxyuser:
197 if not proxyuser:
198 proxyuser = ui.config("http_proxy", "user")
198 proxyuser = ui.config("http_proxy", "user")
199 proxypasswd = ui.config("http_proxy", "passwd")
199 proxypasswd = ui.config("http_proxy", "passwd")
200
200
201 # see if we should use a proxy for this url
201 # see if we should use a proxy for this url
202 no_list = ["localhost", "127.0.0.1"]
202 no_list = ["localhost", "127.0.0.1"]
203 no_list.extend([p.lower() for
203 no_list.extend([p.lower() for
204 p in ui.configlist("http_proxy", "no")])
204 p in ui.configlist("http_proxy", "no")])
205 no_list.extend([p.strip().lower() for
205 no_list.extend([p.strip().lower() for
206 p in os.getenv("no_proxy", '').split(',')
206 p in os.getenv("no_proxy", '').split(',')
207 if p.strip()])
207 if p.strip()])
208 # "http_proxy.always" config is for running tests on localhost
208 # "http_proxy.always" config is for running tests on localhost
209 if ui.configbool("http_proxy", "always"):
209 if ui.configbool("http_proxy", "always"):
210 self.no_list = []
210 self.no_list = []
211 else:
211 else:
212 self.no_list = no_list
212 self.no_list = no_list
213
213
214 proxyurl = urlparse.urlunsplit((
214 proxyurl = urlparse.urlunsplit((
215 proxyscheme, netlocunsplit(proxyhost, proxyport,
215 proxyscheme, netlocunsplit(proxyhost, proxyport,
216 proxyuser, proxypasswd or ''),
216 proxyuser, proxypasswd or ''),
217 proxypath, proxyquery, proxyfrag))
217 proxypath, proxyquery, proxyfrag))
218 proxies = {'http': proxyurl, 'https': proxyurl}
218 proxies = {'http': proxyurl, 'https': proxyurl}
219 ui.debug('proxying through http://%s:%s\n' %
219 ui.debug('proxying through http://%s:%s\n' %
220 (proxyhost, proxyport))
220 (proxyhost, proxyport))
221 else:
221 else:
222 proxies = {}
222 proxies = {}
223
223
224 # urllib2 takes proxy values from the environment and those
224 # urllib2 takes proxy values from the environment and those
225 # will take precedence if found, so drop them
225 # will take precedence if found, so drop them
226 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
226 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
227 try:
227 try:
228 if env in os.environ:
228 if env in os.environ:
229 del os.environ[env]
229 del os.environ[env]
230 except OSError:
230 except OSError:
231 pass
231 pass
232
232
233 urllib2.ProxyHandler.__init__(self, proxies)
233 urllib2.ProxyHandler.__init__(self, proxies)
234 self.ui = ui
234 self.ui = ui
235
235
236 def proxy_open(self, req, proxy, type_):
236 def proxy_open(self, req, proxy, type_):
237 host = req.get_host().split(':')[0]
237 host = req.get_host().split(':')[0]
238 if host in self.no_list:
238 if host in self.no_list:
239 return None
239 return None
240
240
241 # work around a bug in Python < 2.4.2
241 # work around a bug in Python < 2.4.2
242 # (it leaves a "\n" at the end of Proxy-authorization headers)
242 # (it leaves a "\n" at the end of Proxy-authorization headers)
243 baseclass = req.__class__
243 baseclass = req.__class__
244 class _request(baseclass):
244 class _request(baseclass):
245 def add_header(self, key, val):
245 def add_header(self, key, val):
246 if key.lower() == 'proxy-authorization':
246 if key.lower() == 'proxy-authorization':
247 val = val.strip()
247 val = val.strip()
248 return baseclass.add_header(self, key, val)
248 return baseclass.add_header(self, key, val)
249 req.__class__ = _request
249 req.__class__ = _request
250
250
251 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
251 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
252
252
253 class httpsendfile(file):
253 class httpsendfile(file):
254 def __len__(self):
254 def __len__(self):
255 return os.fstat(self.fileno()).st_size
255 return os.fstat(self.fileno()).st_size
256
256
257 def _gen_sendfile(connection):
257 def _gen_sendfile(connection):
258 def _sendfile(self, data):
258 def _sendfile(self, data):
259 # send a file
259 # send a file
260 if isinstance(data, httpsendfile):
260 if isinstance(data, httpsendfile):
261 # if auth required, some data sent twice, so rewind here
261 # if auth required, some data sent twice, so rewind here
262 data.seek(0)
262 data.seek(0)
263 for chunk in util.filechunkiter(data):
263 for chunk in util.filechunkiter(data):
264 connection.send(self, chunk)
264 connection.send(self, chunk)
265 else:
265 else:
266 connection.send(self, data)
266 connection.send(self, data)
267 return _sendfile
267 return _sendfile
268
268
269 has_https = hasattr(urllib2, 'HTTPSHandler')
269 has_https = hasattr(urllib2, 'HTTPSHandler')
270 if has_https:
270 if has_https:
271 try:
271 try:
272 # avoid using deprecated/broken FakeSocket in python 2.6
272 # avoid using deprecated/broken FakeSocket in python 2.6
273 import ssl
273 import ssl
274 _ssl_wrap_socket = ssl.wrap_socket
274 _ssl_wrap_socket = ssl.wrap_socket
275 CERT_REQUIRED = ssl.CERT_REQUIRED
275 CERT_REQUIRED = ssl.CERT_REQUIRED
276 except ImportError:
276 except ImportError:
277 CERT_REQUIRED = 2
277 CERT_REQUIRED = 2
278
278
279 def _ssl_wrap_socket(sock, key_file, cert_file,
279 def _ssl_wrap_socket(sock, key_file, cert_file,
280 cert_reqs=CERT_REQUIRED, ca_certs=None):
280 cert_reqs=CERT_REQUIRED, ca_certs=None):
281 if ca_certs:
281 if ca_certs:
282 raise util.Abort(_(
282 raise util.Abort(_(
283 'certificate checking requires Python 2.6'))
283 'certificate checking requires Python 2.6'))
284
284
285 ssl = socket.ssl(sock, key_file, cert_file)
285 ssl = socket.ssl(sock, key_file, cert_file)
286 return httplib.FakeSocket(sock, ssl)
286 return httplib.FakeSocket(sock, ssl)
287
287
288 try:
288 try:
289 _create_connection = socket.create_connection
289 _create_connection = socket.create_connection
290 except AttributeError:
290 except AttributeError:
291 _GLOBAL_DEFAULT_TIMEOUT = object()
291 _GLOBAL_DEFAULT_TIMEOUT = object()
292
292
293 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
293 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
294 source_address=None):
294 source_address=None):
295 # lifted from Python 2.6
295 # lifted from Python 2.6
296
296
297 msg = "getaddrinfo returns an empty list"
297 msg = "getaddrinfo returns an empty list"
298 host, port = address
298 host, port = address
299 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
299 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
300 af, socktype, proto, canonname, sa = res
300 af, socktype, proto, canonname, sa = res
301 sock = None
301 sock = None
302 try:
302 try:
303 sock = socket.socket(af, socktype, proto)
303 sock = socket.socket(af, socktype, proto)
304 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
304 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
305 sock.settimeout(timeout)
305 sock.settimeout(timeout)
306 if source_address:
306 if source_address:
307 sock.bind(source_address)
307 sock.bind(source_address)
308 sock.connect(sa)
308 sock.connect(sa)
309 return sock
309 return sock
310
310
311 except socket.error, msg:
311 except socket.error, msg:
312 if sock is not None:
312 if sock is not None:
313 sock.close()
313 sock.close()
314
314
315 raise socket.error, msg
315 raise socket.error, msg
316
316
317 class httpconnection(keepalive.HTTPConnection):
317 class httpconnection(keepalive.HTTPConnection):
318 # must be able to send big bundle as stream.
318 # must be able to send big bundle as stream.
319 send = _gen_sendfile(keepalive.HTTPConnection)
319 send = _gen_sendfile(keepalive.HTTPConnection)
320
320
321 def connect(self):
321 def connect(self):
322 if has_https and self.realhostport: # use CONNECT proxy
322 if has_https and self.realhostport: # use CONNECT proxy
323 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
323 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
324 self.sock.connect((self.host, self.port))
324 self.sock.connect((self.host, self.port))
325 if _generic_proxytunnel(self):
325 if _generic_proxytunnel(self):
326 # we do not support client x509 certificates
326 # we do not support client x509 certificates
327 self.sock = _ssl_wrap_socket(self.sock, None, None)
327 self.sock = _ssl_wrap_socket(self.sock, None, None)
328 else:
328 else:
329 keepalive.HTTPConnection.connect(self)
329 keepalive.HTTPConnection.connect(self)
330
330
331 def getresponse(self):
331 def getresponse(self):
332 proxyres = getattr(self, 'proxyres', None)
332 proxyres = getattr(self, 'proxyres', None)
333 if proxyres:
333 if proxyres:
334 if proxyres.will_close:
334 if proxyres.will_close:
335 self.close()
335 self.close()
336 self.proxyres = None
336 self.proxyres = None
337 return proxyres
337 return proxyres
338 return keepalive.HTTPConnection.getresponse(self)
338 return keepalive.HTTPConnection.getresponse(self)
339
339
340 # general transaction handler to support different ways to handle
340 # general transaction handler to support different ways to handle
341 # HTTPS proxying before and after Python 2.6.3.
341 # HTTPS proxying before and after Python 2.6.3.
342 def _generic_start_transaction(handler, h, req):
342 def _generic_start_transaction(handler, h, req):
343 if hasattr(req, '_tunnel_host') and req._tunnel_host:
343 if hasattr(req, '_tunnel_host') and req._tunnel_host:
344 tunnel_host = req._tunnel_host
344 tunnel_host = req._tunnel_host
345 if tunnel_host[:7] not in ['http://', 'https:/']:
345 if tunnel_host[:7] not in ['http://', 'https:/']:
346 tunnel_host = 'https://' + tunnel_host
346 tunnel_host = 'https://' + tunnel_host
347 new_tunnel = True
347 new_tunnel = True
348 else:
348 else:
349 tunnel_host = req.get_selector()
349 tunnel_host = req.get_selector()
350 new_tunnel = False
350 new_tunnel = False
351
351
352 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
352 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
353 urlparts = urlparse.urlparse(tunnel_host)
353 urlparts = urlparse.urlparse(tunnel_host)
354 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
354 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
355 realhostport = urlparts[1]
355 realhostport = urlparts[1]
356 if realhostport[-1] == ']' or ':' not in realhostport:
356 if realhostport[-1] == ']' or ':' not in realhostport:
357 realhostport += ':443'
357 realhostport += ':443'
358
358
359 h.realhostport = realhostport
359 h.realhostport = realhostport
360 h.headers = req.headers.copy()
360 h.headers = req.headers.copy()
361 h.headers.update(handler.parent.addheaders)
361 h.headers.update(handler.parent.addheaders)
362 return
362 return
363
363
364 h.realhostport = None
364 h.realhostport = None
365 h.headers = None
365 h.headers = None
366
366
367 def _generic_proxytunnel(self):
367 def _generic_proxytunnel(self):
368 proxyheaders = dict(
368 proxyheaders = dict(
369 [(x, self.headers[x]) for x in self.headers
369 [(x, self.headers[x]) for x in self.headers
370 if x.lower().startswith('proxy-')])
370 if x.lower().startswith('proxy-')])
371 self._set_hostport(self.host, self.port)
371 self._set_hostport(self.host, self.port)
372 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
372 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
373 for header in proxyheaders.iteritems():
373 for header in proxyheaders.iteritems():
374 self.send('%s: %s\r\n' % header)
374 self.send('%s: %s\r\n' % header)
375 self.send('\r\n')
375 self.send('\r\n')
376
376
377 # majority of the following code is duplicated from
377 # majority of the following code is duplicated from
378 # httplib.HTTPConnection as there are no adequate places to
378 # httplib.HTTPConnection as there are no adequate places to
379 # override functions to provide the needed functionality
379 # override functions to provide the needed functionality
380 res = self.response_class(self.sock,
380 res = self.response_class(self.sock,
381 strict=self.strict,
381 strict=self.strict,
382 method=self._method)
382 method=self._method)
383
383
384 while True:
384 while True:
385 version, status, reason = res._read_status()
385 version, status, reason = res._read_status()
386 if status != httplib.CONTINUE:
386 if status != httplib.CONTINUE:
387 break
387 break
388 while True:
388 while True:
389 skip = res.fp.readline().strip()
389 skip = res.fp.readline().strip()
390 if not skip:
390 if not skip:
391 break
391 break
392 res.status = status
392 res.status = status
393 res.reason = reason.strip()
393 res.reason = reason.strip()
394
394
395 if res.status == 200:
395 if res.status == 200:
396 while True:
396 while True:
397 line = res.fp.readline()
397 line = res.fp.readline()
398 if line == '\r\n':
398 if line == '\r\n':
399 break
399 break
400 return True
400 return True
401
401
402 if version == 'HTTP/1.0':
402 if version == 'HTTP/1.0':
403 res.version = 10
403 res.version = 10
404 elif version.startswith('HTTP/1.'):
404 elif version.startswith('HTTP/1.'):
405 res.version = 11
405 res.version = 11
406 elif version == 'HTTP/0.9':
406 elif version == 'HTTP/0.9':
407 res.version = 9
407 res.version = 9
408 else:
408 else:
409 raise httplib.UnknownProtocol(version)
409 raise httplib.UnknownProtocol(version)
410
410
411 if res.version == 9:
411 if res.version == 9:
412 res.length = None
412 res.length = None
413 res.chunked = 0
413 res.chunked = 0
414 res.will_close = 1
414 res.will_close = 1
415 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
415 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
416 return False
416 return False
417
417
418 res.msg = httplib.HTTPMessage(res.fp)
418 res.msg = httplib.HTTPMessage(res.fp)
419 res.msg.fp = None
419 res.msg.fp = None
420
420
421 # are we using the chunked-style of transfer encoding?
421 # are we using the chunked-style of transfer encoding?
422 trenc = res.msg.getheader('transfer-encoding')
422 trenc = res.msg.getheader('transfer-encoding')
423 if trenc and trenc.lower() == "chunked":
423 if trenc and trenc.lower() == "chunked":
424 res.chunked = 1
424 res.chunked = 1
425 res.chunk_left = None
425 res.chunk_left = None
426 else:
426 else:
427 res.chunked = 0
427 res.chunked = 0
428
428
429 # will the connection close at the end of the response?
429 # will the connection close at the end of the response?
430 res.will_close = res._check_close()
430 res.will_close = res._check_close()
431
431
432 # do we have a Content-Length?
432 # do we have a Content-Length?
433 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
433 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
434 length = res.msg.getheader('content-length')
434 length = res.msg.getheader('content-length')
435 if length and not res.chunked:
435 if length and not res.chunked:
436 try:
436 try:
437 res.length = int(length)
437 res.length = int(length)
438 except ValueError:
438 except ValueError:
439 res.length = None
439 res.length = None
440 else:
440 else:
441 if res.length < 0: # ignore nonsensical negative lengths
441 if res.length < 0: # ignore nonsensical negative lengths
442 res.length = None
442 res.length = None
443 else:
443 else:
444 res.length = None
444 res.length = None
445
445
446 # does the body have a fixed length? (of zero)
446 # does the body have a fixed length? (of zero)
447 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
447 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
448 100 <= status < 200 or # 1xx codes
448 100 <= status < 200 or # 1xx codes
449 res._method == 'HEAD'):
449 res._method == 'HEAD'):
450 res.length = 0
450 res.length = 0
451
451
452 # if the connection remains open, and we aren't using chunked, and
452 # if the connection remains open, and we aren't using chunked, and
453 # a content-length was not provided, then assume that the connection
453 # a content-length was not provided, then assume that the connection
454 # WILL close.
454 # WILL close.
455 if (not res.will_close and
455 if (not res.will_close and
456 not res.chunked and
456 not res.chunked and
457 res.length is None):
457 res.length is None):
458 res.will_close = 1
458 res.will_close = 1
459
459
460 self.proxyres = res
460 self.proxyres = res
461
461
462 return False
462 return False
463
463
464 class httphandler(keepalive.HTTPHandler):
464 class httphandler(keepalive.HTTPHandler):
465 def http_open(self, req):
465 def http_open(self, req):
466 return self.do_open(httpconnection, req)
466 return self.do_open(httpconnection, req)
467
467
468 def _start_transaction(self, h, req):
468 def _start_transaction(self, h, req):
469 _generic_start_transaction(self, h, req)
469 _generic_start_transaction(self, h, req)
470 return keepalive.HTTPHandler._start_transaction(self, h, req)
470 return keepalive.HTTPHandler._start_transaction(self, h, req)
471
471
472 def __del__(self):
472 def __del__(self):
473 self.close_all()
473 self.close_all()
474
474
475 if has_https:
475 if has_https:
476 class BetterHTTPS(httplib.HTTPSConnection):
476 class BetterHTTPS(httplib.HTTPSConnection):
477 send = keepalive.safesend
477 send = keepalive.safesend
478
478
479 def connect(self):
479 def connect(self):
480 if hasattr(self, 'ui'):
480 if hasattr(self, 'ui'):
481 cacerts = self.ui.config('web', 'cacerts')
481 cacerts = self.ui.config('web', 'cacerts')
482 else:
482 else:
483 cacerts = None
483 cacerts = None
484
484
485 if cacerts:
485 if cacerts:
486 sock = _create_connection((self.host, self.port))
486 sock = _create_connection((self.host, self.port))
487 self.sock = _ssl_wrap_socket(sock, self.key_file,
487 self.sock = _ssl_wrap_socket(sock, self.key_file,
488 self.cert_file, cert_reqs=CERT_REQUIRED,
488 self.cert_file, cert_reqs=CERT_REQUIRED,
489 ca_certs=cacerts)
489 ca_certs=cacerts)
490 self.ui.debug(_('server identity verification succeeded\n'))
490 self.ui.debug(_('server identity verification succeeded\n'))
491 else:
491 else:
492 httplib.HTTPSConnection.connect(self)
492 httplib.HTTPSConnection.connect(self)
493
493
494 class httpsconnection(BetterHTTPS):
494 class httpsconnection(BetterHTTPS):
495 response_class = keepalive.HTTPResponse
495 response_class = keepalive.HTTPResponse
496 # must be able to send big bundle as stream.
496 # must be able to send big bundle as stream.
497 send = _gen_sendfile(BetterHTTPS)
497 send = _gen_sendfile(BetterHTTPS)
498 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
498 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
499
499
500 def connect(self):
500 def connect(self):
501 if self.realhostport: # use CONNECT proxy
501 if self.realhostport: # use CONNECT proxy
502 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
502 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
503 self.sock.connect((self.host, self.port))
503 self.sock.connect((self.host, self.port))
504 if _generic_proxytunnel(self):
504 if _generic_proxytunnel(self):
505 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
505 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
506 self.key_file)
506 self.key_file)
507 else:
507 else:
508 BetterHTTPS.connect(self)
508 BetterHTTPS.connect(self)
509
509
510 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
510 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
511 def __init__(self, ui):
511 def __init__(self, ui):
512 keepalive.KeepAliveHandler.__init__(self)
512 keepalive.KeepAliveHandler.__init__(self)
513 urllib2.HTTPSHandler.__init__(self)
513 urllib2.HTTPSHandler.__init__(self)
514 self.ui = ui
514 self.ui = ui
515 self.pwmgr = passwordmgr(self.ui)
515 self.pwmgr = passwordmgr(self.ui)
516
516
517 def _start_transaction(self, h, req):
517 def _start_transaction(self, h, req):
518 _generic_start_transaction(self, h, req)
518 _generic_start_transaction(self, h, req)
519 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
519 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
520
520
521 def https_open(self, req):
521 def https_open(self, req):
522 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
522 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
523 return self.do_open(self._makeconnection, req)
523 return self.do_open(self._makeconnection, req)
524
524
525 def _makeconnection(self, host, port=None, *args, **kwargs):
525 def _makeconnection(self, host, port=None, *args, **kwargs):
526 keyfile = None
526 keyfile = None
527 certfile = None
527 certfile = None
528
528
529 if len(args) >= 1: # key_file
529 if len(args) >= 1: # key_file
530 keyfile = args[0]
530 keyfile = args[0]
531 if len(args) >= 2: # cert_file
531 if len(args) >= 2: # cert_file
532 certfile = args[1]
532 certfile = args[1]
533 args = args[2:]
533 args = args[2:]
534
534
535 # if the user has specified different key/cert files in
535 # if the user has specified different key/cert files in
536 # hgrc, we prefer these
536 # hgrc, we prefer these
537 if self.auth and 'key' in self.auth and 'cert' in self.auth:
537 if self.auth and 'key' in self.auth and 'cert' in self.auth:
538 keyfile = self.auth['key']
538 keyfile = self.auth['key']
539 certfile = self.auth['cert']
539 certfile = self.auth['cert']
540
540
541 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
541 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
542 conn.ui = self.ui
542 conn.ui = self.ui
543 return conn
543 return conn
544
544
545 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
546 def __init__(self, *args, **kwargs):
547 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
548 self.retried_req = None
549
550 def reset_retry_count(self):
551 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
552 # forever. We disable reset_retry_count completely and reset in
553 # http_error_auth_reqed instead.
554 pass
555
556 def http_error_auth_reqed(self, auth_header, host, req, headers):
557 # Reset the retry counter once for each request.
558 if req is not self.retried_req:
559 self.retried_req = req
560 self.retried = 0
545 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
561 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
546 # it doesn't know about the auth type requested. This can happen if
562 # it doesn't know about the auth type requested. This can happen if
547 # somebody is using BasicAuth and types a bad password.
563 # somebody is using BasicAuth and types a bad password.
548 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
549 def http_error_auth_reqed(self, auth_header, host, req, headers):
550 try:
564 try:
551 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
565 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
552 self, auth_header, host, req, headers)
566 self, auth_header, host, req, headers)
553 except ValueError, inst:
567 except ValueError, inst:
554 arg = inst.args[0]
568 arg = inst.args[0]
555 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
569 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
556 return
570 return
557 raise
571 raise
558
572
559 # Python 2.6.5 will keep resetting the retry count on redirects, for
560 # example when the server returns 401 on failing auth (like google code
561 # currently does). We stop the endless recursion by not resetting the
562 # count.
563 def reset_retry_count(self):
564 pass
565
566 def getauthinfo(path):
573 def getauthinfo(path):
567 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
574 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
568 if not urlpath:
575 if not urlpath:
569 urlpath = '/'
576 urlpath = '/'
570 if scheme != 'file':
577 if scheme != 'file':
571 # XXX: why are we quoting the path again with some smart
578 # XXX: why are we quoting the path again with some smart
572 # heuristic here? Anyway, it cannot be done with file://
579 # heuristic here? Anyway, it cannot be done with file://
573 # urls since path encoding is os/fs dependent (see
580 # urls since path encoding is os/fs dependent (see
574 # urllib.pathname2url() for details).
581 # urllib.pathname2url() for details).
575 urlpath = quotepath(urlpath)
582 urlpath = quotepath(urlpath)
576 host, port, user, passwd = netlocsplit(netloc)
583 host, port, user, passwd = netlocsplit(netloc)
577
584
578 # urllib cannot handle URLs with embedded user or passwd
585 # urllib cannot handle URLs with embedded user or passwd
579 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
586 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
580 urlpath, query, frag))
587 urlpath, query, frag))
581 if user:
588 if user:
582 netloc = host
589 netloc = host
583 if port:
590 if port:
584 netloc += ':' + port
591 netloc += ':' + port
585 # Python < 2.4.3 uses only the netloc to search for a password
592 # Python < 2.4.3 uses only the netloc to search for a password
586 authinfo = (None, (url, netloc), user, passwd or '')
593 authinfo = (None, (url, netloc), user, passwd or '')
587 else:
594 else:
588 authinfo = None
595 authinfo = None
589 return url, authinfo
596 return url, authinfo
590
597
591 handlerfuncs = []
598 handlerfuncs = []
592
599
593 def opener(ui, authinfo=None):
600 def opener(ui, authinfo=None):
594 '''
601 '''
595 construct an opener suitable for urllib2
602 construct an opener suitable for urllib2
596 authinfo will be added to the password manager
603 authinfo will be added to the password manager
597 '''
604 '''
598 handlers = [httphandler()]
605 handlers = [httphandler()]
599 if has_https:
606 if has_https:
600 handlers.append(httpshandler(ui))
607 handlers.append(httpshandler(ui))
601
608
602 handlers.append(proxyhandler(ui))
609 handlers.append(proxyhandler(ui))
603
610
604 passmgr = passwordmgr(ui)
611 passmgr = passwordmgr(ui)
605 if authinfo is not None:
612 if authinfo is not None:
606 passmgr.add_password(*authinfo)
613 passmgr.add_password(*authinfo)
607 user, passwd = authinfo[2:4]
614 user, passwd = authinfo[2:4]
608 ui.debug('http auth: user %s, password %s\n' %
615 ui.debug('http auth: user %s, password %s\n' %
609 (user, passwd and '*' * len(passwd) or 'not set'))
616 (user, passwd and '*' * len(passwd) or 'not set'))
610
617
611 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
618 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
612 httpdigestauthhandler(passmgr)))
619 httpdigestauthhandler(passmgr)))
613 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
620 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
614 opener = urllib2.build_opener(*handlers)
621 opener = urllib2.build_opener(*handlers)
615
622
616 # 1.0 here is the _protocol_ version
623 # 1.0 here is the _protocol_ version
617 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
624 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
618 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
625 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
619 return opener
626 return opener
620
627
621 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
628 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
622
629
623 def open(ui, url, data=None):
630 def open(ui, url, data=None):
624 scheme = None
631 scheme = None
625 m = scheme_re.search(url)
632 m = scheme_re.search(url)
626 if m:
633 if m:
627 scheme = m.group(1).lower()
634 scheme = m.group(1).lower()
628 if not scheme:
635 if not scheme:
629 path = util.normpath(os.path.abspath(url))
636 path = util.normpath(os.path.abspath(url))
630 url = 'file://' + urllib.pathname2url(path)
637 url = 'file://' + urllib.pathname2url(path)
631 authinfo = None
638 authinfo = None
632 else:
639 else:
633 url, authinfo = getauthinfo(url)
640 url, authinfo = getauthinfo(url)
634 return opener(ui, authinfo).open(url, data)
641 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now