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