##// END OF EJS Templates
py3: replace file() with open() in test-transplant.t...
py3: replace file() with open() in test-transplant.t file() is not present in Python 3. This patch also adds a b'' prefix to make sure we write bytes in Python 3. Differential Revision: https://phab.mercurial-scm.org/D2115

File last commit:

r36006:04231e89 default
r36033:ca62987f default
Show More
wireprotoserver.py
437 lines | 13.9 KiB | text/x-python | PythonLexer
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 import abc
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 import cgi
import struct
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 import sys
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 from .i18n import _
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 from . import (
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 encoding,
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 error,
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 hook,
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 pycompat,
util,
wireproto,
)
stringio = util.stringio
urlerr = util.urlerr
urlreq = util.urlreq
Gregory Szorc
wireprotoserver: don't import symbol from hgweb.common...
r35876 HTTP_OK = 200
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 HGTYPE = 'application/mercurial-0.1'
HGTYPE2 = 'application/mercurial-0.2'
HGERRTYPE = 'application/hg-error'
Gregory Szorc
sshpeer: initial definition and implementation of new SSH protocol...
r35994 # Names of the SSH protocol implementations.
SSHV1 = 'ssh-v1'
# This is advertised over the wire. Incremental the counter at the end
# to reflect BC breakages.
SSHV2 = 'exp-ssh-v2-0001'
Gregory Szorc
wireprotoserver: rename abstractserverproto and improve docstring...
r36006 class baseprotocolhandler(object):
"""Abstract base class for wire protocol handlers.
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878
Gregory Szorc
wireprotoserver: rename abstractserverproto and improve docstring...
r36006 A wire protocol handler serves as an interface between protocol command
handlers and the wire protocol transport layer. Protocol handlers provide
methods to read command arguments, redirect stdio for the duration of
the request, handle response types, etc.
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 """
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 __metaclass__ = abc.ABCMeta
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891 @abc.abstractproperty
def name(self):
"""The name of the protocol implementation.
Used for uniquely identifying the transport type.
"""
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 @abc.abstractmethod
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 def getargs(self, args):
"""return the value for arguments in <args>
returns a list of values (same order as <args>)"""
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 @abc.abstractmethod
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 def getfile(self, fp):
"""write the whole content of a file into a file like object
The file is in the form::
(<chunk-size>\n<chunk>)+0\n
chunk size is the ascii version of the int.
"""
Gregory Szorc
wireprotoserver: make abstractserverproto a proper abstract base class...
r35890 @abc.abstractmethod
Gregory Szorc
wireprotoserver: move abstractserverproto class from wireproto...
r35878 def redirect(self):
"""may setup interception for stdout and stderr
See also the `restore` method."""
# If the `redirect` function does install interception, the `restore`
# function MUST be defined. If interception is not used, this function
# MUST NOT be defined.
#
# left commented here on purpose
#
#def restore(self):
# """reinstall previous stdout and stderr and return intercepted stdout
# """
# raise NotImplementedError()
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def decodevaluefromheaders(req, headerprefix):
"""Decode a long value from multiple HTTP request headers.
Returns the value as a bytes, not a str.
"""
chunks = []
i = 1
prefix = headerprefix.upper().replace(r'-', r'_')
while True:
v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
if v is None:
break
chunks.append(pycompat.bytesurl(v))
i += 1
return ''.join(chunks)
Gregory Szorc
wireprotoserver: rename abstractserverproto and improve docstring...
r36006 class webproto(baseprotocolhandler):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def __init__(self, req, ui):
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._req = req
self._ui = ui
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891
@property
def name(self):
return 'http'
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
def getargs(self, args):
knownargs = self._args()
data = {}
keys = args.split()
for k in keys:
if k == '*':
star = {}
for key in knownargs.keys():
if key != 'cmd' and key not in keys:
star[key] = knownargs[key][0]
data['*'] = star
else:
data[k] = knownargs[k][0]
return [data[k] for k in keys]
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def _args(self):
Yuya Nishihara
py3: factor out helpers to apply string conversion recursively
r35918 args = util.rapply(pycompat.bytesurl, self._req.form.copy())
Gregory Szorc
wireprotoserver: make attributes private...
r35884 postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 if postlen:
args.update(cgi.parse_qs(
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._req.read(postlen), keep_blank_values=True))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return args
Gregory Szorc
wireprotoserver: make attributes private...
r35884 argvalue = decodevaluefromheaders(self._req, r'X-HgArg')
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
return args
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def getfile(self, fp):
Gregory Szorc
wireprotoserver: make attributes private...
r35884 length = int(self._req.env[r'CONTENT_LENGTH'])
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 # If httppostargs is used, we need to read Content-Length
# minus the amount that was consumed by args.
Gregory Szorc
wireprotoserver: make attributes private...
r35884 length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0))
for s in util.filechunkiter(self._req, limit=length):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 fp.write(s)
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def redirect(self):
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._oldio = self._ui.fout, self._ui.ferr
self._ui.ferr = self._ui.fout = stringio()
Gregory Szorc
wireprotoserver: add some blank lines between methods...
r35881
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def restore(self):
Gregory Szorc
wireprotoserver: make attributes private...
r35884 val = self._ui.fout.getvalue()
self._ui.ferr, self._ui.fout = self._oldio
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return val
def _client(self):
return 'remote:%s:%s:%s' % (
Gregory Szorc
wireprotoserver: make attributes private...
r35884 self._req.env.get('wsgi.url_scheme') or 'http',
urlreq.quote(self._req.env.get('REMOTE_HOST', '')),
urlreq.quote(self._req.env.get('REMOTE_USER', '')))
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
def responsetype(self, prefer_uncompressed):
"""Determine the appropriate response type and compression settings.
Returns a tuple of (mediatype, compengine, engineopts).
"""
# Determine the response media type and compression engine based
# on the request parameters.
Gregory Szorc
wireprotoserver: make attributes private...
r35884 protocaps = decodevaluefromheaders(self._req, r'X-HgProto').split(' ')
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
if '0.2' in protocaps:
# All clients are expected to support uncompressed data.
if prefer_uncompressed:
return HGTYPE2, util._noopengine(), {}
# Default as defined by wire protocol spec.
compformats = ['zlib', 'none']
for cap in protocaps:
if cap.startswith('comp='):
compformats = cap[5:].split(',')
break
# Now find an agreed upon compression format.
Gregory Szorc
wireprotoserver: make attributes private...
r35884 for engine in wireproto.supportedcompengines(self._ui, self,
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 util.SERVERROLE):
if engine.wireprotosupport().name in compformats:
opts = {}
Gregory Szorc
wireprotoserver: make attributes private...
r35884 level = self._ui.configint('server',
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 '%slevel' % engine.name())
if level is not None:
opts['level'] = level
return HGTYPE2, engine, opts
# No mutually supported compression format. Fall back to the
# legacy protocol.
# Don't allow untrusted settings because disabling compression or
# setting a very high compression level could lead to flooding
# the server's network or CPU.
Gregory Szorc
wireprotoserver: make attributes private...
r35884 opts = {'level': self._ui.configint('server', 'zliblevel')}
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 return HGTYPE, util.compengines['zlib'], opts
def iscmd(cmd):
return cmd in wireproto.commands
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 def parsehttprequest(repo, req, query):
"""Parse the HTTP request for a wire protocol request.
If the current request appears to be a wire protocol request, this
function returns a dict with details about that request, including
an ``abstractprotocolserver`` instance suitable for handling the
request. Otherwise, ``None`` is returned.
``req`` is a ``wsgirequest`` instance.
"""
# HTTP version 1 wire protocol requests are denoted by a "cmd" query
# string parameter. If it isn't present, this isn't a wire protocol
# request.
if r'cmd' not in req.form:
return None
cmd = pycompat.sysbytes(req.form[r'cmd'][0])
# The "cmd" request parameter is used by both the wire protocol and hgweb.
# While not all wire protocol commands are available for all transports,
# if we see a "cmd" value that resembles a known wire protocol command, we
# route it to a protocol handler. This is better than routing possible
# wire protocol requests to hgweb because it prevents hgweb from using
# known wire protocol commands and it is less confusing for machine
# clients.
if cmd not in wireproto.commands:
return None
Gregory Szorc
wireprotoserver: rename p to proto...
r35882 proto = webproto(req, repo.ui)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 return {
'cmd': cmd,
'proto': proto,
'dispatch': lambda: _callhttp(repo, req, proto, cmd),
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 'handleerror': lambda ex: _handlehttperror(ex, req, cmd),
Gregory Szorc
wireprotoserver: move protocol parsing and dispatch out of hgweb...
r36002 }
def _callhttp(repo, req, proto, cmd):
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 def genversion2(gen, engine, engineopts):
# application/mercurial-0.2 always sends a payload header
# identifying the compression engine.
name = engine.wireprotosupport().name
assert 0 < len(name) < 256
yield struct.pack('B', len(name))
yield name
for chunk in gen:
yield chunk
Gregory Szorc
wireprotoserver: rename p to proto...
r35882 rsp = wireproto.dispatch(repo, proto, cmd)
Gregory Szorc
wireproto: function for testing if wire protocol command is available...
r36000
if not wireproto.commands.commandavailable(cmd, proto):
req.respond(HTTP_OK, HGERRTYPE,
body=_('requested wire protocol command is not available '
'over HTTP'))
return []
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 if isinstance(rsp, bytes):
req.respond(HTTP_OK, HGTYPE, body=rsp)
return []
elif isinstance(rsp, wireproto.streamres_legacy):
gen = rsp.gen
req.respond(HTTP_OK, HGTYPE)
return gen
elif isinstance(rsp, wireproto.streamres):
gen = rsp.gen
# This code for compression should not be streamres specific. It
# is here because we only compress streamres at the moment.
Gregory Szorc
wireprotoserver: rename p to proto...
r35882 mediatype, engine, engineopts = proto.responsetype(
rsp.prefer_uncompressed)
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 gen = engine.compressstream(gen, engineopts)
if mediatype == HGTYPE2:
gen = genversion2(gen, engine, engineopts)
req.respond(HTTP_OK, mediatype)
return gen
elif isinstance(rsp, wireproto.pushres):
Gregory Szorc
wireprotoserver: rename p to proto...
r35882 val = proto.restore()
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 rsp = '%d\n%s' % (rsp.res, val)
req.respond(HTTP_OK, HGTYPE, body=rsp)
return []
elif isinstance(rsp, wireproto.pusherr):
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # This is the httplib workaround documented in _handlehttperror().
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 req.drain()
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005
Gregory Szorc
wireprotoserver: rename p to proto...
r35882 proto.restore()
Gregory Szorc
wireprotoserver: rename hgweb.protocol to wireprotoserver (API)...
r35874 rsp = '0\n%s\n' % rsp.res
req.respond(HTTP_OK, HGTYPE, body=rsp)
return []
elif isinstance(rsp, wireproto.ooberror):
rsp = rsp.message
req.respond(HTTP_OK, HGERRTYPE, body=rsp)
return []
raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 def _handlehttperror(e, req, cmd):
"""Called when an ErrorResponse is raised during HTTP request processing."""
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005
# Clients using Python's httplib are stateful: the HTTP client
# won't process an HTTP response until all request data is
# sent to the server. The intent of this code is to ensure
# we always read HTTP request data from the client, thus
# ensuring httplib transitions to a state that allows it to read
# the HTTP response. In other words, it helps prevent deadlocks
# on clients using httplib.
if (req.env[r'REQUEST_METHOD'] == r'POST' and
# But not if Expect: 100-continue is being used.
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 (req.env.get('HTTP_EXPECT',
'').lower() != '100-continue') or
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # Or the non-httplib HTTP library is being advertised by
# the client.
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 req.env.get('X-HgHttp2', '')):
req.drain()
else:
req.headers.append((r'Connection', r'Close'))
Gregory Szorc
wireprotoserver: document and improve the httplib workaround...
r36005 # TODO This response body assumes the failed command was
# "unbundle." That assumption is not always valid.
Gregory Szorc
wireprotoserver: move error response handling out of hgweb...
r36004 req.respond(e, HGTYPE, body='0\n%s\n' % e)
return ''
Gregory Szorc
wireprotoserver: rename abstractserverproto and improve docstring...
r36006 class sshserver(baseprotocolhandler):
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 def __init__(self, ui, repo):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 self._ui = ui
self._repo = repo
self._fin = ui.fin
self._fout = ui.fout
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
hook.redirect(True)
ui.fout = repo.ui.fout = ui.ferr
# Prevent insertion/deletion of CRs
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 util.setbinary(self._fin)
util.setbinary(self._fout)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: make name part of protocol interface...
r35891 @property
def name(self):
return 'ssh'
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 def getargs(self, args):
data = {}
keys = args.split()
for n in xrange(len(keys)):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 argline = self._fin.readline()[:-1]
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 arg, l = argline.split()
if arg not in keys:
raise error.Abort(_("unexpected parameter %r") % arg)
if arg == '*':
star = {}
for k in xrange(int(l)):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 argline = self._fin.readline()[:-1]
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 arg, l = argline.split()
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 val = self._fin.read(int(l))
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 star[arg] = val
data['*'] = star
else:
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 val = self._fin.read(int(l))
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 data[arg] = val
return [data[k] for k in keys]
def getfile(self, fpout):
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 self._sendresponse('')
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 count = int(self._fin.readline())
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 while count:
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 fpout.write(self._fin.read(count))
count = int(self._fin.readline())
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
def redirect(self):
pass
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 def _sendresponse(self, v):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 self._fout.write("%d\n" % len(v))
self._fout.write(v)
self._fout.flush()
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 def _sendstream(self, source):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 write = self._fout.write
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 for chunk in source.gen:
write(chunk)
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 self._fout.flush()
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 def _sendpushresponse(self, rsp):
self._sendresponse('')
self._sendresponse(str(rsp.res))
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 def _sendpusherror(self, rsp):
self._sendresponse(rsp.res)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 def _sendooberror(self, rsp):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 self._ui.ferr.write('%s\n-\n' % rsp.message)
self._ui.ferr.flush()
self._fout.write('\n')
self._fout.flush()
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877
def serve_forever(self):
Gregory Szorc
wireprotoserver: remove lock references...
r35886 while self.serve_one():
pass
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 sys.exit(0)
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 _handlers = {
str: _sendresponse,
wireproto.streamres: _sendstream,
wireproto.streamres_legacy: _sendstream,
wireproto.pushres: _sendpushresponse,
wireproto.pusherr: _sendpusherror,
wireproto.ooberror: _sendooberror,
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 }
def serve_one(self):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 cmd = self._fin.readline()[:-1]
Gregory Szorc
wireproto: function for testing if wire protocol command is available...
r36000 if cmd and wireproto.commands.commandavailable(cmd, self):
Gregory Szorc
wireprotoserver: make some instance attributes private...
r35888 rsp = wireproto.dispatch(self._repo, self, cmd)
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 self._handlers[rsp.__class__](self, rsp)
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 elif cmd:
Gregory Szorc
wireprotoserver: make response handling attributes private...
r35889 self._sendresponse("")
Gregory Szorc
wireprotoserver: move sshserver into module (API)...
r35877 return cmd != ''
def _client(self):
client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
return 'remote:ssh:' + client