##// END OF EJS Templates
httprepo: factor out proxy handling
Benoit Boissinot -
r7269:95a53961 default
parent child Browse files
Show More
@@ -1,467 +1,481
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from node import bin, hex, nullid
9 from node import bin, hex, nullid
10 from i18n import _
10 from i18n import _
11 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
11 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
12 import errno, keepalive, socket, changegroup, statichttprepo
12 import errno, keepalive, socket, changegroup, statichttprepo
13
13
14 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
14 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 def __init__(self, ui):
15 def __init__(self, ui):
16 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
16 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 self.ui = ui
17 self.ui = ui
18
18
19 def find_user_password(self, realm, authuri):
19 def find_user_password(self, realm, authuri):
20 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
20 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 self, realm, authuri)
21 self, realm, authuri)
22 user, passwd = authinfo
22 user, passwd = authinfo
23 if user and passwd:
23 if user and passwd:
24 return (user, passwd)
24 return (user, passwd)
25
25
26 if not self.ui.interactive:
26 if not self.ui.interactive:
27 raise util.Abort(_('http authorization required'))
27 raise util.Abort(_('http authorization required'))
28
28
29 self.ui.write(_("http authorization required\n"))
29 self.ui.write(_("http authorization required\n"))
30 self.ui.status(_("realm: %s\n") % realm)
30 self.ui.status(_("realm: %s\n") % realm)
31 if user:
31 if user:
32 self.ui.status(_("user: %s\n") % user)
32 self.ui.status(_("user: %s\n") % user)
33 else:
33 else:
34 user = self.ui.prompt(_("user:"), default=None)
34 user = self.ui.prompt(_("user:"), default=None)
35
35
36 if not passwd:
36 if not passwd:
37 passwd = self.ui.getpass()
37 passwd = self.ui.getpass()
38
38
39 self.add_password(realm, authuri, user, passwd)
39 self.add_password(realm, authuri, user, passwd)
40 return (user, passwd)
40 return (user, passwd)
41
41
42 class proxyhandler(urllib2.ProxyHandler):
43 def __init__(self, ui):
44 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
45 # XXX proxyauthinfo = None
46
47 if proxyurl:
48 # proxy can be proper url or host[:port]
49 if not (proxyurl.startswith('http:') or
50 proxyurl.startswith('https:')):
51 proxyurl = 'http://' + proxyurl + '/'
52 snpqf = urlparse.urlsplit(proxyurl)
53 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
54 hpup = netlocsplit(proxynetloc)
55
56 proxyhost, proxyport, proxyuser, proxypasswd = hpup
57 if not proxyuser:
58 proxyuser = ui.config("http_proxy", "user")
59 proxypasswd = ui.config("http_proxy", "passwd")
60
61 # see if we should use a proxy for this url
62 no_list = [ "localhost", "127.0.0.1" ]
63 no_list.extend([p.lower() for
64 p in ui.configlist("http_proxy", "no")])
65 no_list.extend([p.strip().lower() for
66 p in os.getenv("no_proxy", '').split(',')
67 if p.strip()])
68 # "http_proxy.always" config is for running tests on localhost
69 if ui.configbool("http_proxy", "always"):
70 self.no_list = []
71 else:
72 self.no_list = no_list
73
74 proxyurl = urlparse.urlunsplit((
75 proxyscheme, netlocunsplit(proxyhost, proxyport,
76 proxyuser, proxypasswd or ''),
77 proxypath, proxyquery, proxyfrag))
78 proxies = {'http': proxyurl, 'https': proxyurl}
79 ui.debug(_('proxying through http://%s:%s\n') %
80 (proxyhost, proxyport))
81 else:
82 proxies = {}
83
84 # urllib2 takes proxy values from the environment and those
85 # will take precedence if found, so drop them
86 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
87 try:
88 if env in os.environ:
89 del os.environ[env]
90 except OSError:
91 pass
92
93 urllib2.ProxyHandler.__init__(self, proxies)
94 self.ui = ui
95
96 def proxy_open(self, req, proxy, type):
97 host = req.get_host().split(':')[0]
98 if host in self.no_list:
99 return None
100 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type)
101
42 def netlocsplit(netloc):
102 def netlocsplit(netloc):
43 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
103 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
44
104
45 a = netloc.find('@')
105 a = netloc.find('@')
46 if a == -1:
106 if a == -1:
47 user, passwd = None, None
107 user, passwd = None, None
48 else:
108 else:
49 userpass, netloc = netloc[:a], netloc[a+1:]
109 userpass, netloc = netloc[:a], netloc[a+1:]
50 c = userpass.find(':')
110 c = userpass.find(':')
51 if c == -1:
111 if c == -1:
52 user, passwd = urllib.unquote(userpass), None
112 user, passwd = urllib.unquote(userpass), None
53 else:
113 else:
54 user = urllib.unquote(userpass[:c])
114 user = urllib.unquote(userpass[:c])
55 passwd = urllib.unquote(userpass[c+1:])
115 passwd = urllib.unquote(userpass[c+1:])
56 c = netloc.find(':')
116 c = netloc.find(':')
57 if c == -1:
117 if c == -1:
58 host, port = netloc, None
118 host, port = netloc, None
59 else:
119 else:
60 host, port = netloc[:c], netloc[c+1:]
120 host, port = netloc[:c], netloc[c+1:]
61 return host, port, user, passwd
121 return host, port, user, passwd
62
122
63 def netlocunsplit(host, port, user=None, passwd=None):
123 def netlocunsplit(host, port, user=None, passwd=None):
64 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
124 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
65 if port:
125 if port:
66 hostport = host + ':' + port
126 hostport = host + ':' + port
67 else:
127 else:
68 hostport = host
128 hostport = host
69 if user:
129 if user:
70 if passwd:
130 if passwd:
71 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
131 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
72 else:
132 else:
73 userpass = urllib.quote(user)
133 userpass = urllib.quote(user)
74 return userpass + '@' + hostport
134 return userpass + '@' + hostport
75 return hostport
135 return hostport
76
136
77 # work around a bug in Python < 2.4.2
137 # work around a bug in Python < 2.4.2
78 # (it leaves a "\n" at the end of Proxy-authorization headers)
138 # (it leaves a "\n" at the end of Proxy-authorization headers)
79 class request(urllib2.Request):
139 class request(urllib2.Request):
80 def add_header(self, key, val):
140 def add_header(self, key, val):
81 if key.lower() == 'proxy-authorization':
141 if key.lower() == 'proxy-authorization':
82 val = val.strip()
142 val = val.strip()
83 return urllib2.Request.add_header(self, key, val)
143 return urllib2.Request.add_header(self, key, val)
84
144
85 class httpsendfile(file):
145 class httpsendfile(file):
86 def __len__(self):
146 def __len__(self):
87 return os.fstat(self.fileno()).st_size
147 return os.fstat(self.fileno()).st_size
88
148
89 def _gen_sendfile(connection):
149 def _gen_sendfile(connection):
90 def _sendfile(self, data):
150 def _sendfile(self, data):
91 # send a file
151 # send a file
92 if isinstance(data, httpsendfile):
152 if isinstance(data, httpsendfile):
93 # if auth required, some data sent twice, so rewind here
153 # if auth required, some data sent twice, so rewind here
94 data.seek(0)
154 data.seek(0)
95 for chunk in util.filechunkiter(data):
155 for chunk in util.filechunkiter(data):
96 connection.send(self, chunk)
156 connection.send(self, chunk)
97 else:
157 else:
98 connection.send(self, data)
158 connection.send(self, data)
99 return _sendfile
159 return _sendfile
100
160
101 class httpconnection(keepalive.HTTPConnection):
161 class httpconnection(keepalive.HTTPConnection):
102 # must be able to send big bundle as stream.
162 # must be able to send big bundle as stream.
103 send = _gen_sendfile(keepalive.HTTPConnection)
163 send = _gen_sendfile(keepalive.HTTPConnection)
104
164
105 class httphandler(keepalive.HTTPHandler):
165 class httphandler(keepalive.HTTPHandler):
106 def http_open(self, req):
166 def http_open(self, req):
107 return self.do_open(httpconnection, req)
167 return self.do_open(httpconnection, req)
108
168
109 def __del__(self):
169 def __del__(self):
110 self.close_all()
170 self.close_all()
111
171
112 has_https = hasattr(urllib2, 'HTTPSHandler')
172 has_https = hasattr(urllib2, 'HTTPSHandler')
113 if has_https:
173 if has_https:
114 class httpsconnection(httplib.HTTPSConnection):
174 class httpsconnection(httplib.HTTPSConnection):
115 response_class = keepalive.HTTPResponse
175 response_class = keepalive.HTTPResponse
116 # must be able to send big bundle as stream.
176 # must be able to send big bundle as stream.
117 send = _gen_sendfile(httplib.HTTPSConnection)
177 send = _gen_sendfile(httplib.HTTPSConnection)
118
178
119 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
179 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
120 def https_open(self, req):
180 def https_open(self, req):
121 return self.do_open(httpsconnection, req)
181 return self.do_open(httpsconnection, req)
122
182
123 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
183 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
124 # it doesn't know about the auth type requested. This can happen if
184 # it doesn't know about the auth type requested. This can happen if
125 # somebody is using BasicAuth and types a bad password.
185 # somebody is using BasicAuth and types a bad password.
126 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
186 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
127 def http_error_auth_reqed(self, auth_header, host, req, headers):
187 def http_error_auth_reqed(self, auth_header, host, req, headers):
128 try:
188 try:
129 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
189 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
130 self, auth_header, host, req, headers)
190 self, auth_header, host, req, headers)
131 except ValueError, inst:
191 except ValueError, inst:
132 arg = inst.args[0]
192 arg = inst.args[0]
133 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
193 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
134 return
194 return
135 raise
195 raise
136
196
137 def zgenerator(f):
197 def zgenerator(f):
138 zd = zlib.decompressobj()
198 zd = zlib.decompressobj()
139 try:
199 try:
140 for chunk in util.filechunkiter(f):
200 for chunk in util.filechunkiter(f):
141 yield zd.decompress(chunk)
201 yield zd.decompress(chunk)
142 except httplib.HTTPException, inst:
202 except httplib.HTTPException, inst:
143 raise IOError(None, _('connection ended unexpectedly'))
203 raise IOError(None, _('connection ended unexpectedly'))
144 yield zd.flush()
204 yield zd.flush()
145
205
146 _safe = ('abcdefghijklmnopqrstuvwxyz'
206 _safe = ('abcdefghijklmnopqrstuvwxyz'
147 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
207 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
148 '0123456789' '_.-/')
208 '0123456789' '_.-/')
149 _safeset = None
209 _safeset = None
150 _hex = None
210 _hex = None
151 def quotepath(path):
211 def quotepath(path):
152 '''quote the path part of a URL
212 '''quote the path part of a URL
153
213
154 This is similar to urllib.quote, but it also tries to avoid
214 This is similar to urllib.quote, but it also tries to avoid
155 quoting things twice (inspired by wget):
215 quoting things twice (inspired by wget):
156
216
157 >>> quotepath('abc def')
217 >>> quotepath('abc def')
158 'abc%20def'
218 'abc%20def'
159 >>> quotepath('abc%20def')
219 >>> quotepath('abc%20def')
160 'abc%20def'
220 'abc%20def'
161 >>> quotepath('abc%20 def')
221 >>> quotepath('abc%20 def')
162 'abc%20%20def'
222 'abc%20%20def'
163 >>> quotepath('abc def%20')
223 >>> quotepath('abc def%20')
164 'abc%20def%20'
224 'abc%20def%20'
165 >>> quotepath('abc def%2')
225 >>> quotepath('abc def%2')
166 'abc%20def%252'
226 'abc%20def%252'
167 >>> quotepath('abc def%')
227 >>> quotepath('abc def%')
168 'abc%20def%25'
228 'abc%20def%25'
169 '''
229 '''
170 global _safeset, _hex
230 global _safeset, _hex
171 if _safeset is None:
231 if _safeset is None:
172 _safeset = util.set(_safe)
232 _safeset = util.set(_safe)
173 _hex = util.set('abcdefABCDEF0123456789')
233 _hex = util.set('abcdefABCDEF0123456789')
174 l = list(path)
234 l = list(path)
175 for i in xrange(len(l)):
235 for i in xrange(len(l)):
176 c = l[i]
236 c = l[i]
177 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
237 if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
178 pass
238 pass
179 elif c not in _safeset:
239 elif c not in _safeset:
180 l[i] = '%%%02X' % ord(c)
240 l[i] = '%%%02X' % ord(c)
181 return ''.join(l)
241 return ''.join(l)
182
242
183 class httprepository(repo.repository):
243 class httprepository(repo.repository):
184 def __init__(self, ui, path):
244 def __init__(self, ui, path):
185 self.path = path
245 self.path = path
186 self.caps = None
246 self.caps = None
187 self.handler = None
247 self.handler = None
188 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
248 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
189 if query or frag:
249 if query or frag:
190 raise util.Abort(_('unsupported URL component: "%s"') %
250 raise util.Abort(_('unsupported URL component: "%s"') %
191 (query or frag))
251 (query or frag))
192 if not urlpath:
252 if not urlpath:
193 urlpath = '/'
253 urlpath = '/'
194 urlpath = quotepath(urlpath)
254 urlpath = quotepath(urlpath)
195 host, port, user, passwd = netlocsplit(netloc)
255 host, port, user, passwd = netlocsplit(netloc)
196
256
197 # urllib cannot handle URLs with embedded user or passwd
257 # urllib cannot handle URLs with embedded user or passwd
198 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
258 self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
199 urlpath, '', ''))
259 urlpath, '', ''))
200 self.ui = ui
260 self.ui = ui
201 self.ui.debug(_('using %s\n') % self._url)
261 self.ui.debug(_('using %s\n') % self._url)
202
262
203 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
204 # XXX proxyauthinfo = None
205 handlers = [httphandler()]
263 handlers = [httphandler()]
206 if has_https:
264 if has_https:
207 handlers.append(httpshandler())
265 handlers.append(httpshandler())
208
266
209 if proxyurl:
267 handlers.append(proxyhandler(ui))
210 # proxy can be proper url or host[:port]
211 if not (proxyurl.startswith('http:') or
212 proxyurl.startswith('https:')):
213 proxyurl = 'http://' + proxyurl + '/'
214 snpqf = urlparse.urlsplit(proxyurl)
215 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
216 hpup = netlocsplit(proxynetloc)
217
218 proxyhost, proxyport, proxyuser, proxypasswd = hpup
219 if not proxyuser:
220 proxyuser = ui.config("http_proxy", "user")
221 proxypasswd = ui.config("http_proxy", "passwd")
222
223 # see if we should use a proxy for this url
224 no_list = [ "localhost", "127.0.0.1" ]
225 no_list.extend([p.lower() for
226 p in ui.configlist("http_proxy", "no")])
227 no_list.extend([p.strip().lower() for
228 p in os.getenv("no_proxy", '').split(',')
229 if p.strip()])
230 # "http_proxy.always" config is for running tests on localhost
231 if (not ui.configbool("http_proxy", "always") and
232 host.lower() in no_list):
233 # avoid auto-detection of proxy settings by appending
234 # a ProxyHandler with no proxies defined.
235 handlers.append(urllib2.ProxyHandler({}))
236 ui.debug(_('disabling proxy for %s\n') % host)
237 else:
238 proxyurl = urlparse.urlunsplit((
239 proxyscheme, netlocunsplit(proxyhost, proxyport,
240 proxyuser, proxypasswd or ''),
241 proxypath, proxyquery, proxyfrag))
242 handlers.append(urllib2.ProxyHandler({scheme: proxyurl}))
243 ui.debug(_('proxying through http://%s:%s\n') %
244 (proxyhost, proxyport))
245
246 # urllib2 takes proxy values from the environment and those
247 # will take precedence if found, so drop them
248 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
249 try:
250 if env in os.environ:
251 del os.environ[env]
252 except OSError:
253 pass
254
268
255 passmgr = passwordmgr(ui)
269 passmgr = passwordmgr(ui)
256 if user:
270 if user:
257 ui.debug(_('http auth: user %s, password %s\n') %
271 ui.debug(_('http auth: user %s, password %s\n') %
258 (user, passwd and '*' * len(passwd) or 'not set'))
272 (user, passwd and '*' * len(passwd) or 'not set'))
259 netloc = host
273 netloc = host
260 if port:
274 if port:
261 netloc += ':' + port
275 netloc += ':' + port
262 # Python < 2.4.3 uses only the netloc to search for a password
276 # Python < 2.4.3 uses only the netloc to search for a password
263 passmgr.add_password(None, (self._url, netloc), user, passwd or '')
277 passmgr.add_password(None, (self._url, netloc), user, passwd or '')
264
278
265 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
279 handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
266 httpdigestauthhandler(passmgr)))
280 httpdigestauthhandler(passmgr)))
267 opener = urllib2.build_opener(*handlers)
281 opener = urllib2.build_opener(*handlers)
268
282
269 # 1.0 here is the _protocol_ version
283 # 1.0 here is the _protocol_ version
270 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
284 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
271 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
285 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
272 urllib2.install_opener(opener)
286 urllib2.install_opener(opener)
273
287
274 def url(self):
288 def url(self):
275 return self.path
289 return self.path
276
290
277 # look up capabilities only when needed
291 # look up capabilities only when needed
278
292
279 def get_caps(self):
293 def get_caps(self):
280 if self.caps is None:
294 if self.caps is None:
281 try:
295 try:
282 self.caps = util.set(self.do_read('capabilities').split())
296 self.caps = util.set(self.do_read('capabilities').split())
283 except repo.RepoError:
297 except repo.RepoError:
284 self.caps = util.set()
298 self.caps = util.set()
285 self.ui.debug(_('capabilities: %s\n') %
299 self.ui.debug(_('capabilities: %s\n') %
286 (' '.join(self.caps or ['none'])))
300 (' '.join(self.caps or ['none'])))
287 return self.caps
301 return self.caps
288
302
289 capabilities = property(get_caps)
303 capabilities = property(get_caps)
290
304
291 def lock(self):
305 def lock(self):
292 raise util.Abort(_('operation not supported over http'))
306 raise util.Abort(_('operation not supported over http'))
293
307
294 def do_cmd(self, cmd, **args):
308 def do_cmd(self, cmd, **args):
295 data = args.pop('data', None)
309 data = args.pop('data', None)
296 headers = args.pop('headers', {})
310 headers = args.pop('headers', {})
297 self.ui.debug(_("sending %s command\n") % cmd)
311 self.ui.debug(_("sending %s command\n") % cmd)
298 q = {"cmd": cmd}
312 q = {"cmd": cmd}
299 q.update(args)
313 q.update(args)
300 qs = '?%s' % urllib.urlencode(q)
314 qs = '?%s' % urllib.urlencode(q)
301 cu = "%s%s" % (self._url, qs)
315 cu = "%s%s" % (self._url, qs)
302 try:
316 try:
303 if data:
317 if data:
304 self.ui.debug(_("sending %s bytes\n") % len(data))
318 self.ui.debug(_("sending %s bytes\n") % len(data))
305 resp = urllib2.urlopen(request(cu, data, headers))
319 resp = urllib2.urlopen(request(cu, data, headers))
306 except urllib2.HTTPError, inst:
320 except urllib2.HTTPError, inst:
307 if inst.code == 401:
321 if inst.code == 401:
308 raise util.Abort(_('authorization failed'))
322 raise util.Abort(_('authorization failed'))
309 raise
323 raise
310 except httplib.HTTPException, inst:
324 except httplib.HTTPException, inst:
311 self.ui.debug(_('http error while sending %s command\n') % cmd)
325 self.ui.debug(_('http error while sending %s command\n') % cmd)
312 self.ui.print_exc()
326 self.ui.print_exc()
313 raise IOError(None, inst)
327 raise IOError(None, inst)
314 except IndexError:
328 except IndexError:
315 # this only happens with Python 2.3, later versions raise URLError
329 # this only happens with Python 2.3, later versions raise URLError
316 raise util.Abort(_('http error, possibly caused by proxy setting'))
330 raise util.Abort(_('http error, possibly caused by proxy setting'))
317 # record the url we got redirected to
331 # record the url we got redirected to
318 resp_url = resp.geturl()
332 resp_url = resp.geturl()
319 if resp_url.endswith(qs):
333 if resp_url.endswith(qs):
320 resp_url = resp_url[:-len(qs)]
334 resp_url = resp_url[:-len(qs)]
321 if self._url != resp_url:
335 if self._url != resp_url:
322 self.ui.status(_('real URL is %s\n') % resp_url)
336 self.ui.status(_('real URL is %s\n') % resp_url)
323 self._url = resp_url
337 self._url = resp_url
324 try:
338 try:
325 proto = resp.getheader('content-type')
339 proto = resp.getheader('content-type')
326 except AttributeError:
340 except AttributeError:
327 proto = resp.headers['content-type']
341 proto = resp.headers['content-type']
328
342
329 # accept old "text/plain" and "application/hg-changegroup" for now
343 # accept old "text/plain" and "application/hg-changegroup" for now
330 if not (proto.startswith('application/mercurial-') or
344 if not (proto.startswith('application/mercurial-') or
331 proto.startswith('text/plain') or
345 proto.startswith('text/plain') or
332 proto.startswith('application/hg-changegroup')):
346 proto.startswith('application/hg-changegroup')):
333 self.ui.debug(_("Requested URL: '%s'\n") % cu)
347 self.ui.debug(_("Requested URL: '%s'\n") % cu)
334 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
348 raise repo.RepoError(_("'%s' does not appear to be an hg repository")
335 % self._url)
349 % self._url)
336
350
337 if proto.startswith('application/mercurial-'):
351 if proto.startswith('application/mercurial-'):
338 try:
352 try:
339 version = proto.split('-', 1)[1]
353 version = proto.split('-', 1)[1]
340 version_info = tuple([int(n) for n in version.split('.')])
354 version_info = tuple([int(n) for n in version.split('.')])
341 except ValueError:
355 except ValueError:
342 raise repo.RepoError(_("'%s' sent a broken Content-Type "
356 raise repo.RepoError(_("'%s' sent a broken Content-Type "
343 "header (%s)") % (self._url, proto))
357 "header (%s)") % (self._url, proto))
344 if version_info > (0, 1):
358 if version_info > (0, 1):
345 raise repo.RepoError(_("'%s' uses newer protocol %s") %
359 raise repo.RepoError(_("'%s' uses newer protocol %s") %
346 (self._url, version))
360 (self._url, version))
347
361
348 return resp
362 return resp
349
363
350 def do_read(self, cmd, **args):
364 def do_read(self, cmd, **args):
351 fp = self.do_cmd(cmd, **args)
365 fp = self.do_cmd(cmd, **args)
352 try:
366 try:
353 return fp.read()
367 return fp.read()
354 finally:
368 finally:
355 # if using keepalive, allow connection to be reused
369 # if using keepalive, allow connection to be reused
356 fp.close()
370 fp.close()
357
371
358 def lookup(self, key):
372 def lookup(self, key):
359 self.requirecap('lookup', _('look up remote revision'))
373 self.requirecap('lookup', _('look up remote revision'))
360 d = self.do_cmd("lookup", key = key).read()
374 d = self.do_cmd("lookup", key = key).read()
361 success, data = d[:-1].split(' ', 1)
375 success, data = d[:-1].split(' ', 1)
362 if int(success):
376 if int(success):
363 return bin(data)
377 return bin(data)
364 raise repo.RepoError(data)
378 raise repo.RepoError(data)
365
379
366 def heads(self):
380 def heads(self):
367 d = self.do_read("heads")
381 d = self.do_read("heads")
368 try:
382 try:
369 return map(bin, d[:-1].split(" "))
383 return map(bin, d[:-1].split(" "))
370 except:
384 except:
371 raise util.UnexpectedOutput(_("unexpected response:"), d)
385 raise util.UnexpectedOutput(_("unexpected response:"), d)
372
386
373 def branches(self, nodes):
387 def branches(self, nodes):
374 n = " ".join(map(hex, nodes))
388 n = " ".join(map(hex, nodes))
375 d = self.do_read("branches", nodes=n)
389 d = self.do_read("branches", nodes=n)
376 try:
390 try:
377 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
391 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
378 return br
392 return br
379 except:
393 except:
380 raise util.UnexpectedOutput(_("unexpected response:"), d)
394 raise util.UnexpectedOutput(_("unexpected response:"), d)
381
395
382 def between(self, pairs):
396 def between(self, pairs):
383 n = " ".join(["-".join(map(hex, p)) for p in pairs])
397 n = " ".join(["-".join(map(hex, p)) for p in pairs])
384 d = self.do_read("between", pairs=n)
398 d = self.do_read("between", pairs=n)
385 try:
399 try:
386 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
400 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
387 return p
401 return p
388 except:
402 except:
389 raise util.UnexpectedOutput(_("unexpected response:"), d)
403 raise util.UnexpectedOutput(_("unexpected response:"), d)
390
404
391 def changegroup(self, nodes, kind):
405 def changegroup(self, nodes, kind):
392 n = " ".join(map(hex, nodes))
406 n = " ".join(map(hex, nodes))
393 f = self.do_cmd("changegroup", roots=n)
407 f = self.do_cmd("changegroup", roots=n)
394 return util.chunkbuffer(zgenerator(f))
408 return util.chunkbuffer(zgenerator(f))
395
409
396 def changegroupsubset(self, bases, heads, source):
410 def changegroupsubset(self, bases, heads, source):
397 self.requirecap('changegroupsubset', _('look up remote changes'))
411 self.requirecap('changegroupsubset', _('look up remote changes'))
398 baselst = " ".join([hex(n) for n in bases])
412 baselst = " ".join([hex(n) for n in bases])
399 headlst = " ".join([hex(n) for n in heads])
413 headlst = " ".join([hex(n) for n in heads])
400 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
414 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
401 return util.chunkbuffer(zgenerator(f))
415 return util.chunkbuffer(zgenerator(f))
402
416
403 def unbundle(self, cg, heads, source):
417 def unbundle(self, cg, heads, source):
404 # have to stream bundle to a temp file because we do not have
418 # have to stream bundle to a temp file because we do not have
405 # http 1.1 chunked transfer.
419 # http 1.1 chunked transfer.
406
420
407 type = ""
421 type = ""
408 types = self.capable('unbundle')
422 types = self.capable('unbundle')
409 # servers older than d1b16a746db6 will send 'unbundle' as a
423 # servers older than d1b16a746db6 will send 'unbundle' as a
410 # boolean capability
424 # boolean capability
411 try:
425 try:
412 types = types.split(',')
426 types = types.split(',')
413 except AttributeError:
427 except AttributeError:
414 types = [""]
428 types = [""]
415 if types:
429 if types:
416 for x in types:
430 for x in types:
417 if x in changegroup.bundletypes:
431 if x in changegroup.bundletypes:
418 type = x
432 type = x
419 break
433 break
420
434
421 tempname = changegroup.writebundle(cg, None, type)
435 tempname = changegroup.writebundle(cg, None, type)
422 fp = httpsendfile(tempname, "rb")
436 fp = httpsendfile(tempname, "rb")
423 try:
437 try:
424 try:
438 try:
425 resp = self.do_read(
439 resp = self.do_read(
426 'unbundle', data=fp,
440 'unbundle', data=fp,
427 headers={'Content-Type': 'application/octet-stream'},
441 headers={'Content-Type': 'application/octet-stream'},
428 heads=' '.join(map(hex, heads)))
442 heads=' '.join(map(hex, heads)))
429 resp_code, output = resp.split('\n', 1)
443 resp_code, output = resp.split('\n', 1)
430 try:
444 try:
431 ret = int(resp_code)
445 ret = int(resp_code)
432 except ValueError, err:
446 except ValueError, err:
433 raise util.UnexpectedOutput(
447 raise util.UnexpectedOutput(
434 _('push failed (unexpected response):'), resp)
448 _('push failed (unexpected response):'), resp)
435 self.ui.write(output)
449 self.ui.write(output)
436 return ret
450 return ret
437 except socket.error, err:
451 except socket.error, err:
438 if err[0] in (errno.ECONNRESET, errno.EPIPE):
452 if err[0] in (errno.ECONNRESET, errno.EPIPE):
439 raise util.Abort(_('push failed: %s') % err[1])
453 raise util.Abort(_('push failed: %s') % err[1])
440 raise util.Abort(err[1])
454 raise util.Abort(err[1])
441 finally:
455 finally:
442 fp.close()
456 fp.close()
443 os.unlink(tempname)
457 os.unlink(tempname)
444
458
445 def stream_out(self):
459 def stream_out(self):
446 return self.do_cmd('stream_out')
460 return self.do_cmd('stream_out')
447
461
448 class httpsrepository(httprepository):
462 class httpsrepository(httprepository):
449 def __init__(self, ui, path):
463 def __init__(self, ui, path):
450 if not has_https:
464 if not has_https:
451 raise util.Abort(_('Python support for SSL and HTTPS '
465 raise util.Abort(_('Python support for SSL and HTTPS '
452 'is not installed'))
466 'is not installed'))
453 httprepository.__init__(self, ui, path)
467 httprepository.__init__(self, ui, path)
454
468
455 def instance(ui, path, create):
469 def instance(ui, path, create):
456 if create:
470 if create:
457 raise util.Abort(_('cannot create new http repository'))
471 raise util.Abort(_('cannot create new http repository'))
458 try:
472 try:
459 if path.startswith('https:'):
473 if path.startswith('https:'):
460 inst = httpsrepository(ui, path)
474 inst = httpsrepository(ui, path)
461 else:
475 else:
462 inst = httprepository(ui, path)
476 inst = httprepository(ui, path)
463 inst.between([(nullid, nullid)])
477 inst.between([(nullid, nullid)])
464 return inst
478 return inst
465 except repo.RepoError:
479 except repo.RepoError:
466 ui.note('(falling back to static-http)\n')
480 ui.note('(falling back to static-http)\n')
467 return statichttprepo.instance(ui, "static-" + path, create)
481 return statichttprepo.instance(ui, "static-" + path, create)
@@ -1,42 +1,45
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init a
3 hg init a
4 cd a
4 cd a
5 echo a > a
5 echo a > a
6 hg ci -Ama -d '1123456789 0'
6 hg ci -Ama -d '1123456789 0'
7 hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
7 hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
8 cat hg.pid >> $DAEMON_PIDS
8 cat hg.pid >> $DAEMON_PIDS
9
9
10 cd ..
10 cd ..
11 ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
11 ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
12 echo $! > proxy.pid)
12 echo $! > proxy.pid)
13 cat proxy.pid >> $DAEMON_PIDS
13 cat proxy.pid >> $DAEMON_PIDS
14 sleep 2
14 sleep 2
15
15
16 echo %% url for proxy, stream
16 echo %% url for proxy, stream
17 http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b | \
17 http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b | \
18 sed -e 's/[0-9][0-9.]*/XXX/g' -e 's/[KM]\(B\/sec\)/X\1/'
18 sed -e 's/[0-9][0-9.]*/XXX/g' -e 's/[KM]\(B\/sec\)/X\1/'
19 cd b
19 cd b
20 hg verify
20 hg verify
21 cd ..
21 cd ..
22
22
23 echo %% url for proxy, pull
23 echo %% url for proxy, pull
24 http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone http://localhost:$HGPORT/ b-pull
24 http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone http://localhost:$HGPORT/ b-pull
25 cd b-pull
25 cd b-pull
26 hg verify
26 hg verify
27 cd ..
27 cd ..
28
28
29 echo %% host:port for proxy
29 echo %% host:port for proxy
30 http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ c
30 http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ c
31
31
32 echo %% proxy url with user name and password
32 echo %% proxy url with user name and password
33 http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ d
33 http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ d
34
34
35 echo %% url with user name and password
35 echo %% url with user name and password
36 http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://user:passwd@localhost:$HGPORT/ e
36 http_proxy=http://user:passwd@localhost:$HGPORT1 hg clone --config http_proxy.always=True http://user:passwd@localhost:$HGPORT/ e
37
37
38 echo %% bad host:port for proxy
38 echo %% bad host:port for proxy
39 http_proxy=localhost:$HGPORT2 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ f
39 http_proxy=localhost:$HGPORT2 hg clone --config http_proxy.always=True http://localhost:$HGPORT/ f
40
40
41 echo %% do not use the proxy if it is in the no list
42 http_proxy=localhost:$HGPORT1 hg clone --config http_proxy.no=localhost http://localhost:$HGPORT/ g
43
41 cat proxy.log | sed -e 's/^.*\] /XXX /'
44 cat proxy.log | sed -e 's/^.*\] /XXX /'
42 exit 0
45 exit 0
@@ -1,66 +1,74
1 adding a
1 adding a
2 %% url for proxy, stream
2 %% url for proxy, stream
3 streaming all changes
3 streaming all changes
4 XXX files to transfer, XXX bytes of data
4 XXX files to transfer, XXX bytes of data
5 transferred XXX bytes in XXX seconds (XXX XB/sec)
5 transferred XXX bytes in XXX seconds (XXX XB/sec)
6 updating working directory
6 updating working directory
7 XXX files updated, XXX files merged, XXX files removed, XXX files unresolved
7 XXX files updated, XXX files merged, XXX files removed, XXX files unresolved
8 checking changesets
8 checking changesets
9 checking manifests
9 checking manifests
10 crosschecking files in changesets and manifests
10 crosschecking files in changesets and manifests
11 checking files
11 checking files
12 1 files, 1 changesets, 1 total revisions
12 1 files, 1 changesets, 1 total revisions
13 %% url for proxy, pull
13 %% url for proxy, pull
14 requesting all changes
14 requesting all changes
15 adding changesets
15 adding changesets
16 adding manifests
16 adding manifests
17 adding file changes
17 adding file changes
18 added 1 changesets with 1 changes to 1 files
18 added 1 changesets with 1 changes to 1 files
19 updating working directory
19 updating working directory
20 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
21 checking changesets
21 checking changesets
22 checking manifests
22 checking manifests
23 crosschecking files in changesets and manifests
23 crosschecking files in changesets and manifests
24 checking files
24 checking files
25 1 files, 1 changesets, 1 total revisions
25 1 files, 1 changesets, 1 total revisions
26 %% host:port for proxy
26 %% host:port for proxy
27 requesting all changes
27 requesting all changes
28 adding changesets
28 adding changesets
29 adding manifests
29 adding manifests
30 adding file changes
30 adding file changes
31 added 1 changesets with 1 changes to 1 files
31 added 1 changesets with 1 changes to 1 files
32 updating working directory
32 updating working directory
33 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 %% proxy url with user name and password
34 %% proxy url with user name and password
35 requesting all changes
35 requesting all changes
36 adding changesets
36 adding changesets
37 adding manifests
37 adding manifests
38 adding file changes
38 adding file changes
39 added 1 changesets with 1 changes to 1 files
39 added 1 changesets with 1 changes to 1 files
40 updating working directory
40 updating working directory
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 %% url with user name and password
42 %% url with user name and password
43 requesting all changes
43 requesting all changes
44 adding changesets
44 adding changesets
45 adding manifests
45 adding manifests
46 adding file changes
46 adding file changes
47 added 1 changesets with 1 changes to 1 files
47 added 1 changesets with 1 changes to 1 files
48 updating working directory
48 updating working directory
49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 %% bad host:port for proxy
50 %% bad host:port for proxy
51 abort: error: Connection refused
51 abort: error: Connection refused
52 %% do not use the proxy if it is in the no list
53 requesting all changes
54 adding changesets
55 adding manifests
56 adding file changes
57 added 1 changesets with 1 changes to 1 files
58 updating working directory
59 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
60 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
53 XXX "GET http://localhost:20059/?cmd=capabilities HTTP/1.1" - -
61 XXX "GET http://localhost:20059/?cmd=capabilities HTTP/1.1" - -
54 XXX "GET http://localhost:20059/?cmd=stream_out HTTP/1.1" - -
62 XXX "GET http://localhost:20059/?cmd=stream_out HTTP/1.1" - -
55 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
63 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
56 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
64 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
57 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
65 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
58 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
66 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
59 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
67 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
60 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
68 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
61 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
69 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
62 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
70 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
63 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
71 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
64 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
72 XXX "GET http://localhost:20059/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - -
65 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
73 XXX "GET http://localhost:20059/?cmd=heads HTTP/1.1" - -
66 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
74 XXX "GET http://localhost:20059/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - -
General Comments 0
You need to be logged in to leave comments. Login now