##// END OF EJS Templates
wireprotoserver: make attributes private...
Gregory Szorc -
r35884:d747cf39 default
parent child Browse files
Show More
@@ -1,358 +1,358
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import cgi
9 import cgi
10 import struct
10 import struct
11 import sys
11 import sys
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 encoding,
15 encoding,
16 error,
16 error,
17 hook,
17 hook,
18 pycompat,
18 pycompat,
19 util,
19 util,
20 wireproto,
20 wireproto,
21 )
21 )
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 HTTP_OK = 200
28 HTTP_OK = 200
29
29
30 HGTYPE = 'application/mercurial-0.1'
30 HGTYPE = 'application/mercurial-0.1'
31 HGTYPE2 = 'application/mercurial-0.2'
31 HGTYPE2 = 'application/mercurial-0.2'
32 HGERRTYPE = 'application/hg-error'
32 HGERRTYPE = 'application/hg-error'
33
33
34 class abstractserverproto(object):
34 class abstractserverproto(object):
35 """abstract class that summarizes the protocol API
35 """abstract class that summarizes the protocol API
36
36
37 Used as reference and documentation.
37 Used as reference and documentation.
38 """
38 """
39
39
40 def getargs(self, args):
40 def getargs(self, args):
41 """return the value for arguments in <args>
41 """return the value for arguments in <args>
42
42
43 returns a list of values (same order as <args>)"""
43 returns a list of values (same order as <args>)"""
44 raise NotImplementedError()
44 raise NotImplementedError()
45
45
46 def getfile(self, fp):
46 def getfile(self, fp):
47 """write the whole content of a file into a file like object
47 """write the whole content of a file into a file like object
48
48
49 The file is in the form::
49 The file is in the form::
50
50
51 (<chunk-size>\n<chunk>)+0\n
51 (<chunk-size>\n<chunk>)+0\n
52
52
53 chunk size is the ascii version of the int.
53 chunk size is the ascii version of the int.
54 """
54 """
55 raise NotImplementedError()
55 raise NotImplementedError()
56
56
57 def redirect(self):
57 def redirect(self):
58 """may setup interception for stdout and stderr
58 """may setup interception for stdout and stderr
59
59
60 See also the `restore` method."""
60 See also the `restore` method."""
61 raise NotImplementedError()
61 raise NotImplementedError()
62
62
63 # If the `redirect` function does install interception, the `restore`
63 # If the `redirect` function does install interception, the `restore`
64 # function MUST be defined. If interception is not used, this function
64 # function MUST be defined. If interception is not used, this function
65 # MUST NOT be defined.
65 # MUST NOT be defined.
66 #
66 #
67 # left commented here on purpose
67 # left commented here on purpose
68 #
68 #
69 #def restore(self):
69 #def restore(self):
70 # """reinstall previous stdout and stderr and return intercepted stdout
70 # """reinstall previous stdout and stderr and return intercepted stdout
71 # """
71 # """
72 # raise NotImplementedError()
72 # raise NotImplementedError()
73
73
74 def decodevaluefromheaders(req, headerprefix):
74 def decodevaluefromheaders(req, headerprefix):
75 """Decode a long value from multiple HTTP request headers.
75 """Decode a long value from multiple HTTP request headers.
76
76
77 Returns the value as a bytes, not a str.
77 Returns the value as a bytes, not a str.
78 """
78 """
79 chunks = []
79 chunks = []
80 i = 1
80 i = 1
81 prefix = headerprefix.upper().replace(r'-', r'_')
81 prefix = headerprefix.upper().replace(r'-', r'_')
82 while True:
82 while True:
83 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
83 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
84 if v is None:
84 if v is None:
85 break
85 break
86 chunks.append(pycompat.bytesurl(v))
86 chunks.append(pycompat.bytesurl(v))
87 i += 1
87 i += 1
88
88
89 return ''.join(chunks)
89 return ''.join(chunks)
90
90
91 class webproto(abstractserverproto):
91 class webproto(abstractserverproto):
92 def __init__(self, req, ui):
92 def __init__(self, req, ui):
93 self.req = req
93 self._req = req
94 self.ui = ui
94 self._ui = ui
95 self.name = 'http'
95 self.name = 'http'
96
96
97 def getargs(self, args):
97 def getargs(self, args):
98 knownargs = self._args()
98 knownargs = self._args()
99 data = {}
99 data = {}
100 keys = args.split()
100 keys = args.split()
101 for k in keys:
101 for k in keys:
102 if k == '*':
102 if k == '*':
103 star = {}
103 star = {}
104 for key in knownargs.keys():
104 for key in knownargs.keys():
105 if key != 'cmd' and key not in keys:
105 if key != 'cmd' and key not in keys:
106 star[key] = knownargs[key][0]
106 star[key] = knownargs[key][0]
107 data['*'] = star
107 data['*'] = star
108 else:
108 else:
109 data[k] = knownargs[k][0]
109 data[k] = knownargs[k][0]
110 return [data[k] for k in keys]
110 return [data[k] for k in keys]
111
111
112 def _args(self):
112 def _args(self):
113 args = self.req.form.copy()
113 args = self._req.form.copy()
114 if pycompat.ispy3:
114 if pycompat.ispy3:
115 args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
115 args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
116 for k, vs in args.items()}
116 for k, vs in args.items()}
117 postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
117 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
118 if postlen:
118 if postlen:
119 args.update(cgi.parse_qs(
119 args.update(cgi.parse_qs(
120 self.req.read(postlen), keep_blank_values=True))
120 self._req.read(postlen), keep_blank_values=True))
121 return args
121 return args
122
122
123 argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
123 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
124 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
124 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
125 return args
125 return args
126
126
127 def getfile(self, fp):
127 def getfile(self, fp):
128 length = int(self.req.env[r'CONTENT_LENGTH'])
128 length = int(self._req.env[r'CONTENT_LENGTH'])
129 # If httppostargs is used, we need to read Content-Length
129 # If httppostargs is used, we need to read Content-Length
130 # minus the amount that was consumed by args.
130 # minus the amount that was consumed by args.
131 length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
131 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
132 for s in util.filechunkiter(self.req, limit=length):
132 for s in util.filechunkiter(self._req, limit=length):
133 fp.write(s)
133 fp.write(s)
134
134
135 def redirect(self):
135 def redirect(self):
136 self.oldio = self.ui.fout, self.ui.ferr
136 self._oldio = self._ui.fout, self._ui.ferr
137 self.ui.ferr = self.ui.fout = stringio()
137 self._ui.ferr = self._ui.fout = stringio()
138
138
139 def restore(self):
139 def restore(self):
140 val = self.ui.fout.getvalue()
140 val = self._ui.fout.getvalue()
141 self.ui.ferr, self.ui.fout = self.oldio
141 self._ui.ferr, self._ui.fout = self._oldio
142 return val
142 return val
143
143
144 def _client(self):
144 def _client(self):
145 return 'remote:%s:%s:%s' % (
145 return 'remote:%s:%s:%s' % (
146 self.req.env.get('wsgi.url_scheme') or 'http',
146 self._req.env.get('wsgi.url_scheme') or 'http',
147 urlreq.quote(self.req.env.get('REMOTE_HOST', '')),
147 urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
148 urlreq.quote(self.req.env.get('REMOTE_USER', '')))
148 urlreq.quote(self._req.env.get('REMOTE_USER', '')))
149
149
150 def responsetype(self, prefer_uncompressed):
150 def responsetype(self, prefer_uncompressed):
151 """Determine the appropriate response type and compression settings.
151 """Determine the appropriate response type and compression settings.
152
152
153 Returns a tuple of (mediatype, compengine, engineopts).
153 Returns a tuple of (mediatype, compengine, engineopts).
154 """
154 """
155 # Determine the response media type and compression engine based
155 # Determine the response media type and compression engine based
156 # on the request parameters.
156 # on the request parameters.
157 protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
157 protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ')
158
158
159 if '0.2' in protocaps:
159 if '0.2' in protocaps:
160 # All clients are expected to support uncompressed data.
160 # All clients are expected to support uncompressed data.
161 if prefer_uncompressed:
161 if prefer_uncompressed:
162 return HGTYPE2, util._noopengine(), {}
162 return HGTYPE2, util._noopengine(), {}
163
163
164 # Default as defined by wire protocol spec.
164 # Default as defined by wire protocol spec.
165 compformats = ['zlib', 'none']
165 compformats = ['zlib', 'none']
166 for cap in protocaps:
166 for cap in protocaps:
167 if cap.startswith('comp='):
167 if cap.startswith('comp='):
168 compformats = cap[5:].split(',')
168 compformats = cap[5:].split(',')
169 break
169 break
170
170
171 # Now find an agreed upon compression format.
171 # Now find an agreed upon compression format.
172 for engine in wireproto.supportedcompengines(self.ui, self,
172 for engine in wireproto.supportedcompengines(self._ui, self,
173 util.SERVERROLE):
173 util.SERVERROLE):
174 if engine.wireprotosupport().name in compformats:
174 if engine.wireprotosupport().name in compformats:
175 opts = {}
175 opts = {}
176 level = self.ui.configint('server',
176 level = self._ui.configint('server',
177 '%slevel' % engine.name())
177 '%slevel' % engine.name())
178 if level is not None:
178 if level is not None:
179 opts['level'] = level
179 opts['level'] = level
180
180
181 return HGTYPE2, engine, opts
181 return HGTYPE2, engine, opts
182
182
183 # No mutually supported compression format. Fall back to the
183 # No mutually supported compression format. Fall back to the
184 # legacy protocol.
184 # legacy protocol.
185
185
186 # Don't allow untrusted settings because disabling compression or
186 # Don't allow untrusted settings because disabling compression or
187 # setting a very high compression level could lead to flooding
187 # setting a very high compression level could lead to flooding
188 # the server's network or CPU.
188 # the server's network or CPU.
189 opts = {'level': self.ui.configint('server', 'zliblevel')}
189 opts = {'level': self._ui.configint('server', 'zliblevel')}
190 return HGTYPE, util.compengines['zlib'], opts
190 return HGTYPE, util.compengines['zlib'], opts
191
191
192 def iscmd(cmd):
192 def iscmd(cmd):
193 return cmd in wireproto.commands
193 return cmd in wireproto.commands
194
194
195 def callhttp(repo, req, cmd):
195 def callhttp(repo, req, cmd):
196 proto = webproto(req, repo.ui)
196 proto = webproto(req, repo.ui)
197
197
198 def genversion2(gen, engine, engineopts):
198 def genversion2(gen, engine, engineopts):
199 # application/mercurial-0.2 always sends a payload header
199 # application/mercurial-0.2 always sends a payload header
200 # identifying the compression engine.
200 # identifying the compression engine.
201 name = engine.wireprotosupport().name
201 name = engine.wireprotosupport().name
202 assert 0 < len(name) < 256
202 assert 0 < len(name) < 256
203 yield struct.pack('B', len(name))
203 yield struct.pack('B', len(name))
204 yield name
204 yield name
205
205
206 for chunk in gen:
206 for chunk in gen:
207 yield chunk
207 yield chunk
208
208
209 rsp = wireproto.dispatch(repo, proto, cmd)
209 rsp = wireproto.dispatch(repo, proto, cmd)
210 if isinstance(rsp, bytes):
210 if isinstance(rsp, bytes):
211 req.respond(HTTP_OK, HGTYPE, body=rsp)
211 req.respond(HTTP_OK, HGTYPE, body=rsp)
212 return []
212 return []
213 elif isinstance(rsp, wireproto.streamres_legacy):
213 elif isinstance(rsp, wireproto.streamres_legacy):
214 gen = rsp.gen
214 gen = rsp.gen
215 req.respond(HTTP_OK, HGTYPE)
215 req.respond(HTTP_OK, HGTYPE)
216 return gen
216 return gen
217 elif isinstance(rsp, wireproto.streamres):
217 elif isinstance(rsp, wireproto.streamres):
218 gen = rsp.gen
218 gen = rsp.gen
219
219
220 # This code for compression should not be streamres specific. It
220 # This code for compression should not be streamres specific. It
221 # is here because we only compress streamres at the moment.
221 # is here because we only compress streamres at the moment.
222 mediatype, engine, engineopts = proto.responsetype(
222 mediatype, engine, engineopts = proto.responsetype(
223 rsp.prefer_uncompressed)
223 rsp.prefer_uncompressed)
224 gen = engine.compressstream(gen, engineopts)
224 gen = engine.compressstream(gen, engineopts)
225
225
226 if mediatype == HGTYPE2:
226 if mediatype == HGTYPE2:
227 gen = genversion2(gen, engine, engineopts)
227 gen = genversion2(gen, engine, engineopts)
228
228
229 req.respond(HTTP_OK, mediatype)
229 req.respond(HTTP_OK, mediatype)
230 return gen
230 return gen
231 elif isinstance(rsp, wireproto.pushres):
231 elif isinstance(rsp, wireproto.pushres):
232 val = proto.restore()
232 val = proto.restore()
233 rsp = '%d\n%s' % (rsp.res, val)
233 rsp = '%d\n%s' % (rsp.res, val)
234 req.respond(HTTP_OK, HGTYPE, body=rsp)
234 req.respond(HTTP_OK, HGTYPE, body=rsp)
235 return []
235 return []
236 elif isinstance(rsp, wireproto.pusherr):
236 elif isinstance(rsp, wireproto.pusherr):
237 # drain the incoming bundle
237 # drain the incoming bundle
238 req.drain()
238 req.drain()
239 proto.restore()
239 proto.restore()
240 rsp = '0\n%s\n' % rsp.res
240 rsp = '0\n%s\n' % rsp.res
241 req.respond(HTTP_OK, HGTYPE, body=rsp)
241 req.respond(HTTP_OK, HGTYPE, body=rsp)
242 return []
242 return []
243 elif isinstance(rsp, wireproto.ooberror):
243 elif isinstance(rsp, wireproto.ooberror):
244 rsp = rsp.message
244 rsp = rsp.message
245 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
245 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
246 return []
246 return []
247 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
247 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
248
248
249 class sshserver(abstractserverproto):
249 class sshserver(abstractserverproto):
250 def __init__(self, ui, repo):
250 def __init__(self, ui, repo):
251 self.ui = ui
251 self.ui = ui
252 self.repo = repo
252 self.repo = repo
253 self.lock = None
253 self.lock = None
254 self.fin = ui.fin
254 self.fin = ui.fin
255 self.fout = ui.fout
255 self.fout = ui.fout
256 self.name = 'ssh'
256 self.name = 'ssh'
257
257
258 hook.redirect(True)
258 hook.redirect(True)
259 ui.fout = repo.ui.fout = ui.ferr
259 ui.fout = repo.ui.fout = ui.ferr
260
260
261 # Prevent insertion/deletion of CRs
261 # Prevent insertion/deletion of CRs
262 util.setbinary(self.fin)
262 util.setbinary(self.fin)
263 util.setbinary(self.fout)
263 util.setbinary(self.fout)
264
264
265 def getargs(self, args):
265 def getargs(self, args):
266 data = {}
266 data = {}
267 keys = args.split()
267 keys = args.split()
268 for n in xrange(len(keys)):
268 for n in xrange(len(keys)):
269 argline = self.fin.readline()[:-1]
269 argline = self.fin.readline()[:-1]
270 arg, l = argline.split()
270 arg, l = argline.split()
271 if arg not in keys:
271 if arg not in keys:
272 raise error.Abort(_("unexpected parameter %r") % arg)
272 raise error.Abort(_("unexpected parameter %r") % arg)
273 if arg == '*':
273 if arg == '*':
274 star = {}
274 star = {}
275 for k in xrange(int(l)):
275 for k in xrange(int(l)):
276 argline = self.fin.readline()[:-1]
276 argline = self.fin.readline()[:-1]
277 arg, l = argline.split()
277 arg, l = argline.split()
278 val = self.fin.read(int(l))
278 val = self.fin.read(int(l))
279 star[arg] = val
279 star[arg] = val
280 data['*'] = star
280 data['*'] = star
281 else:
281 else:
282 val = self.fin.read(int(l))
282 val = self.fin.read(int(l))
283 data[arg] = val
283 data[arg] = val
284 return [data[k] for k in keys]
284 return [data[k] for k in keys]
285
285
286 def getarg(self, name):
286 def getarg(self, name):
287 return self.getargs(name)[0]
287 return self.getargs(name)[0]
288
288
289 def getfile(self, fpout):
289 def getfile(self, fpout):
290 self.sendresponse('')
290 self.sendresponse('')
291 count = int(self.fin.readline())
291 count = int(self.fin.readline())
292 while count:
292 while count:
293 fpout.write(self.fin.read(count))
293 fpout.write(self.fin.read(count))
294 count = int(self.fin.readline())
294 count = int(self.fin.readline())
295
295
296 def redirect(self):
296 def redirect(self):
297 pass
297 pass
298
298
299 def sendresponse(self, v):
299 def sendresponse(self, v):
300 self.fout.write("%d\n" % len(v))
300 self.fout.write("%d\n" % len(v))
301 self.fout.write(v)
301 self.fout.write(v)
302 self.fout.flush()
302 self.fout.flush()
303
303
304 def sendstream(self, source):
304 def sendstream(self, source):
305 write = self.fout.write
305 write = self.fout.write
306 for chunk in source.gen:
306 for chunk in source.gen:
307 write(chunk)
307 write(chunk)
308 self.fout.flush()
308 self.fout.flush()
309
309
310 def sendpushresponse(self, rsp):
310 def sendpushresponse(self, rsp):
311 self.sendresponse('')
311 self.sendresponse('')
312 self.sendresponse(str(rsp.res))
312 self.sendresponse(str(rsp.res))
313
313
314 def sendpusherror(self, rsp):
314 def sendpusherror(self, rsp):
315 self.sendresponse(rsp.res)
315 self.sendresponse(rsp.res)
316
316
317 def sendooberror(self, rsp):
317 def sendooberror(self, rsp):
318 self.ui.ferr.write('%s\n-\n' % rsp.message)
318 self.ui.ferr.write('%s\n-\n' % rsp.message)
319 self.ui.ferr.flush()
319 self.ui.ferr.flush()
320 self.fout.write('\n')
320 self.fout.write('\n')
321 self.fout.flush()
321 self.fout.flush()
322
322
323 def serve_forever(self):
323 def serve_forever(self):
324 try:
324 try:
325 while self.serve_one():
325 while self.serve_one():
326 pass
326 pass
327 finally:
327 finally:
328 if self.lock is not None:
328 if self.lock is not None:
329 self.lock.release()
329 self.lock.release()
330 sys.exit(0)
330 sys.exit(0)
331
331
332 handlers = {
332 handlers = {
333 str: sendresponse,
333 str: sendresponse,
334 wireproto.streamres: sendstream,
334 wireproto.streamres: sendstream,
335 wireproto.streamres_legacy: sendstream,
335 wireproto.streamres_legacy: sendstream,
336 wireproto.pushres: sendpushresponse,
336 wireproto.pushres: sendpushresponse,
337 wireproto.pusherr: sendpusherror,
337 wireproto.pusherr: sendpusherror,
338 wireproto.ooberror: sendooberror,
338 wireproto.ooberror: sendooberror,
339 }
339 }
340
340
341 def serve_one(self):
341 def serve_one(self):
342 cmd = self.fin.readline()[:-1]
342 cmd = self.fin.readline()[:-1]
343 if cmd and cmd in wireproto.commands:
343 if cmd and cmd in wireproto.commands:
344 rsp = wireproto.dispatch(self.repo, self, cmd)
344 rsp = wireproto.dispatch(self.repo, self, cmd)
345 self.handlers[rsp.__class__](self, rsp)
345 self.handlers[rsp.__class__](self, rsp)
346 elif cmd:
346 elif cmd:
347 impl = getattr(self, 'do_' + cmd, None)
347 impl = getattr(self, 'do_' + cmd, None)
348 if impl:
348 if impl:
349 r = impl()
349 r = impl()
350 if r is not None:
350 if r is not None:
351 self.sendresponse(r)
351 self.sendresponse(r)
352 else:
352 else:
353 self.sendresponse("")
353 self.sendresponse("")
354 return cmd != ''
354 return cmd != ''
355
355
356 def _client(self):
356 def _client(self):
357 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
357 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
358 return 'remote:ssh:' + client
358 return 'remote:ssh:' + client
General Comments 0
You need to be logged in to leave comments. Login now