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