##// END OF EJS Templates
httprepo: proper handling of invalid responses without content-type (issue2019)...
Mads Kiilerich -
r14503:4e958f2a stable
parent child Browse files
Show More
@@ -1,203 +1,203 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 nullid
10 10 from i18n import _
11 11 import changegroup, statichttprepo, error, url, util, wireproto
12 12 import os, urllib, urllib2, urlparse, zlib, httplib
13 13 import errno, socket
14 14
15 15 def zgenerator(f):
16 16 zd = zlib.decompressobj()
17 17 try:
18 18 for chunk in util.filechunkiter(f):
19 19 while chunk:
20 20 yield zd.decompress(chunk, 2**18)
21 21 chunk = zd.unconsumed_tail
22 22 except httplib.HTTPException:
23 23 raise IOError(None, _('connection ended unexpectedly'))
24 24 yield zd.flush()
25 25
26 26 class httprepository(wireproto.wirerepository):
27 27 def __init__(self, ui, path):
28 28 self.path = path
29 29 self.caps = None
30 30 self.handler = None
31 31 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
32 32 if query or frag:
33 33 raise util.Abort(_('unsupported URL component: "%s"') %
34 34 (query or frag))
35 35
36 36 # urllib cannot handle URLs with embedded user or passwd
37 37 self._url, authinfo = url.getauthinfo(path)
38 38
39 39 self.ui = ui
40 40 self.ui.debug('using %s\n' % self._url)
41 41
42 42 self.urlopener = url.opener(ui, authinfo)
43 43
44 44 def __del__(self):
45 45 for h in self.urlopener.handlers:
46 46 h.close()
47 47 if hasattr(h, "close_all"):
48 48 h.close_all()
49 49
50 50 def url(self):
51 51 return self.path
52 52
53 53 # look up capabilities only when needed
54 54
55 55 def get_caps(self):
56 56 if self.caps is None:
57 57 try:
58 58 self.caps = set(self._call('capabilities').split())
59 59 except error.RepoError:
60 60 self.caps = set()
61 61 self.ui.debug('capabilities: %s\n' %
62 62 (' '.join(self.caps or ['none'])))
63 63 return self.caps
64 64
65 65 capabilities = property(get_caps)
66 66
67 67 def lock(self):
68 68 raise util.Abort(_('operation not supported over http'))
69 69
70 70 def _callstream(self, cmd, **args):
71 71 if cmd == 'pushkey':
72 72 args['data'] = ''
73 73 data = args.pop('data', None)
74 74 headers = args.pop('headers', {})
75 75 self.ui.debug("sending %s command\n" % cmd)
76 76 q = {"cmd": cmd}
77 77 q.update(args)
78 78 qs = '?%s' % urllib.urlencode(q)
79 79 cu = "%s%s" % (self._url, qs)
80 80 req = urllib2.Request(cu, data, headers)
81 81 if data is not None:
82 82 # len(data) is broken if data doesn't fit into Py_ssize_t
83 83 # add the header ourself to avoid OverflowError
84 84 size = data.__len__()
85 85 self.ui.debug("sending %s bytes\n" % size)
86 86 req.add_unredirected_header('Content-Length', '%d' % size)
87 87 try:
88 88 resp = self.urlopener.open(req)
89 89 except urllib2.HTTPError, inst:
90 90 if inst.code == 401:
91 91 raise util.Abort(_('authorization failed'))
92 92 raise
93 93 except httplib.HTTPException, inst:
94 94 self.ui.debug('http error while sending %s command\n' % cmd)
95 95 self.ui.traceback()
96 96 raise IOError(None, inst)
97 97 except IndexError:
98 98 # this only happens with Python 2.3, later versions raise URLError
99 99 raise util.Abort(_('http error, possibly caused by proxy setting'))
100 100 # record the url we got redirected to
101 101 resp_url = resp.geturl()
102 102 if resp_url.endswith(qs):
103 103 resp_url = resp_url[:-len(qs)]
104 104 if self._url.rstrip('/') != resp_url.rstrip('/'):
105 105 self.ui.status(_('real URL is %s\n') % resp_url)
106 106 self._url = resp_url
107 107 try:
108 108 proto = resp.getheader('content-type')
109 109 except AttributeError:
110 proto = resp.headers['content-type']
110 proto = resp.headers.get('content-type', '')
111 111
112 112 safeurl = url.hidepassword(self._url)
113 113 # accept old "text/plain" and "application/hg-changegroup" for now
114 114 if not (proto.startswith('application/mercurial-') or
115 115 proto.startswith('text/plain') or
116 116 proto.startswith('application/hg-changegroup')):
117 117 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
118 118 raise error.RepoError(
119 119 _("'%s' does not appear to be an hg repository:\n"
120 120 "---%%<--- (%s)\n%s\n---%%<---\n")
121 % (safeurl, proto, resp.read()))
121 % (safeurl, proto or 'no content-type', resp.read()))
122 122
123 123 if proto.startswith('application/mercurial-'):
124 124 try:
125 125 version = proto.split('-', 1)[1]
126 126 version_info = tuple([int(n) for n in version.split('.')])
127 127 except ValueError:
128 128 raise error.RepoError(_("'%s' sent a broken Content-Type "
129 129 "header (%s)") % (safeurl, proto))
130 130 if version_info > (0, 1):
131 131 raise error.RepoError(_("'%s' uses newer protocol %s") %
132 132 (safeurl, version))
133 133
134 134 return resp
135 135
136 136 def _call(self, cmd, **args):
137 137 fp = self._callstream(cmd, **args)
138 138 try:
139 139 return fp.read()
140 140 finally:
141 141 # if using keepalive, allow connection to be reused
142 142 fp.close()
143 143
144 144 def _callpush(self, cmd, cg, **args):
145 145 # have to stream bundle to a temp file because we do not have
146 146 # http 1.1 chunked transfer.
147 147
148 148 type = ""
149 149 types = self.capable('unbundle')
150 150 # servers older than d1b16a746db6 will send 'unbundle' as a
151 151 # boolean capability
152 152 try:
153 153 types = types.split(',')
154 154 except AttributeError:
155 155 types = [""]
156 156 if types:
157 157 for x in types:
158 158 if x in changegroup.bundletypes:
159 159 type = x
160 160 break
161 161
162 162 tempname = changegroup.writebundle(cg, None, type)
163 163 fp = url.httpsendfile(self.ui, tempname, "rb")
164 164 headers = {'Content-Type': 'application/mercurial-0.1'}
165 165
166 166 try:
167 167 try:
168 168 r = self._call(cmd, data=fp, headers=headers, **args)
169 169 return r.split('\n', 1)
170 170 except socket.error, err:
171 171 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
172 172 raise util.Abort(_('push failed: %s') % err.args[1])
173 173 raise util.Abort(err.args[1])
174 174 finally:
175 175 fp.close()
176 176 os.unlink(tempname)
177 177
178 178 def _abort(self, exception):
179 179 raise exception
180 180
181 181 def _decompress(self, stream):
182 182 return util.chunkbuffer(zgenerator(stream))
183 183
184 184 class httpsrepository(httprepository):
185 185 def __init__(self, ui, path):
186 186 if not url.has_https:
187 187 raise util.Abort(_('Python support for SSL and HTTPS '
188 188 'is not installed'))
189 189 httprepository.__init__(self, ui, path)
190 190
191 191 def instance(ui, path, create):
192 192 if create:
193 193 raise util.Abort(_('cannot create new http repository'))
194 194 try:
195 195 if path.startswith('https:'):
196 196 inst = httpsrepository(ui, path)
197 197 else:
198 198 inst = httprepository(ui, path)
199 199 inst.between([(nullid, nullid)])
200 200 return inst
201 201 except error.RepoError:
202 202 ui.note('(falling back to static-http)\n')
203 203 return statichttprepo.instance(ui, "static-" + path, create)
General Comments 0
You need to be logged in to leave comments. Login now