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