##// END OF EJS Templates
httprepo: send Content-Type instead of content-type
Sune Foldager -
r10526:72d3a02c stable
parent child Browse files
Show More
@@ -1,274 +1,274 b''
1 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from node import bin, hex, nullid
10 10 from i18n import _
11 11 import repo, changegroup, statichttprepo, error, url, util
12 12 import os, urllib, urllib2, urlparse, zlib, httplib
13 13 import errno, socket
14 14 import encoding
15 15
16 16 def zgenerator(f):
17 17 zd = zlib.decompressobj()
18 18 try:
19 19 for chunk in util.filechunkiter(f):
20 20 yield zd.decompress(chunk)
21 21 except httplib.HTTPException:
22 22 raise IOError(None, _('connection ended unexpectedly'))
23 23 yield zd.flush()
24 24
25 25 class httprepository(repo.repository):
26 26 def __init__(self, ui, path):
27 27 self.path = path
28 28 self.caps = None
29 29 self.handler = None
30 30 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
31 31 if query or frag:
32 32 raise util.Abort(_('unsupported URL component: "%s"') %
33 33 (query or frag))
34 34
35 35 # urllib cannot handle URLs with embedded user or passwd
36 36 self._url, authinfo = url.getauthinfo(path)
37 37
38 38 self.ui = ui
39 39 self.ui.debug('using %s\n' % self._url)
40 40
41 41 self.urlopener = url.opener(ui, authinfo)
42 42
43 43 def __del__(self):
44 44 for h in self.urlopener.handlers:
45 45 h.close()
46 46 if hasattr(h, "close_all"):
47 47 h.close_all()
48 48
49 49 def url(self):
50 50 return self.path
51 51
52 52 # look up capabilities only when needed
53 53
54 54 def get_caps(self):
55 55 if self.caps is None:
56 56 try:
57 57 self.caps = set(self.do_read('capabilities').split())
58 58 except error.RepoError:
59 59 self.caps = set()
60 60 self.ui.debug('capabilities: %s\n' %
61 61 (' '.join(self.caps or ['none'])))
62 62 return self.caps
63 63
64 64 capabilities = property(get_caps)
65 65
66 66 def lock(self):
67 67 raise util.Abort(_('operation not supported over http'))
68 68
69 69 def do_cmd(self, cmd, **args):
70 70 data = args.pop('data', None)
71 71 headers = args.pop('headers', {})
72 72 self.ui.debug("sending %s command\n" % cmd)
73 73 q = {"cmd": cmd}
74 74 q.update(args)
75 75 qs = '?%s' % urllib.urlencode(q)
76 76 cu = "%s%s" % (self._url, qs)
77 77 req = urllib2.Request(cu, data, headers)
78 78 if data is not None:
79 79 # len(data) is broken if data doesn't fit into Py_ssize_t
80 80 # add the header ourself to avoid OverflowError
81 81 size = data.__len__()
82 82 self.ui.debug("sending %s bytes\n" % size)
83 83 req.add_unredirected_header('Content-Length', '%d' % size)
84 84 try:
85 85 resp = self.urlopener.open(req)
86 86 except urllib2.HTTPError, inst:
87 87 if inst.code == 401:
88 88 raise util.Abort(_('authorization failed'))
89 89 raise
90 90 except httplib.HTTPException, inst:
91 91 self.ui.debug('http error while sending %s command\n' % cmd)
92 92 self.ui.traceback()
93 93 raise IOError(None, inst)
94 94 except IndexError:
95 95 # this only happens with Python 2.3, later versions raise URLError
96 96 raise util.Abort(_('http error, possibly caused by proxy setting'))
97 97 # record the url we got redirected to
98 98 resp_url = resp.geturl()
99 99 if resp_url.endswith(qs):
100 100 resp_url = resp_url[:-len(qs)]
101 101 if self._url.rstrip('/') != resp_url.rstrip('/'):
102 102 self.ui.status(_('real URL is %s\n') % resp_url)
103 103 self._url = resp_url
104 104 try:
105 105 proto = resp.getheader('content-type')
106 106 except AttributeError:
107 107 proto = resp.headers['content-type']
108 108
109 109 safeurl = url.hidepassword(self._url)
110 110 # accept old "text/plain" and "application/hg-changegroup" for now
111 111 if not (proto.startswith('application/mercurial-') or
112 112 proto.startswith('text/plain') or
113 113 proto.startswith('application/hg-changegroup')):
114 114 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
115 115 raise error.RepoError(
116 116 _("'%s' does not appear to be an hg repository:\n"
117 117 "---%%<--- (%s)\n%s\n---%%<---\n")
118 118 % (safeurl, proto, resp.read()))
119 119
120 120 if proto.startswith('application/mercurial-'):
121 121 try:
122 122 version = proto.split('-', 1)[1]
123 123 version_info = tuple([int(n) for n in version.split('.')])
124 124 except ValueError:
125 125 raise error.RepoError(_("'%s' sent a broken Content-Type "
126 126 "header (%s)") % (safeurl, proto))
127 127 if version_info > (0, 1):
128 128 raise error.RepoError(_("'%s' uses newer protocol %s") %
129 129 (safeurl, version))
130 130
131 131 return resp
132 132
133 133 def do_read(self, cmd, **args):
134 134 fp = self.do_cmd(cmd, **args)
135 135 try:
136 136 return fp.read()
137 137 finally:
138 138 # if using keepalive, allow connection to be reused
139 139 fp.close()
140 140
141 141 def lookup(self, key):
142 142 self.requirecap('lookup', _('look up remote revision'))
143 143 d = self.do_cmd("lookup", key = key).read()
144 144 success, data = d[:-1].split(' ', 1)
145 145 if int(success):
146 146 return bin(data)
147 147 raise error.RepoError(data)
148 148
149 149 def heads(self):
150 150 d = self.do_read("heads")
151 151 try:
152 152 return map(bin, d[:-1].split(" "))
153 153 except:
154 154 raise error.ResponseError(_("unexpected response:"), d)
155 155
156 156 def branchmap(self):
157 157 d = self.do_read("branchmap")
158 158 try:
159 159 branchmap = {}
160 160 for branchpart in d.splitlines():
161 161 branchheads = branchpart.split(' ')
162 162 branchname = urllib.unquote(branchheads[0])
163 163 # Earlier servers (1.3.x) send branch names in (their) local
164 164 # charset. The best we can do is assume it's identical to our
165 165 # own local charset, in case it's not utf-8.
166 166 try:
167 167 branchname.decode('utf-8')
168 168 except UnicodeDecodeError:
169 169 branchname = encoding.fromlocal(branchname)
170 170 branchheads = [bin(x) for x in branchheads[1:]]
171 171 branchmap[branchname] = branchheads
172 172 return branchmap
173 173 except:
174 174 raise error.ResponseError(_("unexpected response:"), d)
175 175
176 176 def branches(self, nodes):
177 177 n = " ".join(map(hex, nodes))
178 178 d = self.do_read("branches", nodes=n)
179 179 try:
180 180 br = [tuple(map(bin, b.split(" "))) for b in d.splitlines()]
181 181 return br
182 182 except:
183 183 raise error.ResponseError(_("unexpected response:"), d)
184 184
185 185 def between(self, pairs):
186 186 batch = 8 # avoid giant requests
187 187 r = []
188 188 for i in xrange(0, len(pairs), batch):
189 189 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
190 190 d = self.do_read("between", pairs=n)
191 191 try:
192 192 r += [l and map(bin, l.split(" ")) or []
193 193 for l in d.splitlines()]
194 194 except:
195 195 raise error.ResponseError(_("unexpected response:"), d)
196 196 return r
197 197
198 198 def changegroup(self, nodes, kind):
199 199 n = " ".join(map(hex, nodes))
200 200 f = self.do_cmd("changegroup", roots=n)
201 201 return util.chunkbuffer(zgenerator(f))
202 202
203 203 def changegroupsubset(self, bases, heads, source):
204 204 self.requirecap('changegroupsubset', _('look up remote changes'))
205 205 baselst = " ".join([hex(n) for n in bases])
206 206 headlst = " ".join([hex(n) for n in heads])
207 207 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
208 208 return util.chunkbuffer(zgenerator(f))
209 209
210 210 def unbundle(self, cg, heads, source):
211 211 # have to stream bundle to a temp file because we do not have
212 212 # http 1.1 chunked transfer.
213 213
214 214 type = ""
215 215 types = self.capable('unbundle')
216 216 # servers older than d1b16a746db6 will send 'unbundle' as a
217 217 # boolean capability
218 218 try:
219 219 types = types.split(',')
220 220 except AttributeError:
221 221 types = [""]
222 222 if types:
223 223 for x in types:
224 224 if x in changegroup.bundletypes:
225 225 type = x
226 226 break
227 227
228 228 tempname = changegroup.writebundle(cg, None, type)
229 229 fp = url.httpsendfile(tempname, "rb")
230 230 try:
231 231 try:
232 232 resp = self.do_read(
233 233 'unbundle', data=fp,
234 headers={'content-type': 'application/mercurial-0.1'},
234 headers={'Content-Type': 'application/mercurial-0.1'},
235 235 heads=' '.join(map(hex, heads)))
236 236 resp_code, output = resp.split('\n', 1)
237 237 try:
238 238 ret = int(resp_code)
239 239 except ValueError, err:
240 240 raise error.ResponseError(
241 241 _('push failed (unexpected response):'), resp)
242 242 self.ui.write(output)
243 243 return ret
244 244 except socket.error, err:
245 245 if err[0] in (errno.ECONNRESET, errno.EPIPE):
246 246 raise util.Abort(_('push failed: %s') % err[1])
247 247 raise util.Abort(err[1])
248 248 finally:
249 249 fp.close()
250 250 os.unlink(tempname)
251 251
252 252 def stream_out(self):
253 253 return self.do_cmd('stream_out')
254 254
255 255 class httpsrepository(httprepository):
256 256 def __init__(self, ui, path):
257 257 if not url.has_https:
258 258 raise util.Abort(_('Python support for SSL and HTTPS '
259 259 'is not installed'))
260 260 httprepository.__init__(self, ui, path)
261 261
262 262 def instance(ui, path, create):
263 263 if create:
264 264 raise util.Abort(_('cannot create new http repository'))
265 265 try:
266 266 if path.startswith('https:'):
267 267 inst = httpsrepository(ui, path)
268 268 else:
269 269 inst = httprepository(ui, path)
270 270 inst.between([(nullid, nullid)])
271 271 return inst
272 272 except error.RepoError:
273 273 ui.note('(falling back to static-http)\n')
274 274 return statichttprepo.instance(ui, "static-" + path, create)
General Comments 0
You need to be logged in to leave comments. Login now