##// END OF EJS Templates
hgweb: fix decodevaluefromheaders to always return a bytes value...
Augie Fackler -
r34745:0a2ef612 default
parent child Browse files
Show More
@@ -1,207 +1,210 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cgi
10 import cgi
11 import struct
11 import struct
12
12
13 from .common import (
13 from .common import (
14 HTTP_OK,
14 HTTP_OK,
15 )
15 )
16
16
17 from .. import (
17 from .. import (
18 error,
18 error,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 wireproto,
21 wireproto,
22 )
22 )
23 stringio = util.stringio
23 stringio = util.stringio
24
24
25 urlerr = util.urlerr
25 urlerr = util.urlerr
26 urlreq = util.urlreq
26 urlreq = util.urlreq
27
27
28 HGTYPE = 'application/mercurial-0.1'
28 HGTYPE = 'application/mercurial-0.1'
29 HGTYPE2 = 'application/mercurial-0.2'
29 HGTYPE2 = 'application/mercurial-0.2'
30 HGERRTYPE = 'application/hg-error'
30 HGERRTYPE = 'application/hg-error'
31
31
32 def decodevaluefromheaders(req, headerprefix):
32 def decodevaluefromheaders(req, headerprefix):
33 """Decode a long value from multiple HTTP request headers."""
33 """Decode a long value from multiple HTTP request headers.
34
35 Returns the value as a bytes, not a str.
36 """
34 chunks = []
37 chunks = []
35 i = 1
38 i = 1
39 prefix = headerprefix.upper().replace(r'-', r'_')
36 while True:
40 while True:
37 v = req.env.get('HTTP_%s_%d' % (
41 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
38 headerprefix.upper().replace('-', '_'), i))
39 if v is None:
42 if v is None:
40 break
43 break
41 chunks.append(v)
44 chunks.append(pycompat.bytesurl(v))
42 i += 1
45 i += 1
43
46
44 return ''.join(chunks)
47 return ''.join(chunks)
45
48
46 class webproto(wireproto.abstractserverproto):
49 class webproto(wireproto.abstractserverproto):
47 def __init__(self, req, ui):
50 def __init__(self, req, ui):
48 self.req = req
51 self.req = req
49 self.response = ''
52 self.response = ''
50 self.ui = ui
53 self.ui = ui
51 self.name = 'http'
54 self.name = 'http'
52
55
53 def getargs(self, args):
56 def getargs(self, args):
54 knownargs = self._args()
57 knownargs = self._args()
55 data = {}
58 data = {}
56 keys = args.split()
59 keys = args.split()
57 for k in keys:
60 for k in keys:
58 if k == '*':
61 if k == '*':
59 star = {}
62 star = {}
60 for key in knownargs.keys():
63 for key in knownargs.keys():
61 if key != 'cmd' and key not in keys:
64 if key != 'cmd' and key not in keys:
62 star[key] = knownargs[key][0]
65 star[key] = knownargs[key][0]
63 data['*'] = star
66 data['*'] = star
64 else:
67 else:
65 data[k] = knownargs[k][0]
68 data[k] = knownargs[k][0]
66 return [data[k] for k in keys]
69 return [data[k] for k in keys]
67 def _args(self):
70 def _args(self):
68 args = self.req.form.copy()
71 args = self.req.form.copy()
69 if pycompat.ispy3:
72 if pycompat.ispy3:
70 args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
73 args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
71 for k, vs in args.items()}
74 for k, vs in args.items()}
72 postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
75 postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
73 if postlen:
76 if postlen:
74 args.update(cgi.parse_qs(
77 args.update(cgi.parse_qs(
75 self.req.read(postlen), keep_blank_values=True))
78 self.req.read(postlen), keep_blank_values=True))
76 return args
79 return args
77
80
78 argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
81 argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
79 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
82 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
80 return args
83 return args
81 def getfile(self, fp):
84 def getfile(self, fp):
82 length = int(self.req.env[r'CONTENT_LENGTH'])
85 length = int(self.req.env[r'CONTENT_LENGTH'])
83 # If httppostargs is used, we need to read Content-Length
86 # If httppostargs is used, we need to read Content-Length
84 # minus the amount that was consumed by args.
87 # minus the amount that was consumed by args.
85 length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
88 length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
86 for s in util.filechunkiter(self.req, limit=length):
89 for s in util.filechunkiter(self.req, limit=length):
87 fp.write(s)
90 fp.write(s)
88 def redirect(self):
91 def redirect(self):
89 self.oldio = self.ui.fout, self.ui.ferr
92 self.oldio = self.ui.fout, self.ui.ferr
90 self.ui.ferr = self.ui.fout = stringio()
93 self.ui.ferr = self.ui.fout = stringio()
91 def restore(self):
94 def restore(self):
92 val = self.ui.fout.getvalue()
95 val = self.ui.fout.getvalue()
93 self.ui.ferr, self.ui.fout = self.oldio
96 self.ui.ferr, self.ui.fout = self.oldio
94 return val
97 return val
95
98
96 def _client(self):
99 def _client(self):
97 return 'remote:%s:%s:%s' % (
100 return 'remote:%s:%s:%s' % (
98 self.req.env.get('wsgi.url_scheme') or 'http',
101 self.req.env.get('wsgi.url_scheme') or 'http',
99 urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
102 urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
100 urlreq.quote(self.req.env.get('REMOTE_USER', '')))
103 urlreq.quote(self.req.env.get('REMOTE_USER', '')))
101
104
102 def responsetype(self, v1compressible=False):
105 def responsetype(self, v1compressible=False):
103 """Determine the appropriate response type and compression settings.
106 """Determine the appropriate response type and compression settings.
104
107
105 The ``v1compressible`` argument states whether the response with
108 The ``v1compressible`` argument states whether the response with
106 application/mercurial-0.1 media types should be zlib compressed.
109 application/mercurial-0.1 media types should be zlib compressed.
107
110
108 Returns a tuple of (mediatype, compengine, engineopts).
111 Returns a tuple of (mediatype, compengine, engineopts).
109 """
112 """
110 # For now, if it isn't compressible in the old world, it's never
113 # For now, if it isn't compressible in the old world, it's never
111 # compressible. We can change this to send uncompressed 0.2 payloads
114 # compressible. We can change this to send uncompressed 0.2 payloads
112 # later.
115 # later.
113 if not v1compressible:
116 if not v1compressible:
114 return HGTYPE, None, None
117 return HGTYPE, None, None
115
118
116 # Determine the response media type and compression engine based
119 # Determine the response media type and compression engine based
117 # on the request parameters.
120 # on the request parameters.
118 protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
121 protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
119
122
120 if '0.2' in protocaps:
123 if '0.2' in protocaps:
121 # Default as defined by wire protocol spec.
124 # Default as defined by wire protocol spec.
122 compformats = ['zlib', 'none']
125 compformats = ['zlib', 'none']
123 for cap in protocaps:
126 for cap in protocaps:
124 if cap.startswith('comp='):
127 if cap.startswith('comp='):
125 compformats = cap[5:].split(',')
128 compformats = cap[5:].split(',')
126 break
129 break
127
130
128 # Now find an agreed upon compression format.
131 # Now find an agreed upon compression format.
129 for engine in wireproto.supportedcompengines(self.ui, self,
132 for engine in wireproto.supportedcompengines(self.ui, self,
130 util.SERVERROLE):
133 util.SERVERROLE):
131 if engine.wireprotosupport().name in compformats:
134 if engine.wireprotosupport().name in compformats:
132 opts = {}
135 opts = {}
133 level = self.ui.configint('server',
136 level = self.ui.configint('server',
134 '%slevel' % engine.name())
137 '%slevel' % engine.name())
135 if level is not None:
138 if level is not None:
136 opts['level'] = level
139 opts['level'] = level
137
140
138 return HGTYPE2, engine, opts
141 return HGTYPE2, engine, opts
139
142
140 # No mutually supported compression format. Fall back to the
143 # No mutually supported compression format. Fall back to the
141 # legacy protocol.
144 # legacy protocol.
142
145
143 # Don't allow untrusted settings because disabling compression or
146 # Don't allow untrusted settings because disabling compression or
144 # setting a very high compression level could lead to flooding
147 # setting a very high compression level could lead to flooding
145 # the server's network or CPU.
148 # the server's network or CPU.
146 opts = {'level': self.ui.configint('server', 'zliblevel')}
149 opts = {'level': self.ui.configint('server', 'zliblevel')}
147 return HGTYPE, util.compengines['zlib'], opts
150 return HGTYPE, util.compengines['zlib'], opts
148
151
149 def iscmd(cmd):
152 def iscmd(cmd):
150 return cmd in wireproto.commands
153 return cmd in wireproto.commands
151
154
152 def call(repo, req, cmd):
155 def call(repo, req, cmd):
153 p = webproto(req, repo.ui)
156 p = webproto(req, repo.ui)
154
157
155 def genversion2(gen, compress, engine, engineopts):
158 def genversion2(gen, compress, engine, engineopts):
156 # application/mercurial-0.2 always sends a payload header
159 # application/mercurial-0.2 always sends a payload header
157 # identifying the compression engine.
160 # identifying the compression engine.
158 name = engine.wireprotosupport().name
161 name = engine.wireprotosupport().name
159 assert 0 < len(name) < 256
162 assert 0 < len(name) < 256
160 yield struct.pack('B', len(name))
163 yield struct.pack('B', len(name))
161 yield name
164 yield name
162
165
163 if compress:
166 if compress:
164 for chunk in engine.compressstream(gen, opts=engineopts):
167 for chunk in engine.compressstream(gen, opts=engineopts):
165 yield chunk
168 yield chunk
166 else:
169 else:
167 for chunk in gen:
170 for chunk in gen:
168 yield chunk
171 yield chunk
169
172
170 rsp = wireproto.dispatch(repo, p, cmd)
173 rsp = wireproto.dispatch(repo, p, cmd)
171 if isinstance(rsp, bytes):
174 if isinstance(rsp, bytes):
172 req.respond(HTTP_OK, HGTYPE, body=rsp)
175 req.respond(HTTP_OK, HGTYPE, body=rsp)
173 return []
176 return []
174 elif isinstance(rsp, wireproto.streamres):
177 elif isinstance(rsp, wireproto.streamres):
175 if rsp.reader:
178 if rsp.reader:
176 gen = iter(lambda: rsp.reader.read(32768), '')
179 gen = iter(lambda: rsp.reader.read(32768), '')
177 else:
180 else:
178 gen = rsp.gen
181 gen = rsp.gen
179
182
180 # This code for compression should not be streamres specific. It
183 # This code for compression should not be streamres specific. It
181 # is here because we only compress streamres at the moment.
184 # is here because we only compress streamres at the moment.
182 mediatype, engine, engineopts = p.responsetype(rsp.v1compressible)
185 mediatype, engine, engineopts = p.responsetype(rsp.v1compressible)
183
186
184 if mediatype == HGTYPE and rsp.v1compressible:
187 if mediatype == HGTYPE and rsp.v1compressible:
185 gen = engine.compressstream(gen, engineopts)
188 gen = engine.compressstream(gen, engineopts)
186 elif mediatype == HGTYPE2:
189 elif mediatype == HGTYPE2:
187 gen = genversion2(gen, rsp.v1compressible, engine, engineopts)
190 gen = genversion2(gen, rsp.v1compressible, engine, engineopts)
188
191
189 req.respond(HTTP_OK, mediatype)
192 req.respond(HTTP_OK, mediatype)
190 return gen
193 return gen
191 elif isinstance(rsp, wireproto.pushres):
194 elif isinstance(rsp, wireproto.pushres):
192 val = p.restore()
195 val = p.restore()
193 rsp = '%d\n%s' % (rsp.res, val)
196 rsp = '%d\n%s' % (rsp.res, val)
194 req.respond(HTTP_OK, HGTYPE, body=rsp)
197 req.respond(HTTP_OK, HGTYPE, body=rsp)
195 return []
198 return []
196 elif isinstance(rsp, wireproto.pusherr):
199 elif isinstance(rsp, wireproto.pusherr):
197 # drain the incoming bundle
200 # drain the incoming bundle
198 req.drain()
201 req.drain()
199 p.restore()
202 p.restore()
200 rsp = '0\n%s\n' % rsp.res
203 rsp = '0\n%s\n' % rsp.res
201 req.respond(HTTP_OK, HGTYPE, body=rsp)
204 req.respond(HTTP_OK, HGTYPE, body=rsp)
202 return []
205 return []
203 elif isinstance(rsp, wireproto.ooberror):
206 elif isinstance(rsp, wireproto.ooberror):
204 rsp = rsp.message
207 rsp = rsp.message
205 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
208 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
206 return []
209 return []
207 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
210 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
General Comments 0
You need to be logged in to leave comments. Login now