wireprototypes.py
227 lines
| 6.5 KiB
| text/x-python
|
PythonLexer
/ mercurial / wireprototypes.py
|
r36090 | # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.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 | ||||
|
r37630 | from .node import ( | ||
bin, | ||||
hex, | ||||
) | ||||
|
r37312 | from .thirdparty.zope import ( | ||
interface as zi, | ||||
) | ||||
|
r36389 | |||
|
r36553 | # Names of the SSH protocol implementations. | ||
SSHV1 = 'ssh-v1' | ||||
|
r37064 | # These are advertised over the wire. Increment the counters at the end | ||
|
r36553 | # to reflect BC breakages. | ||
SSHV2 = 'exp-ssh-v2-0001' | ||||
|
r37064 | HTTPV2 = 'exp-http-v2-0001' | ||
|
r36553 | |||
|
r36627 | # All available wire protocol transports. | ||
TRANSPORTS = { | ||||
SSHV1: { | ||||
'transport': 'ssh', | ||||
'version': 1, | ||||
}, | ||||
SSHV2: { | ||||
'transport': 'ssh', | ||||
|
r37310 | # TODO mark as version 2 once all commands are implemented. | ||
'version': 1, | ||||
|
r36627 | }, | ||
'http-v1': { | ||||
'transport': 'http', | ||||
'version': 1, | ||||
|
r37064 | }, | ||
HTTPV2: { | ||||
'transport': 'http', | ||||
'version': 2, | ||||
|
r36627 | } | ||
} | ||||
|
r36091 | class bytesresponse(object): | ||
"""A wire protocol response consisting of raw bytes.""" | ||||
def __init__(self, data): | ||||
self.data = data | ||||
|
r36090 | class ooberror(object): | ||
"""wireproto reply: failure of a batch of operation | ||||
Something failed during a batch call. The error message is stored in | ||||
`self.message`. | ||||
""" | ||||
def __init__(self, message): | ||||
self.message = message | ||||
class pushres(object): | ||||
"""wireproto reply: success with simple integer return | ||||
The call was successful and returned an integer contained in `self.res`. | ||||
""" | ||||
def __init__(self, res, output): | ||||
self.res = res | ||||
self.output = output | ||||
class pusherr(object): | ||||
"""wireproto reply: failure | ||||
The call failed. The `self.res` attribute contains the error message. | ||||
""" | ||||
def __init__(self, res, output): | ||||
self.res = res | ||||
self.output = output | ||||
class streamres(object): | ||||
"""wireproto reply: binary stream | ||||
The call was successful and the result is a stream. | ||||
Accepts a generator containing chunks of data to be sent to the client. | ||||
``prefer_uncompressed`` indicates that the data is expected to be | ||||
uncompressable and that the stream should therefore use the ``none`` | ||||
engine. | ||||
""" | ||||
def __init__(self, gen=None, prefer_uncompressed=False): | ||||
self.gen = gen | ||||
self.prefer_uncompressed = prefer_uncompressed | ||||
class streamreslegacy(object): | ||||
"""wireproto reply: uncompressed binary stream | ||||
The call was successful and the result is a stream. | ||||
Accepts a generator containing chunks of data to be sent to the client. | ||||
Like ``streamres``, but sends an uncompressed data for "version 1" clients | ||||
using the application/mercurial-0.1 media type. | ||||
""" | ||||
def __init__(self, gen=None): | ||||
self.gen = gen | ||||
|
r36389 | |||
|
r37503 | class cborresponse(object): | ||
"""Encode the response value as CBOR.""" | ||||
def __init__(self, v): | ||||
self.value = v | ||||
|
r37630 | # list of nodes encoding / decoding | ||
def decodelist(l, sep=' '): | ||||
if l: | ||||
return [bin(v) for v in l.split(sep)] | ||||
return [] | ||||
def encodelist(l, sep=' '): | ||||
try: | ||||
return sep.join(map(hex, l)) | ||||
except TypeError: | ||||
raise | ||||
# batched call argument encoding | ||||
def escapebatcharg(plain): | ||||
return (plain | ||||
.replace(':', ':c') | ||||
.replace(',', ':o') | ||||
.replace(';', ':s') | ||||
.replace('=', ':e')) | ||||
def unescapebatcharg(escaped): | ||||
return (escaped | ||||
.replace(':e', '=') | ||||
.replace(':s', ';') | ||||
.replace(':o', ',') | ||||
.replace(':c', ':')) | ||||
|
r37631 | # mapping of options accepted by getbundle and their types | ||
# | ||||
# Meant to be extended by extensions. It is extensions responsibility to ensure | ||||
# such options are properly processed in exchange.getbundle. | ||||
# | ||||
# supported types are: | ||||
# | ||||
# :nodes: list of binary nodes | ||||
# :csv: list of comma-separated values | ||||
# :scsv: list of comma-separated values return as set | ||||
# :plain: string with no transformation needed. | ||||
GETBUNDLE_ARGUMENTS = { | ||||
'heads': 'nodes', | ||||
'bookmarks': 'boolean', | ||||
'common': 'nodes', | ||||
'obsmarkers': 'boolean', | ||||
'phases': 'boolean', | ||||
'bundlecaps': 'scsv', | ||||
'listkeys': 'csv', | ||||
'cg': 'boolean', | ||||
'cbattempted': 'boolean', | ||||
'stream': 'boolean', | ||||
} | ||||
|
r37312 | class baseprotocolhandler(zi.Interface): | ||
|
r36389 | """Abstract base class for wire protocol handlers. | ||
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. | ||||
""" | ||||
|
r37312 | name = zi.Attribute( | ||
|
r36389 | """The name of the protocol implementation. | ||
Used for uniquely identifying the transport type. | ||||
|
r37312 | """) | ||
|
r36389 | |||
|
r37312 | def getargs(args): | ||
|
r36389 | """return the value for arguments in <args> | ||
|
r37503 | For version 1 transports, returns a list of values in the same | ||
order they appear in ``args``. For version 2 transports, returns | ||||
a dict mapping argument name to value. | ||||
""" | ||||
|
r36389 | |||
|
r37411 | def getprotocaps(): | ||
"""Returns the list of protocol-level capabilities of client | ||||
Returns a list of capabilities as declared by the client for | ||||
the current request (or connection for stateful protocol handlers).""" | ||||
|
r37432 | def getpayload(): | ||
"""Provide a generator for the raw payload. | ||||
|
r36389 | |||
|
r37432 | The caller is responsible for ensuring that the full payload is | ||
processed. | ||||
|
r36389 | """ | ||
|
r37312 | def mayberedirectstdio(): | ||
|
r36389 | """Context manager to possibly redirect stdio. | ||
The context manager yields a file-object like object that receives | ||||
stdout and stderr output when the context manager is active. Or it | ||||
yields ``None`` if no I/O redirection occurs. | ||||
The intent of this context manager is to capture stdio output | ||||
so it may be sent in the response. Some transports support streaming | ||||
stdio to the client in real time. For these transports, stdio output | ||||
won't be captured. | ||||
""" | ||||
|
r37312 | def client(): | ||
|
r36389 | """Returns a string representation of this client (as bytes).""" | ||
|
r36631 | |||
|
r37312 | def addcapabilities(repo, caps): | ||
|
r36631 | """Adds advertised capabilities specific to this protocol. | ||
Receives the list of capabilities collected so far. | ||||
Returns a list of capabilities. The passed in argument can be returned. | ||||
""" | ||||
|
r36819 | |||
|
r37312 | def checkperm(perm): | ||
|
r36819 | """Validate that the client has permissions to perform a request. | ||
The argument is the permission required to proceed. If the client | ||||
doesn't have that permission, the exception should raise or abort | ||||
in a protocol specific manner. | ||||
""" | ||||