##// END OF EJS Templates
httprepo: make "http://user:pass@host/" urls work
Vadim Gelfer -
r2447:cd00531e default
parent child Browse files
Show More
@@ -1,254 +1,255 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")
13 demandload(globals(), "keepalive")
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgr):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgr.__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.HTTPPasswordMgr.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 httprepository(remoterepository):
72 class httprepository(remoterepository):
73 def __init__(self, ui, path):
73 def __init__(self, ui, path):
74 self.caps = None
74 self.caps = None
75 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
75 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
76 if query or frag:
76 if query or frag:
77 raise util.Abort(_('unsupported URL component: "%s"') %
77 raise util.Abort(_('unsupported URL component: "%s"') %
78 (query or frag))
78 (query or frag))
79 if not urlpath: urlpath = '/'
79 if not urlpath: urlpath = '/'
80 host, port, user, passwd = netlocsplit(netloc)
80 host, port, user, passwd = netlocsplit(netloc)
81
81
82 # urllib cannot handle URLs with embedded user or passwd
82 # urllib cannot handle URLs with embedded user or passwd
83 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
83 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
84 urlpath, '', ''))
84 urlpath, '', ''))
85 self.ui = ui
85 self.ui = ui
86
86
87 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
87 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
88 proxyauthinfo = None
88 proxyauthinfo = None
89 handler = keepalive.HTTPHandler()
89 handler = keepalive.HTTPHandler()
90
90
91 if proxyurl:
91 if proxyurl:
92 # proxy can be proper url or host[:port]
92 # proxy can be proper url or host[:port]
93 if not (proxyurl.startswith('http:') or
93 if not (proxyurl.startswith('http:') or
94 proxyurl.startswith('https:')):
94 proxyurl.startswith('https:')):
95 proxyurl = 'http://' + proxyurl + '/'
95 proxyurl = 'http://' + proxyurl + '/'
96 snpqf = urlparse.urlsplit(proxyurl)
96 snpqf = urlparse.urlsplit(proxyurl)
97 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
97 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
98 hpup = netlocsplit(proxynetloc)
98 hpup = netlocsplit(proxynetloc)
99
99
100 proxyhost, proxyport, proxyuser, proxypasswd = hpup
100 proxyhost, proxyport, proxyuser, proxypasswd = hpup
101 if not proxyuser:
101 if not proxyuser:
102 proxyuser = ui.config("http_proxy", "user")
102 proxyuser = ui.config("http_proxy", "user")
103 proxypasswd = ui.config("http_proxy", "passwd")
103 proxypasswd = ui.config("http_proxy", "passwd")
104
104
105 # see if we should use a proxy for this url
105 # see if we should use a proxy for this url
106 no_list = [ "localhost", "127.0.0.1" ]
106 no_list = [ "localhost", "127.0.0.1" ]
107 no_list.extend([p.strip().lower() for
107 no_list.extend([p.strip().lower() for
108 p in ui.config("http_proxy", "no", '').split(',')
108 p in ui.config("http_proxy", "no", '').split(',')
109 if p.strip()])
109 if p.strip()])
110 no_list.extend([p.strip().lower() for
110 no_list.extend([p.strip().lower() for
111 p in os.getenv("no_proxy", '').split(',')
111 p in os.getenv("no_proxy", '').split(',')
112 if p.strip()])
112 if p.strip()])
113 # "http_proxy.always" config is for running tests on localhost
113 # "http_proxy.always" config is for running tests on localhost
114 if (not ui.configbool("http_proxy", "always") and
114 if (not ui.configbool("http_proxy", "always") and
115 host.lower() in no_list):
115 host.lower() in no_list):
116 ui.debug(_('disabling proxy for %s\n') % host)
116 ui.debug(_('disabling proxy for %s\n') % host)
117 else:
117 else:
118 proxyurl = urlparse.urlunsplit((
118 proxyurl = urlparse.urlunsplit((
119 proxyscheme, netlocunsplit(proxyhost, proxyport,
119 proxyscheme, netlocunsplit(proxyhost, proxyport,
120 proxyuser, proxypasswd or ''),
120 proxyuser, proxypasswd or ''),
121 proxypath, proxyquery, proxyfrag))
121 proxypath, proxyquery, proxyfrag))
122 handler = urllib2.ProxyHandler({scheme: proxyurl})
122 handler = urllib2.ProxyHandler({scheme: proxyurl})
123 ui.debug(_('proxying through %s\n') % proxyurl)
123 ui.debug(_('proxying through %s\n') % proxyurl)
124
124
125 # urllib2 takes proxy values from the environment and those
125 # urllib2 takes proxy values from the environment and those
126 # will take precedence if found, so drop them
126 # will take precedence if found, so drop them
127 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
127 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
128 try:
128 try:
129 if os.environ.has_key(env):
129 if os.environ.has_key(env):
130 del os.environ[env]
130 del os.environ[env]
131 except OSError:
131 except OSError:
132 pass
132 pass
133
133
134 passmgr = passwordmgr(ui)
134 passmgr = passwordmgr(ui)
135 if user:
135 if user:
136 ui.debug(_('will use user %s for http auth\n') % user)
136 ui.debug(_('will use user %s, password %s for http auth\n') %
137 (user, '*' * len(passwd)))
137 passmgr.add_password(None, host, user, passwd or '')
138 passmgr.add_password(None, host, user, passwd or '')
138
139
139 opener = urllib2.build_opener(
140 opener = urllib2.build_opener(
140 handler,
141 handler,
141 urllib2.HTTPBasicAuthHandler(passmgr),
142 urllib2.HTTPBasicAuthHandler(passmgr),
142 urllib2.HTTPDigestAuthHandler(passmgr))
143 urllib2.HTTPDigestAuthHandler(passmgr))
143
144
144 # 1.0 here is the _protocol_ version
145 # 1.0 here is the _protocol_ version
145 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
146 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
146 urllib2.install_opener(opener)
147 urllib2.install_opener(opener)
147
148
148 # look up capabilities only when needed
149 # look up capabilities only when needed
149
150
150 def get_caps(self):
151 def get_caps(self):
151 if self.caps is None:
152 if self.caps is None:
152 try:
153 try:
153 self.caps = self.do_read('capabilities').split()
154 self.caps = self.do_read('capabilities').split()
154 except hg.RepoError:
155 except hg.RepoError:
155 self.caps = ()
156 self.caps = ()
156 return self.caps
157 return self.caps
157
158
158 capabilities = property(get_caps)
159 capabilities = property(get_caps)
159
160
160 def dev(self):
161 def dev(self):
161 return -1
162 return -1
162
163
163 def lock(self):
164 def lock(self):
164 raise util.Abort(_('operation not supported over http'))
165 raise util.Abort(_('operation not supported over http'))
165
166
166 def do_cmd(self, cmd, **args):
167 def do_cmd(self, cmd, **args):
167 self.ui.debug(_("sending %s command\n") % cmd)
168 self.ui.debug(_("sending %s command\n") % cmd)
168 q = {"cmd": cmd}
169 q = {"cmd": cmd}
169 q.update(args)
170 q.update(args)
170 qs = urllib.urlencode(q)
171 qs = urllib.urlencode(q)
171 cu = "%s?%s" % (self.url, qs)
172 cu = "%s?%s" % (self.url, qs)
172 try:
173 try:
173 resp = urllib2.urlopen(cu)
174 resp = urllib2.urlopen(cu)
174 except httplib.HTTPException, inst:
175 except httplib.HTTPException, inst:
175 self.ui.debug(_('http error while sending %s command\n') % cmd)
176 self.ui.debug(_('http error while sending %s command\n') % cmd)
176 self.ui.print_exc()
177 self.ui.print_exc()
177 raise IOError(None, inst)
178 raise IOError(None, inst)
178 try:
179 try:
179 proto = resp.getheader('content-type')
180 proto = resp.getheader('content-type')
180 except AttributeError:
181 except AttributeError:
181 proto = resp.headers['content-type']
182 proto = resp.headers['content-type']
182
183
183 # accept old "text/plain" and "application/hg-changegroup" for now
184 # accept old "text/plain" and "application/hg-changegroup" for now
184 if not proto.startswith('application/mercurial') and \
185 if not proto.startswith('application/mercurial') and \
185 not proto.startswith('text/plain') and \
186 not proto.startswith('text/plain') and \
186 not proto.startswith('application/hg-changegroup'):
187 not proto.startswith('application/hg-changegroup'):
187 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
188 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
188 self.url)
189 self.url)
189
190
190 if proto.startswith('application/mercurial'):
191 if proto.startswith('application/mercurial'):
191 version = proto[22:]
192 version = proto[22:]
192 if float(version) > 0.1:
193 if float(version) > 0.1:
193 raise hg.RepoError(_("'%s' uses newer protocol %s") %
194 raise hg.RepoError(_("'%s' uses newer protocol %s") %
194 (self.url, version))
195 (self.url, version))
195
196
196 return resp
197 return resp
197
198
198 def do_read(self, cmd, **args):
199 def do_read(self, cmd, **args):
199 fp = self.do_cmd(cmd, **args)
200 fp = self.do_cmd(cmd, **args)
200 try:
201 try:
201 return fp.read()
202 return fp.read()
202 finally:
203 finally:
203 # if using keepalive, allow connection to be reused
204 # if using keepalive, allow connection to be reused
204 fp.close()
205 fp.close()
205
206
206 def heads(self):
207 def heads(self):
207 d = self.do_read("heads")
208 d = self.do_read("heads")
208 try:
209 try:
209 return map(bin, d[:-1].split(" "))
210 return map(bin, d[:-1].split(" "))
210 except:
211 except:
211 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
212 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
212 raise
213 raise
213
214
214 def branches(self, nodes):
215 def branches(self, nodes):
215 n = " ".join(map(hex, nodes))
216 n = " ".join(map(hex, nodes))
216 d = self.do_read("branches", nodes=n)
217 d = self.do_read("branches", nodes=n)
217 try:
218 try:
218 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
219 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
219 return br
220 return br
220 except:
221 except:
221 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
222 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
222 raise
223 raise
223
224
224 def between(self, pairs):
225 def between(self, pairs):
225 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
226 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
226 d = self.do_read("between", pairs=n)
227 d = self.do_read("between", pairs=n)
227 try:
228 try:
228 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
229 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
229 return p
230 return p
230 except:
231 except:
231 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
232 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
232 raise
233 raise
233
234
234 def changegroup(self, nodes, kind):
235 def changegroup(self, nodes, kind):
235 n = " ".join(map(hex, nodes))
236 n = " ".join(map(hex, nodes))
236 f = self.do_cmd("changegroup", roots=n)
237 f = self.do_cmd("changegroup", roots=n)
237 bytes = 0
238 bytes = 0
238
239
239 def zgenerator(f):
240 def zgenerator(f):
240 zd = zlib.decompressobj()
241 zd = zlib.decompressobj()
241 try:
242 try:
242 for chnk in f:
243 for chnk in f:
243 yield zd.decompress(chnk)
244 yield zd.decompress(chnk)
244 except httplib.HTTPException, inst:
245 except httplib.HTTPException, inst:
245 raise IOError(None, _('connection ended unexpectedly'))
246 raise IOError(None, _('connection ended unexpectedly'))
246 yield zd.flush()
247 yield zd.flush()
247
248
248 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
249 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
249
250
250 def unbundle(self, cg, heads, source):
251 def unbundle(self, cg, heads, source):
251 raise util.Abort(_('operation not supported over http'))
252 raise util.Abort(_('operation not supported over http'))
252
253
253 class httpsrepository(httprepository):
254 class httpsrepository(httprepository):
254 pass
255 pass
General Comments 0
You need to be logged in to leave comments. Login now