##// END OF EJS Templates
http client: better work with authorization errors, broken sockets.
Vadim Gelfer -
r2467:4e78dc71 default
parent child Browse files
Show More
@@ -1,297 +1,306 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from remoterepo import *
9 from remoterepo import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
13 demandload(globals(), "keepalive tempfile socket")
13 demandload(globals(), "errno keepalive tempfile socket")
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 if authinfo != (None, None):
23 if authinfo != (None, None):
24 return authinfo
24 return authinfo
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 user = self.ui.prompt(_("user:"), default=None)
31 user = self.ui.prompt(_("user:"), default=None)
32 passwd = self.ui.getpass()
32 passwd = self.ui.getpass()
33
33
34 self.add_password(realm, authuri, user, passwd)
34 self.add_password(realm, authuri, user, passwd)
35 return (user, passwd)
35 return (user, passwd)
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 if passwd:
65 if passwd:
66 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
66 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
67 else:
67 else:
68 userpass = urllib.quote(user)
68 userpass = urllib.quote(user)
69 return userpass + '@' + hostport
69 return userpass + '@' + hostport
70 return hostport
70 return hostport
71
71
72 class httpconnection(keepalive.HTTPConnection):
72 class httpconnection(keepalive.HTTPConnection):
73 # must be able to send big bundle as stream.
73 # must be able to send big bundle as stream.
74
74
75 def send(self, data):
75 def send(self, data):
76 if isinstance(data, str):
76 if isinstance(data, str):
77 keepalive.HTTPConnection.send(self, data)
77 keepalive.HTTPConnection.send(self, data)
78 else:
78 else:
79 # if auth required, some data sent twice, so rewind here
79 # if auth required, some data sent twice, so rewind here
80 data.seek(0)
80 data.seek(0)
81 for chunk in util.filechunkiter(data):
81 for chunk in util.filechunkiter(data):
82 keepalive.HTTPConnection.send(self, chunk)
82 keepalive.HTTPConnection.send(self, chunk)
83
83
84 class httphandler(keepalive.HTTPHandler):
84 class httphandler(keepalive.HTTPHandler):
85 def http_open(self, req):
85 def http_open(self, req):
86 return self.do_open(httpconnection, req)
86 return self.do_open(httpconnection, req)
87
87
88 class httprepository(remoterepository):
88 class httprepository(remoterepository):
89 def __init__(self, ui, path):
89 def __init__(self, ui, path):
90 self.caps = None
90 self.caps = None
91 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
91 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
92 if query or frag:
92 if query or frag:
93 raise util.Abort(_('unsupported URL component: "%s"') %
93 raise util.Abort(_('unsupported URL component: "%s"') %
94 (query or frag))
94 (query or frag))
95 if not urlpath: urlpath = '/'
95 if not urlpath: urlpath = '/'
96 host, port, user, passwd = netlocsplit(netloc)
96 host, port, user, passwd = netlocsplit(netloc)
97
97
98 # urllib cannot handle URLs with embedded user or passwd
98 # urllib cannot handle URLs with embedded user or passwd
99 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
99 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
100 urlpath, '', ''))
100 urlpath, '', ''))
101 self.ui = ui
101 self.ui = ui
102
102
103 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
103 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
104 proxyauthinfo = None
104 proxyauthinfo = None
105 handler = httphandler()
105 handler = httphandler()
106
106
107 if proxyurl:
107 if proxyurl:
108 # proxy can be proper url or host[:port]
108 # proxy can be proper url or host[:port]
109 if not (proxyurl.startswith('http:') or
109 if not (proxyurl.startswith('http:') or
110 proxyurl.startswith('https:')):
110 proxyurl.startswith('https:')):
111 proxyurl = 'http://' + proxyurl + '/'
111 proxyurl = 'http://' + proxyurl + '/'
112 snpqf = urlparse.urlsplit(proxyurl)
112 snpqf = urlparse.urlsplit(proxyurl)
113 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
113 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
114 hpup = netlocsplit(proxynetloc)
114 hpup = netlocsplit(proxynetloc)
115
115
116 proxyhost, proxyport, proxyuser, proxypasswd = hpup
116 proxyhost, proxyport, proxyuser, proxypasswd = hpup
117 if not proxyuser:
117 if not proxyuser:
118 proxyuser = ui.config("http_proxy", "user")
118 proxyuser = ui.config("http_proxy", "user")
119 proxypasswd = ui.config("http_proxy", "passwd")
119 proxypasswd = ui.config("http_proxy", "passwd")
120
120
121 # see if we should use a proxy for this url
121 # see if we should use a proxy for this url
122 no_list = [ "localhost", "127.0.0.1" ]
122 no_list = [ "localhost", "127.0.0.1" ]
123 no_list.extend([p.strip().lower() for
123 no_list.extend([p.strip().lower() for
124 p in ui.config("http_proxy", "no", '').split(',')
124 p in ui.config("http_proxy", "no", '').split(',')
125 if p.strip()])
125 if p.strip()])
126 no_list.extend([p.strip().lower() for
126 no_list.extend([p.strip().lower() for
127 p in os.getenv("no_proxy", '').split(',')
127 p in os.getenv("no_proxy", '').split(',')
128 if p.strip()])
128 if p.strip()])
129 # "http_proxy.always" config is for running tests on localhost
129 # "http_proxy.always" config is for running tests on localhost
130 if (not ui.configbool("http_proxy", "always") and
130 if (not ui.configbool("http_proxy", "always") and
131 host.lower() in no_list):
131 host.lower() in no_list):
132 ui.debug(_('disabling proxy for %s\n') % host)
132 ui.debug(_('disabling proxy for %s\n') % host)
133 else:
133 else:
134 proxyurl = urlparse.urlunsplit((
134 proxyurl = urlparse.urlunsplit((
135 proxyscheme, netlocunsplit(proxyhost, proxyport,
135 proxyscheme, netlocunsplit(proxyhost, proxyport,
136 proxyuser, proxypasswd or ''),
136 proxyuser, proxypasswd or ''),
137 proxypath, proxyquery, proxyfrag))
137 proxypath, proxyquery, proxyfrag))
138 handler = urllib2.ProxyHandler({scheme: proxyurl})
138 handler = urllib2.ProxyHandler({scheme: proxyurl})
139 ui.debug(_('proxying through %s\n') % proxyurl)
139 ui.debug(_('proxying through %s\n') % proxyurl)
140
140
141 # urllib2 takes proxy values from the environment and those
141 # urllib2 takes proxy values from the environment and those
142 # will take precedence if found, so drop them
142 # will take precedence if found, so drop them
143 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
143 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
144 try:
144 try:
145 if os.environ.has_key(env):
145 if os.environ.has_key(env):
146 del os.environ[env]
146 del os.environ[env]
147 except OSError:
147 except OSError:
148 pass
148 pass
149
149
150 passmgr = passwordmgr(ui)
150 passmgr = passwordmgr(ui)
151 if user:
151 if user:
152 ui.debug(_('will use user %s, password %s for http auth\n') %
152 ui.debug(_('will use user %s, password %s for http auth\n') %
153 (user, '*' * len(passwd)))
153 (user, '*' * len(passwd)))
154 passmgr.add_password(None, host, user, passwd or '')
154 passmgr.add_password(None, host, user, passwd or '')
155
155
156 opener = urllib2.build_opener(
156 opener = urllib2.build_opener(
157 handler,
157 handler,
158 urllib2.HTTPBasicAuthHandler(passmgr),
158 urllib2.HTTPBasicAuthHandler(passmgr),
159 urllib2.HTTPDigestAuthHandler(passmgr))
159 urllib2.HTTPDigestAuthHandler(passmgr))
160
160
161 # 1.0 here is the _protocol_ version
161 # 1.0 here is the _protocol_ version
162 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
162 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
163 urllib2.install_opener(opener)
163 urllib2.install_opener(opener)
164
164
165 # look up capabilities only when needed
165 # look up capabilities only when needed
166
166
167 def get_caps(self):
167 def get_caps(self):
168 if self.caps is None:
168 if self.caps is None:
169 try:
169 try:
170 self.caps = self.do_read('capabilities').split()
170 self.caps = self.do_read('capabilities').split()
171 except hg.RepoError:
171 except hg.RepoError:
172 self.caps = ()
172 self.caps = ()
173 self.ui.debug(_('capabilities: %s\n') %
173 self.ui.debug(_('capabilities: %s\n') %
174 (' '.join(self.caps or ['none'])))
174 (' '.join(self.caps or ['none'])))
175 return self.caps
175 return self.caps
176
176
177 capabilities = property(get_caps)
177 capabilities = property(get_caps)
178
178
179 def dev(self):
179 def dev(self):
180 return -1
180 return -1
181
181
182 def lock(self):
182 def lock(self):
183 raise util.Abort(_('operation not supported over http'))
183 raise util.Abort(_('operation not supported over http'))
184
184
185 def do_cmd(self, cmd, **args):
185 def do_cmd(self, cmd, **args):
186 data = args.pop('data', None)
186 data = args.pop('data', None)
187 headers = args.pop('headers', {})
187 headers = args.pop('headers', {})
188 self.ui.debug(_("sending %s command\n") % cmd)
188 self.ui.debug(_("sending %s command\n") % cmd)
189 q = {"cmd": cmd}
189 q = {"cmd": cmd}
190 q.update(args)
190 q.update(args)
191 qs = urllib.urlencode(q)
191 qs = urllib.urlencode(q)
192 cu = "%s?%s" % (self.url, qs)
192 cu = "%s?%s" % (self.url, qs)
193 try:
193 try:
194 resp = urllib2.urlopen(urllib2.Request(cu, data, headers))
194 resp = urllib2.urlopen(urllib2.Request(cu, data, headers))
195 except urllib2.HTTPError, inst:
196 if inst.code == 401:
197 raise util.Abort(_('authorization failed'))
198 raise
195 except httplib.HTTPException, inst:
199 except httplib.HTTPException, inst:
196 self.ui.debug(_('http error while sending %s command\n') % cmd)
200 self.ui.debug(_('http error while sending %s command\n') % cmd)
197 self.ui.print_exc()
201 self.ui.print_exc()
198 raise IOError(None, inst)
202 raise IOError(None, inst)
199 try:
203 try:
200 proto = resp.getheader('content-type')
204 proto = resp.getheader('content-type')
201 except AttributeError:
205 except AttributeError:
202 proto = resp.headers['content-type']
206 proto = resp.headers['content-type']
203
207
204 # accept old "text/plain" and "application/hg-changegroup" for now
208 # accept old "text/plain" and "application/hg-changegroup" for now
205 if not proto.startswith('application/mercurial') and \
209 if not proto.startswith('application/mercurial') and \
206 not proto.startswith('text/plain') and \
210 not proto.startswith('text/plain') and \
207 not proto.startswith('application/hg-changegroup'):
211 not proto.startswith('application/hg-changegroup'):
208 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
212 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
209 self.url)
213 self.url)
210
214
211 if proto.startswith('application/mercurial'):
215 if proto.startswith('application/mercurial'):
212 version = proto[22:]
216 version = proto[22:]
213 if float(version) > 0.1:
217 if float(version) > 0.1:
214 raise hg.RepoError(_("'%s' uses newer protocol %s") %
218 raise hg.RepoError(_("'%s' uses newer protocol %s") %
215 (self.url, version))
219 (self.url, version))
216
220
217 return resp
221 return resp
218
222
219 def do_read(self, cmd, **args):
223 def do_read(self, cmd, **args):
220 fp = self.do_cmd(cmd, **args)
224 fp = self.do_cmd(cmd, **args)
221 try:
225 try:
222 return fp.read()
226 return fp.read()
223 finally:
227 finally:
224 # if using keepalive, allow connection to be reused
228 # if using keepalive, allow connection to be reused
225 fp.close()
229 fp.close()
226
230
227 def heads(self):
231 def heads(self):
228 d = self.do_read("heads")
232 d = self.do_read("heads")
229 try:
233 try:
230 return map(bin, d[:-1].split(" "))
234 return map(bin, d[:-1].split(" "))
231 except:
235 except:
232 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
236 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
233 raise
237 raise
234
238
235 def branches(self, nodes):
239 def branches(self, nodes):
236 n = " ".join(map(hex, nodes))
240 n = " ".join(map(hex, nodes))
237 d = self.do_read("branches", nodes=n)
241 d = self.do_read("branches", nodes=n)
238 try:
242 try:
239 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
243 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
240 return br
244 return br
241 except:
245 except:
242 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
246 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
243 raise
247 raise
244
248
245 def between(self, pairs):
249 def between(self, pairs):
246 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
250 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
247 d = self.do_read("between", pairs=n)
251 d = self.do_read("between", pairs=n)
248 try:
252 try:
249 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
253 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
250 return p
254 return p
251 except:
255 except:
252 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
256 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
253 raise
257 raise
254
258
255 def changegroup(self, nodes, kind):
259 def changegroup(self, nodes, kind):
256 n = " ".join(map(hex, nodes))
260 n = " ".join(map(hex, nodes))
257 f = self.do_cmd("changegroup", roots=n)
261 f = self.do_cmd("changegroup", roots=n)
258 bytes = 0
262 bytes = 0
259
263
260 def zgenerator(f):
264 def zgenerator(f):
261 zd = zlib.decompressobj()
265 zd = zlib.decompressobj()
262 try:
266 try:
263 for chnk in f:
267 for chnk in f:
264 yield zd.decompress(chnk)
268 yield zd.decompress(chnk)
265 except httplib.HTTPException, inst:
269 except httplib.HTTPException, inst:
266 raise IOError(None, _('connection ended unexpectedly'))
270 raise IOError(None, _('connection ended unexpectedly'))
267 yield zd.flush()
271 yield zd.flush()
268
272
269 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
273 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
270
274
271 def unbundle(self, cg, heads, source):
275 def unbundle(self, cg, heads, source):
272 # have to stream bundle to a temp file because we do not have
276 # have to stream bundle to a temp file because we do not have
273 # http 1.1 chunked transfer.
277 # http 1.1 chunked transfer.
274
278
275 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
279 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
276 fp = os.fdopen(fd, 'wb+')
280 fp = os.fdopen(fd, 'wb+')
277 try:
281 try:
278 for chunk in util.filechunkiter(cg):
282 for chunk in util.filechunkiter(cg):
279 fp.write(chunk)
283 fp.write(chunk)
280 length = fp.tell()
284 length = fp.tell()
281 rfp = self.do_cmd(
282 'unbundle', data=fp,
283 headers={'content-length': length,
284 'content-type': 'application/octet-stream'},
285 heads=' '.join(map(hex, heads)))
286 try:
285 try:
287 ret = int(rfp.readline())
286 rfp = self.do_cmd(
288 self.ui.write(rfp.read())
287 'unbundle', data=fp,
289 return ret
288 headers={'content-length': length,
290 finally:
289 'content-type': 'application/octet-stream'},
291 rfp.close()
290 heads=' '.join(map(hex, heads)))
291 try:
292 ret = int(rfp.readline())
293 self.ui.write(rfp.read())
294 return ret
295 finally:
296 rfp.close()
297 except socket.error, err:
298 if err[0] in (errno.ECONNRESET, errno.EPIPE):
299 raise util.Abort(_('push failed: %s'), err[1])
300 raise util.Abort(err[1])
292 finally:
301 finally:
293 fp.close()
302 fp.close()
294 os.unlink(tempname)
303 os.unlink(tempname)
295
304
296 class httpsrepository(httprepository):
305 class httpsrepository(httprepository):
297 pass
306 pass
General Comments 0
You need to be logged in to leave comments. Login now