##// 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 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from remoterepo import *
10 10 from i18n import gettext as _
11 11 from demandload import *
12 12 demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
13 13 demandload(globals(), "keepalive")
14 14
15 class passwordmgr(urllib2.HTTPPasswordMgr):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgr.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 18 self.ui = ui
19 19
20 20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgr.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 22 self, realm, authuri)
23 23 if authinfo != (None, None):
24 24 return authinfo
25 25
26 26 if not self.ui.interactive:
27 27 raise util.Abort(_('http authorization required'))
28 28
29 29 self.ui.write(_("http authorization required\n"))
30 30 self.ui.status(_("realm: %s\n") % realm)
31 31 user = self.ui.prompt(_("user:"), default=None)
32 32 passwd = self.ui.getpass()
33 33
34 34 self.add_password(realm, authuri, user, passwd)
35 35 return (user, passwd)
36 36
37 37 def netlocsplit(netloc):
38 38 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39 39
40 40 a = netloc.find('@')
41 41 if a == -1:
42 42 user, passwd = None, None
43 43 else:
44 44 userpass, netloc = netloc[:a], netloc[a+1:]
45 45 c = userpass.find(':')
46 46 if c == -1:
47 47 user, passwd = urllib.unquote(userpass), None
48 48 else:
49 49 user = urllib.unquote(userpass[:c])
50 50 passwd = urllib.unquote(userpass[c+1:])
51 51 c = netloc.find(':')
52 52 if c == -1:
53 53 host, port = netloc, None
54 54 else:
55 55 host, port = netloc[:c], netloc[c+1:]
56 56 return host, port, user, passwd
57 57
58 58 def netlocunsplit(host, port, user=None, passwd=None):
59 59 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 60 if port:
61 61 hostport = host + ':' + port
62 62 else:
63 63 hostport = host
64 64 if user:
65 65 if passwd:
66 66 userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
67 67 else:
68 68 userpass = urllib.quote(user)
69 69 return userpass + '@' + hostport
70 70 return hostport
71 71
72 72 class httprepository(remoterepository):
73 73 def __init__(self, ui, path):
74 74 self.caps = None
75 75 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
76 76 if query or frag:
77 77 raise util.Abort(_('unsupported URL component: "%s"') %
78 78 (query or frag))
79 79 if not urlpath: urlpath = '/'
80 80 host, port, user, passwd = netlocsplit(netloc)
81 81
82 82 # urllib cannot handle URLs with embedded user or passwd
83 83 self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
84 84 urlpath, '', ''))
85 85 self.ui = ui
86 86
87 87 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
88 88 proxyauthinfo = None
89 89 handler = keepalive.HTTPHandler()
90 90
91 91 if proxyurl:
92 92 # proxy can be proper url or host[:port]
93 93 if not (proxyurl.startswith('http:') or
94 94 proxyurl.startswith('https:')):
95 95 proxyurl = 'http://' + proxyurl + '/'
96 96 snpqf = urlparse.urlsplit(proxyurl)
97 97 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
98 98 hpup = netlocsplit(proxynetloc)
99 99
100 100 proxyhost, proxyport, proxyuser, proxypasswd = hpup
101 101 if not proxyuser:
102 102 proxyuser = ui.config("http_proxy", "user")
103 103 proxypasswd = ui.config("http_proxy", "passwd")
104 104
105 105 # see if we should use a proxy for this url
106 106 no_list = [ "localhost", "127.0.0.1" ]
107 107 no_list.extend([p.strip().lower() for
108 108 p in ui.config("http_proxy", "no", '').split(',')
109 109 if p.strip()])
110 110 no_list.extend([p.strip().lower() for
111 111 p in os.getenv("no_proxy", '').split(',')
112 112 if p.strip()])
113 113 # "http_proxy.always" config is for running tests on localhost
114 114 if (not ui.configbool("http_proxy", "always") and
115 115 host.lower() in no_list):
116 116 ui.debug(_('disabling proxy for %s\n') % host)
117 117 else:
118 118 proxyurl = urlparse.urlunsplit((
119 119 proxyscheme, netlocunsplit(proxyhost, proxyport,
120 120 proxyuser, proxypasswd or ''),
121 121 proxypath, proxyquery, proxyfrag))
122 122 handler = urllib2.ProxyHandler({scheme: proxyurl})
123 123 ui.debug(_('proxying through %s\n') % proxyurl)
124 124
125 125 # urllib2 takes proxy values from the environment and those
126 126 # will take precedence if found, so drop them
127 127 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
128 128 try:
129 129 if os.environ.has_key(env):
130 130 del os.environ[env]
131 131 except OSError:
132 132 pass
133 133
134 134 passmgr = passwordmgr(ui)
135 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 138 passmgr.add_password(None, host, user, passwd or '')
138 139
139 140 opener = urllib2.build_opener(
140 141 handler,
141 142 urllib2.HTTPBasicAuthHandler(passmgr),
142 143 urllib2.HTTPDigestAuthHandler(passmgr))
143 144
144 145 # 1.0 here is the _protocol_ version
145 146 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
146 147 urllib2.install_opener(opener)
147 148
148 149 # look up capabilities only when needed
149 150
150 151 def get_caps(self):
151 152 if self.caps is None:
152 153 try:
153 154 self.caps = self.do_read('capabilities').split()
154 155 except hg.RepoError:
155 156 self.caps = ()
156 157 return self.caps
157 158
158 159 capabilities = property(get_caps)
159 160
160 161 def dev(self):
161 162 return -1
162 163
163 164 def lock(self):
164 165 raise util.Abort(_('operation not supported over http'))
165 166
166 167 def do_cmd(self, cmd, **args):
167 168 self.ui.debug(_("sending %s command\n") % cmd)
168 169 q = {"cmd": cmd}
169 170 q.update(args)
170 171 qs = urllib.urlencode(q)
171 172 cu = "%s?%s" % (self.url, qs)
172 173 try:
173 174 resp = urllib2.urlopen(cu)
174 175 except httplib.HTTPException, inst:
175 176 self.ui.debug(_('http error while sending %s command\n') % cmd)
176 177 self.ui.print_exc()
177 178 raise IOError(None, inst)
178 179 try:
179 180 proto = resp.getheader('content-type')
180 181 except AttributeError:
181 182 proto = resp.headers['content-type']
182 183
183 184 # accept old "text/plain" and "application/hg-changegroup" for now
184 185 if not proto.startswith('application/mercurial') and \
185 186 not proto.startswith('text/plain') and \
186 187 not proto.startswith('application/hg-changegroup'):
187 188 raise hg.RepoError(_("'%s' does not appear to be an hg repository") %
188 189 self.url)
189 190
190 191 if proto.startswith('application/mercurial'):
191 192 version = proto[22:]
192 193 if float(version) > 0.1:
193 194 raise hg.RepoError(_("'%s' uses newer protocol %s") %
194 195 (self.url, version))
195 196
196 197 return resp
197 198
198 199 def do_read(self, cmd, **args):
199 200 fp = self.do_cmd(cmd, **args)
200 201 try:
201 202 return fp.read()
202 203 finally:
203 204 # if using keepalive, allow connection to be reused
204 205 fp.close()
205 206
206 207 def heads(self):
207 208 d = self.do_read("heads")
208 209 try:
209 210 return map(bin, d[:-1].split(" "))
210 211 except:
211 212 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
212 213 raise
213 214
214 215 def branches(self, nodes):
215 216 n = " ".join(map(hex, nodes))
216 217 d = self.do_read("branches", nodes=n)
217 218 try:
218 219 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
219 220 return br
220 221 except:
221 222 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
222 223 raise
223 224
224 225 def between(self, pairs):
225 226 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
226 227 d = self.do_read("between", pairs=n)
227 228 try:
228 229 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
229 230 return p
230 231 except:
231 232 self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
232 233 raise
233 234
234 235 def changegroup(self, nodes, kind):
235 236 n = " ".join(map(hex, nodes))
236 237 f = self.do_cmd("changegroup", roots=n)
237 238 bytes = 0
238 239
239 240 def zgenerator(f):
240 241 zd = zlib.decompressobj()
241 242 try:
242 243 for chnk in f:
243 244 yield zd.decompress(chnk)
244 245 except httplib.HTTPException, inst:
245 246 raise IOError(None, _('connection ended unexpectedly'))
246 247 yield zd.flush()
247 248
248 249 return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
249 250
250 251 def unbundle(self, cg, heads, source):
251 252 raise util.Abort(_('operation not supported over http'))
252 253
253 254 class httpsrepository(httprepository):
254 255 pass
General Comments 0
You need to be logged in to leave comments. Login now