##// END OF EJS Templates
wireprotoserver: move responsetype() out of http handler...
Gregory Szorc -
r36089:341c886e default
parent child Browse files
Show More
@@ -1,455 +1,453
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 abc
9 import abc
10 import cgi
10 import cgi
11 import contextlib
11 import contextlib
12 import struct
12 import struct
13 import sys
13 import sys
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 encoding,
17 encoding,
18 error,
18 error,
19 hook,
19 hook,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 wireproto,
22 wireproto,
23 )
23 )
24
24
25 stringio = util.stringio
25 stringio = util.stringio
26
26
27 urlerr = util.urlerr
27 urlerr = util.urlerr
28 urlreq = util.urlreq
28 urlreq = util.urlreq
29
29
30 HTTP_OK = 200
30 HTTP_OK = 200
31
31
32 HGTYPE = 'application/mercurial-0.1'
32 HGTYPE = 'application/mercurial-0.1'
33 HGTYPE2 = 'application/mercurial-0.2'
33 HGTYPE2 = 'application/mercurial-0.2'
34 HGERRTYPE = 'application/hg-error'
34 HGERRTYPE = 'application/hg-error'
35
35
36 # Names of the SSH protocol implementations.
36 # Names of the SSH protocol implementations.
37 SSHV1 = 'ssh-v1'
37 SSHV1 = 'ssh-v1'
38 # This is advertised over the wire. Incremental the counter at the end
38 # This is advertised over the wire. Incremental the counter at the end
39 # to reflect BC breakages.
39 # to reflect BC breakages.
40 SSHV2 = 'exp-ssh-v2-0001'
40 SSHV2 = 'exp-ssh-v2-0001'
41
41
42 class baseprotocolhandler(object):
42 class baseprotocolhandler(object):
43 """Abstract base class for wire protocol handlers.
43 """Abstract base class for wire protocol handlers.
44
44
45 A wire protocol handler serves as an interface between protocol command
45 A wire protocol handler serves as an interface between protocol command
46 handlers and the wire protocol transport layer. Protocol handlers provide
46 handlers and the wire protocol transport layer. Protocol handlers provide
47 methods to read command arguments, redirect stdio for the duration of
47 methods to read command arguments, redirect stdio for the duration of
48 the request, handle response types, etc.
48 the request, handle response types, etc.
49 """
49 """
50
50
51 __metaclass__ = abc.ABCMeta
51 __metaclass__ = abc.ABCMeta
52
52
53 @abc.abstractproperty
53 @abc.abstractproperty
54 def name(self):
54 def name(self):
55 """The name of the protocol implementation.
55 """The name of the protocol implementation.
56
56
57 Used for uniquely identifying the transport type.
57 Used for uniquely identifying the transport type.
58 """
58 """
59
59
60 @abc.abstractmethod
60 @abc.abstractmethod
61 def getargs(self, args):
61 def getargs(self, args):
62 """return the value for arguments in <args>
62 """return the value for arguments in <args>
63
63
64 returns a list of values (same order as <args>)"""
64 returns a list of values (same order as <args>)"""
65
65
66 @abc.abstractmethod
66 @abc.abstractmethod
67 def forwardpayload(self, fp):
67 def forwardpayload(self, fp):
68 """Read the raw payload and forward to a file.
68 """Read the raw payload and forward to a file.
69
69
70 The payload is read in full before the function returns.
70 The payload is read in full before the function returns.
71 """
71 """
72
72
73 @abc.abstractmethod
73 @abc.abstractmethod
74 def mayberedirectstdio(self):
74 def mayberedirectstdio(self):
75 """Context manager to possibly redirect stdio.
75 """Context manager to possibly redirect stdio.
76
76
77 The context manager yields a file-object like object that receives
77 The context manager yields a file-object like object that receives
78 stdout and stderr output when the context manager is active. Or it
78 stdout and stderr output when the context manager is active. Or it
79 yields ``None`` if no I/O redirection occurs.
79 yields ``None`` if no I/O redirection occurs.
80
80
81 The intent of this context manager is to capture stdio output
81 The intent of this context manager is to capture stdio output
82 so it may be sent in the response. Some transports support streaming
82 so it may be sent in the response. Some transports support streaming
83 stdio to the client in real time. For these transports, stdio output
83 stdio to the client in real time. For these transports, stdio output
84 won't be captured.
84 won't be captured.
85 """
85 """
86
86
87 @abc.abstractmethod
87 @abc.abstractmethod
88 def client(self):
88 def client(self):
89 """Returns a string representation of this client (as bytes)."""
89 """Returns a string representation of this client (as bytes)."""
90
90
91 def decodevaluefromheaders(req, headerprefix):
91 def decodevaluefromheaders(req, headerprefix):
92 """Decode a long value from multiple HTTP request headers.
92 """Decode a long value from multiple HTTP request headers.
93
93
94 Returns the value as a bytes, not a str.
94 Returns the value as a bytes, not a str.
95 """
95 """
96 chunks = []
96 chunks = []
97 i = 1
97 i = 1
98 prefix = headerprefix.upper().replace(r'-', r'_')
98 prefix = headerprefix.upper().replace(r'-', r'_')
99 while True:
99 while True:
100 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
100 v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
101 if v is None:
101 if v is None:
102 break
102 break
103 chunks.append(pycompat.bytesurl(v))
103 chunks.append(pycompat.bytesurl(v))
104 i += 1
104 i += 1
105
105
106 return ''.join(chunks)
106 return ''.join(chunks)
107
107
108 class webproto(baseprotocolhandler):
108 class webproto(baseprotocolhandler):
109 def __init__(self, req, ui):
109 def __init__(self, req, ui):
110 self._req = req
110 self._req = req
111 self._ui = ui
111 self._ui = ui
112
112
113 @property
113 @property
114 def name(self):
114 def name(self):
115 return 'http'
115 return 'http'
116
116
117 def getargs(self, args):
117 def getargs(self, args):
118 knownargs = self._args()
118 knownargs = self._args()
119 data = {}
119 data = {}
120 keys = args.split()
120 keys = args.split()
121 for k in keys:
121 for k in keys:
122 if k == '*':
122 if k == '*':
123 star = {}
123 star = {}
124 for key in knownargs.keys():
124 for key in knownargs.keys():
125 if key != 'cmd' and key not in keys:
125 if key != 'cmd' and key not in keys:
126 star[key] = knownargs[key][0]
126 star[key] = knownargs[key][0]
127 data['*'] = star
127 data['*'] = star
128 else:
128 else:
129 data[k] = knownargs[k][0]
129 data[k] = knownargs[k][0]
130 return [data[k] for k in keys]
130 return [data[k] for k in keys]
131
131
132 def _args(self):
132 def _args(self):
133 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
133 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
134 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
134 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
135 if postlen:
135 if postlen:
136 args.update(cgi.parse_qs(
136 args.update(cgi.parse_qs(
137 self._req.read(postlen), keep_blank_values=True))
137 self._req.read(postlen), keep_blank_values=True))
138 return args
138 return args
139
139
140 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
140 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
141 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
141 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
142 return args
142 return args
143
143
144 def forwardpayload(self, fp):
144 def forwardpayload(self, fp):
145 length = int(self._req.env[r'CONTENT_LENGTH'])
145 length = int(self._req.env[r'CONTENT_LENGTH'])
146 # If httppostargs is used, we need to read Content-Length
146 # If httppostargs is used, we need to read Content-Length
147 # minus the amount that was consumed by args.
147 # minus the amount that was consumed by args.
148 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
148 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
149 for s in util.filechunkiter(self._req, limit=length):
149 for s in util.filechunkiter(self._req, limit=length):
150 fp.write(s)
150 fp.write(s)
151
151
152 @contextlib.contextmanager
152 @contextlib.contextmanager
153 def mayberedirectstdio(self):
153 def mayberedirectstdio(self):
154 oldout = self._ui.fout
154 oldout = self._ui.fout
155 olderr = self._ui.ferr
155 olderr = self._ui.ferr
156
156
157 out = util.stringio()
157 out = util.stringio()
158
158
159 try:
159 try:
160 self._ui.fout = out
160 self._ui.fout = out
161 self._ui.ferr = out
161 self._ui.ferr = out
162 yield out
162 yield out
163 finally:
163 finally:
164 self._ui.fout = oldout
164 self._ui.fout = oldout
165 self._ui.ferr = olderr
165 self._ui.ferr = olderr
166
166
167 def client(self):
167 def client(self):
168 return 'remote:%s:%s:%s' % (
168 return 'remote:%s:%s:%s' % (
169 self._req.env.get('wsgi.url_scheme') or 'http',
169 self._req.env.get('wsgi.url_scheme') or 'http',
170 urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
170 urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
171 urlreq.quote(self._req.env.get('REMOTE_USER', '')))
171 urlreq.quote(self._req.env.get('REMOTE_USER', '')))
172
172
173 def responsetype(self, prefer_uncompressed):
174 """Determine the appropriate response type and compression settings.
175
176 Returns a tuple of (mediatype, compengine, engineopts).
177 """
178 # Determine the response media type and compression engine based
179 # on the request parameters.
180 protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ')
181
182 if '0.2' in protocaps:
183 # All clients are expected to support uncompressed data.
184 if prefer_uncompressed:
185 return HGTYPE2, util._noopengine(), {}
186
187 # Default as defined by wire protocol spec.
188 compformats = ['zlib', 'none']
189 for cap in protocaps:
190 if cap.startswith('comp='):
191 compformats = cap[5:].split(',')
192 break
193
194 # Now find an agreed upon compression format.
195 for engine in wireproto.supportedcompengines(self._ui,
196 util.SERVERROLE):
197 if engine.wireprotosupport().name in compformats:
198 opts = {}
199 level = self._ui.configint('server',
200 '%slevel' % engine.name())
201 if level is not None:
202 opts['level'] = level
203
204 return HGTYPE2, engine, opts
205
206 # No mutually supported compression format. Fall back to the
207 # legacy protocol.
208
209 # Don't allow untrusted settings because disabling compression or
210 # setting a very high compression level could lead to flooding
211 # the server's network or CPU.
212 opts = {'level': self._ui.configint('server', 'zliblevel')}
213 return HGTYPE, util.compengines['zlib'], opts
214
215 def iscmd(cmd):
173 def iscmd(cmd):
216 return cmd in wireproto.commands
174 return cmd in wireproto.commands
217
175
218 def parsehttprequest(repo, req, query):
176 def parsehttprequest(repo, req, query):
219 """Parse the HTTP request for a wire protocol request.
177 """Parse the HTTP request for a wire protocol request.
220
178
221 If the current request appears to be a wire protocol request, this
179 If the current request appears to be a wire protocol request, this
222 function returns a dict with details about that request, including
180 function returns a dict with details about that request, including
223 an ``abstractprotocolserver`` instance suitable for handling the
181 an ``abstractprotocolserver`` instance suitable for handling the
224 request. Otherwise, ``None`` is returned.
182 request. Otherwise, ``None`` is returned.
225
183
226 ``req`` is a ``wsgirequest`` instance.
184 ``req`` is a ``wsgirequest`` instance.
227 """
185 """
228 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
186 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
229 # string parameter. If it isn't present, this isn't a wire protocol
187 # string parameter. If it isn't present, this isn't a wire protocol
230 # request.
188 # request.
231 if r'cmd' not in req.form:
189 if r'cmd' not in req.form:
232 return None
190 return None
233
191
234 cmd = pycompat.sysbytes(req.form[r'cmd'][0])
192 cmd = pycompat.sysbytes(req.form[r'cmd'][0])
235
193
236 # The "cmd" request parameter is used by both the wire protocol and hgweb.
194 # The "cmd" request parameter is used by both the wire protocol and hgweb.
237 # While not all wire protocol commands are available for all transports,
195 # While not all wire protocol commands are available for all transports,
238 # if we see a "cmd" value that resembles a known wire protocol command, we
196 # if we see a "cmd" value that resembles a known wire protocol command, we
239 # route it to a protocol handler. This is better than routing possible
197 # route it to a protocol handler. This is better than routing possible
240 # wire protocol requests to hgweb because it prevents hgweb from using
198 # wire protocol requests to hgweb because it prevents hgweb from using
241 # known wire protocol commands and it is less confusing for machine
199 # known wire protocol commands and it is less confusing for machine
242 # clients.
200 # clients.
243 if cmd not in wireproto.commands:
201 if cmd not in wireproto.commands:
244 return None
202 return None
245
203
246 proto = webproto(req, repo.ui)
204 proto = webproto(req, repo.ui)
247
205
248 return {
206 return {
249 'cmd': cmd,
207 'cmd': cmd,
250 'proto': proto,
208 'proto': proto,
251 'dispatch': lambda: _callhttp(repo, req, proto, cmd),
209 'dispatch': lambda: _callhttp(repo, req, proto, cmd),
252 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
210 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
253 }
211 }
254
212
213 def _httpresponsetype(ui, req, prefer_uncompressed):
214 """Determine the appropriate response type and compression settings.
215
216 Returns a tuple of (mediatype, compengine, engineopts).
217 """
218 # Determine the response media type and compression engine based
219 # on the request parameters.
220 protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ')
221
222 if '0.2' in protocaps:
223 # All clients are expected to support uncompressed data.
224 if prefer_uncompressed:
225 return HGTYPE2, util._noopengine(), {}
226
227 # Default as defined by wire protocol spec.
228 compformats = ['zlib', 'none']
229 for cap in protocaps:
230 if cap.startswith('comp='):
231 compformats = cap[5:].split(',')
232 break
233
234 # Now find an agreed upon compression format.
235 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
236 if engine.wireprotosupport().name in compformats:
237 opts = {}
238 level = ui.configint('server', '%slevel' % engine.name())
239 if level is not None:
240 opts['level'] = level
241
242 return HGTYPE2, engine, opts
243
244 # No mutually supported compression format. Fall back to the
245 # legacy protocol.
246
247 # Don't allow untrusted settings because disabling compression or
248 # setting a very high compression level could lead to flooding
249 # the server's network or CPU.
250 opts = {'level': ui.configint('server', 'zliblevel')}
251 return HGTYPE, util.compengines['zlib'], opts
252
255 def _callhttp(repo, req, proto, cmd):
253 def _callhttp(repo, req, proto, cmd):
256 def genversion2(gen, engine, engineopts):
254 def genversion2(gen, engine, engineopts):
257 # application/mercurial-0.2 always sends a payload header
255 # application/mercurial-0.2 always sends a payload header
258 # identifying the compression engine.
256 # identifying the compression engine.
259 name = engine.wireprotosupport().name
257 name = engine.wireprotosupport().name
260 assert 0 < len(name) < 256
258 assert 0 < len(name) < 256
261 yield struct.pack('B', len(name))
259 yield struct.pack('B', len(name))
262 yield name
260 yield name
263
261
264 for chunk in gen:
262 for chunk in gen:
265 yield chunk
263 yield chunk
266
264
267 rsp = wireproto.dispatch(repo, proto, cmd)
265 rsp = wireproto.dispatch(repo, proto, cmd)
268
266
269 if not wireproto.commands.commandavailable(cmd, proto):
267 if not wireproto.commands.commandavailable(cmd, proto):
270 req.respond(HTTP_OK, HGERRTYPE,
268 req.respond(HTTP_OK, HGERRTYPE,
271 body=_('requested wire protocol command is not available '
269 body=_('requested wire protocol command is not available '
272 'over HTTP'))
270 'over HTTP'))
273 return []
271 return []
274
272
275 if isinstance(rsp, bytes):
273 if isinstance(rsp, bytes):
276 req.respond(HTTP_OK, HGTYPE, body=rsp)
274 req.respond(HTTP_OK, HGTYPE, body=rsp)
277 return []
275 return []
278 elif isinstance(rsp, wireproto.streamres_legacy):
276 elif isinstance(rsp, wireproto.streamres_legacy):
279 gen = rsp.gen
277 gen = rsp.gen
280 req.respond(HTTP_OK, HGTYPE)
278 req.respond(HTTP_OK, HGTYPE)
281 return gen
279 return gen
282 elif isinstance(rsp, wireproto.streamres):
280 elif isinstance(rsp, wireproto.streamres):
283 gen = rsp.gen
281 gen = rsp.gen
284
282
285 # This code for compression should not be streamres specific. It
283 # This code for compression should not be streamres specific. It
286 # is here because we only compress streamres at the moment.
284 # is here because we only compress streamres at the moment.
287 mediatype, engine, engineopts = proto.responsetype(
285 mediatype, engine, engineopts = _httpresponsetype(
288 rsp.prefer_uncompressed)
286 repo.ui, req, rsp.prefer_uncompressed)
289 gen = engine.compressstream(gen, engineopts)
287 gen = engine.compressstream(gen, engineopts)
290
288
291 if mediatype == HGTYPE2:
289 if mediatype == HGTYPE2:
292 gen = genversion2(gen, engine, engineopts)
290 gen = genversion2(gen, engine, engineopts)
293
291
294 req.respond(HTTP_OK, mediatype)
292 req.respond(HTTP_OK, mediatype)
295 return gen
293 return gen
296 elif isinstance(rsp, wireproto.pushres):
294 elif isinstance(rsp, wireproto.pushres):
297 rsp = '%d\n%s' % (rsp.res, rsp.output)
295 rsp = '%d\n%s' % (rsp.res, rsp.output)
298 req.respond(HTTP_OK, HGTYPE, body=rsp)
296 req.respond(HTTP_OK, HGTYPE, body=rsp)
299 return []
297 return []
300 elif isinstance(rsp, wireproto.pusherr):
298 elif isinstance(rsp, wireproto.pusherr):
301 # This is the httplib workaround documented in _handlehttperror().
299 # This is the httplib workaround documented in _handlehttperror().
302 req.drain()
300 req.drain()
303
301
304 rsp = '0\n%s\n' % rsp.res
302 rsp = '0\n%s\n' % rsp.res
305 req.respond(HTTP_OK, HGTYPE, body=rsp)
303 req.respond(HTTP_OK, HGTYPE, body=rsp)
306 return []
304 return []
307 elif isinstance(rsp, wireproto.ooberror):
305 elif isinstance(rsp, wireproto.ooberror):
308 rsp = rsp.message
306 rsp = rsp.message
309 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
307 req.respond(HTTP_OK, HGERRTYPE, body=rsp)
310 return []
308 return []
311 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
309 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
312
310
313 def _handlehttperror(e, req, cmd):
311 def _handlehttperror(e, req, cmd):
314 """Called when an ErrorResponse is raised during HTTP request processing."""
312 """Called when an ErrorResponse is raised during HTTP request processing."""
315
313
316 # Clients using Python's httplib are stateful: the HTTP client
314 # Clients using Python's httplib are stateful: the HTTP client
317 # won't process an HTTP response until all request data is
315 # won't process an HTTP response until all request data is
318 # sent to the server. The intent of this code is to ensure
316 # sent to the server. The intent of this code is to ensure
319 # we always read HTTP request data from the client, thus
317 # we always read HTTP request data from the client, thus
320 # ensuring httplib transitions to a state that allows it to read
318 # ensuring httplib transitions to a state that allows it to read
321 # the HTTP response. In other words, it helps prevent deadlocks
319 # the HTTP response. In other words, it helps prevent deadlocks
322 # on clients using httplib.
320 # on clients using httplib.
323
321
324 if (req.env[r'REQUEST_METHOD'] == r'POST' and
322 if (req.env[r'REQUEST_METHOD'] == r'POST' and
325 # But not if Expect: 100-continue is being used.
323 # But not if Expect: 100-continue is being used.
326 (req.env.get('HTTP_EXPECT',
324 (req.env.get('HTTP_EXPECT',
327 '').lower() != '100-continue') or
325 '').lower() != '100-continue') or
328 # Or the non-httplib HTTP library is being advertised by
326 # Or the non-httplib HTTP library is being advertised by
329 # the client.
327 # the client.
330 req.env.get('X-HgHttp2', '')):
328 req.env.get('X-HgHttp2', '')):
331 req.drain()
329 req.drain()
332 else:
330 else:
333 req.headers.append((r'Connection', r'Close'))
331 req.headers.append((r'Connection', r'Close'))
334
332
335 # TODO This response body assumes the failed command was
333 # TODO This response body assumes the failed command was
336 # "unbundle." That assumption is not always valid.
334 # "unbundle." That assumption is not always valid.
337 req.respond(e, HGTYPE, body='0\n%s\n' % e)
335 req.respond(e, HGTYPE, body='0\n%s\n' % e)
338
336
339 return ''
337 return ''
340
338
341 def _sshv1respondbytes(fout, value):
339 def _sshv1respondbytes(fout, value):
342 """Send a bytes response for protocol version 1."""
340 """Send a bytes response for protocol version 1."""
343 fout.write('%d\n' % len(value))
341 fout.write('%d\n' % len(value))
344 fout.write(value)
342 fout.write(value)
345 fout.flush()
343 fout.flush()
346
344
347 def _sshv1respondstream(fout, source):
345 def _sshv1respondstream(fout, source):
348 write = fout.write
346 write = fout.write
349 for chunk in source.gen:
347 for chunk in source.gen:
350 write(chunk)
348 write(chunk)
351 fout.flush()
349 fout.flush()
352
350
353 def _sshv1respondooberror(fout, ferr, rsp):
351 def _sshv1respondooberror(fout, ferr, rsp):
354 ferr.write(b'%s\n-\n' % rsp)
352 ferr.write(b'%s\n-\n' % rsp)
355 ferr.flush()
353 ferr.flush()
356 fout.write(b'\n')
354 fout.write(b'\n')
357 fout.flush()
355 fout.flush()
358
356
359 class sshv1protocolhandler(baseprotocolhandler):
357 class sshv1protocolhandler(baseprotocolhandler):
360 """Handler for requests services via version 1 of SSH protocol."""
358 """Handler for requests services via version 1 of SSH protocol."""
361 def __init__(self, ui, fin, fout):
359 def __init__(self, ui, fin, fout):
362 self._ui = ui
360 self._ui = ui
363 self._fin = fin
361 self._fin = fin
364 self._fout = fout
362 self._fout = fout
365
363
366 @property
364 @property
367 def name(self):
365 def name(self):
368 return 'ssh'
366 return 'ssh'
369
367
370 def getargs(self, args):
368 def getargs(self, args):
371 data = {}
369 data = {}
372 keys = args.split()
370 keys = args.split()
373 for n in xrange(len(keys)):
371 for n in xrange(len(keys)):
374 argline = self._fin.readline()[:-1]
372 argline = self._fin.readline()[:-1]
375 arg, l = argline.split()
373 arg, l = argline.split()
376 if arg not in keys:
374 if arg not in keys:
377 raise error.Abort(_("unexpected parameter %r") % arg)
375 raise error.Abort(_("unexpected parameter %r") % arg)
378 if arg == '*':
376 if arg == '*':
379 star = {}
377 star = {}
380 for k in xrange(int(l)):
378 for k in xrange(int(l)):
381 argline = self._fin.readline()[:-1]
379 argline = self._fin.readline()[:-1]
382 arg, l = argline.split()
380 arg, l = argline.split()
383 val = self._fin.read(int(l))
381 val = self._fin.read(int(l))
384 star[arg] = val
382 star[arg] = val
385 data['*'] = star
383 data['*'] = star
386 else:
384 else:
387 val = self._fin.read(int(l))
385 val = self._fin.read(int(l))
388 data[arg] = val
386 data[arg] = val
389 return [data[k] for k in keys]
387 return [data[k] for k in keys]
390
388
391 def forwardpayload(self, fpout):
389 def forwardpayload(self, fpout):
392 # The file is in the form:
390 # The file is in the form:
393 #
391 #
394 # <chunk size>\n<chunk>
392 # <chunk size>\n<chunk>
395 # ...
393 # ...
396 # 0\n
394 # 0\n
397 _sshv1respondbytes(self._fout, b'')
395 _sshv1respondbytes(self._fout, b'')
398 count = int(self._fin.readline())
396 count = int(self._fin.readline())
399 while count:
397 while count:
400 fpout.write(self._fin.read(count))
398 fpout.write(self._fin.read(count))
401 count = int(self._fin.readline())
399 count = int(self._fin.readline())
402
400
403 @contextlib.contextmanager
401 @contextlib.contextmanager
404 def mayberedirectstdio(self):
402 def mayberedirectstdio(self):
405 yield None
403 yield None
406
404
407 def client(self):
405 def client(self):
408 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
406 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
409 return 'remote:ssh:' + client
407 return 'remote:ssh:' + client
410
408
411 class sshserver(object):
409 class sshserver(object):
412 def __init__(self, ui, repo):
410 def __init__(self, ui, repo):
413 self._ui = ui
411 self._ui = ui
414 self._repo = repo
412 self._repo = repo
415 self._fin = ui.fin
413 self._fin = ui.fin
416 self._fout = ui.fout
414 self._fout = ui.fout
417
415
418 hook.redirect(True)
416 hook.redirect(True)
419 ui.fout = repo.ui.fout = ui.ferr
417 ui.fout = repo.ui.fout = ui.ferr
420
418
421 # Prevent insertion/deletion of CRs
419 # Prevent insertion/deletion of CRs
422 util.setbinary(self._fin)
420 util.setbinary(self._fin)
423 util.setbinary(self._fout)
421 util.setbinary(self._fout)
424
422
425 self._proto = sshv1protocolhandler(self._ui, self._fin, self._fout)
423 self._proto = sshv1protocolhandler(self._ui, self._fin, self._fout)
426
424
427 def serve_forever(self):
425 def serve_forever(self):
428 while self.serve_one():
426 while self.serve_one():
429 pass
427 pass
430 sys.exit(0)
428 sys.exit(0)
431
429
432 def serve_one(self):
430 def serve_one(self):
433 cmd = self._fin.readline()[:-1]
431 cmd = self._fin.readline()[:-1]
434 if cmd and wireproto.commands.commandavailable(cmd, self._proto):
432 if cmd and wireproto.commands.commandavailable(cmd, self._proto):
435 rsp = wireproto.dispatch(self._repo, self._proto, cmd)
433 rsp = wireproto.dispatch(self._repo, self._proto, cmd)
436
434
437 if isinstance(rsp, bytes):
435 if isinstance(rsp, bytes):
438 _sshv1respondbytes(self._fout, rsp)
436 _sshv1respondbytes(self._fout, rsp)
439 elif isinstance(rsp, wireproto.streamres):
437 elif isinstance(rsp, wireproto.streamres):
440 _sshv1respondstream(self._fout, rsp)
438 _sshv1respondstream(self._fout, rsp)
441 elif isinstance(rsp, wireproto.streamres_legacy):
439 elif isinstance(rsp, wireproto.streamres_legacy):
442 _sshv1respondstream(self._fout, rsp)
440 _sshv1respondstream(self._fout, rsp)
443 elif isinstance(rsp, wireproto.pushres):
441 elif isinstance(rsp, wireproto.pushres):
444 _sshv1respondbytes(self._fout, b'')
442 _sshv1respondbytes(self._fout, b'')
445 _sshv1respondbytes(self._fout, bytes(rsp.res))
443 _sshv1respondbytes(self._fout, bytes(rsp.res))
446 elif isinstance(rsp, wireproto.pusherr):
444 elif isinstance(rsp, wireproto.pusherr):
447 _sshv1respondbytes(self._fout, rsp.res)
445 _sshv1respondbytes(self._fout, rsp.res)
448 elif isinstance(rsp, wireproto.ooberror):
446 elif isinstance(rsp, wireproto.ooberror):
449 _sshv1respondooberror(self._fout, self._ui.ferr, rsp.message)
447 _sshv1respondooberror(self._fout, self._ui.ferr, rsp.message)
450 else:
448 else:
451 raise error.ProgrammingError('unhandled response type from '
449 raise error.ProgrammingError('unhandled response type from '
452 'wire protocol command: %s' % rsp)
450 'wire protocol command: %s' % rsp)
453 elif cmd:
451 elif cmd:
454 _sshv1respondbytes(self._fout, b'')
452 _sshv1respondbytes(self._fout, b'')
455 return cmd != ''
453 return cmd != ''
General Comments 0
You need to be logged in to leave comments. Login now