RAHHH
This commit is contained in:
403
backend/lib/python3.12/site-packages/twisted/spread/banana.py
Normal file
403
backend/lib/python3.12/site-packages/twisted/spread/banana.py
Normal file
@@ -0,0 +1,403 @@
|
||||
# -*- test-case-name: twisted.spread.test.test_banana -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Banana -- s-exp based protocol.
|
||||
|
||||
Future Plans: This module is almost entirely stable. The same caveat applies
|
||||
to it as applies to L{twisted.spread.jelly}, however. Read its future plans
|
||||
for more details.
|
||||
|
||||
@author: Glyph Lefkowitz
|
||||
"""
|
||||
|
||||
|
||||
import copy
|
||||
import struct
|
||||
from io import BytesIO
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.persisted import styles
|
||||
from twisted.python import log
|
||||
from twisted.python.compat import iterbytes
|
||||
from twisted.python.reflect import fullyQualifiedName
|
||||
|
||||
|
||||
class BananaError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def int2b128(integer, stream):
|
||||
if integer == 0:
|
||||
stream(b"\0")
|
||||
return
|
||||
assert integer > 0, "can only encode positive integers"
|
||||
while integer:
|
||||
stream(bytes((integer & 0x7F,)))
|
||||
integer = integer >> 7
|
||||
|
||||
|
||||
def b1282int(st):
|
||||
"""
|
||||
Convert an integer represented as a base 128 string into an L{int}.
|
||||
|
||||
@param st: The integer encoded in a byte string.
|
||||
@type st: L{bytes}
|
||||
|
||||
@return: The integer value extracted from the byte string.
|
||||
@rtype: L{int}
|
||||
"""
|
||||
e = 1
|
||||
i = 0
|
||||
for char in iterbytes(st):
|
||||
n = ord(char)
|
||||
i += n * e
|
||||
e <<= 7
|
||||
return i
|
||||
|
||||
|
||||
# delimiter characters.
|
||||
LIST = b"\x80"
|
||||
INT = b"\x81"
|
||||
STRING = b"\x82"
|
||||
NEG = b"\x83"
|
||||
FLOAT = b"\x84"
|
||||
# "optional" -- these might be refused by a low-level implementation.
|
||||
LONGINT = b"\x85"
|
||||
LONGNEG = b"\x86"
|
||||
# really optional; this is part of the 'pb' vocabulary
|
||||
VOCAB = b"\x87"
|
||||
|
||||
HIGH_BIT_SET = b"\x80"
|
||||
|
||||
|
||||
def setPrefixLimit(limit):
|
||||
"""
|
||||
Set the limit on the prefix length for all Banana connections
|
||||
established after this call.
|
||||
|
||||
The prefix length limit determines how many bytes of prefix a banana
|
||||
decoder will allow before rejecting a potential object as too large.
|
||||
|
||||
@type limit: L{int}
|
||||
@param limit: The number of bytes of prefix for banana to allow when
|
||||
decoding.
|
||||
"""
|
||||
global _PREFIX_LIMIT
|
||||
_PREFIX_LIMIT = limit
|
||||
|
||||
|
||||
_PREFIX_LIMIT = None
|
||||
setPrefixLimit(64)
|
||||
|
||||
SIZE_LIMIT = 640 * 1024 # 640k is all you'll ever need :-)
|
||||
|
||||
|
||||
class Banana(protocol.Protocol, styles.Ephemeral):
|
||||
"""
|
||||
L{Banana} implements the I{Banana} s-expression protocol, client and
|
||||
server.
|
||||
|
||||
@ivar knownDialects: These are the profiles supported by this Banana
|
||||
implementation.
|
||||
@type knownDialects: L{list} of L{bytes}
|
||||
"""
|
||||
|
||||
# The specification calls these profiles but this implementation calls them
|
||||
# dialects instead.
|
||||
knownDialects = [b"pb", b"none"]
|
||||
|
||||
prefixLimit = None
|
||||
sizeLimit = SIZE_LIMIT
|
||||
|
||||
def setPrefixLimit(self, limit):
|
||||
"""
|
||||
Set the prefix limit for decoding done by this protocol instance.
|
||||
|
||||
@see: L{setPrefixLimit}
|
||||
"""
|
||||
self.prefixLimit = limit
|
||||
self._smallestLongInt = -(2 ** (limit * 7)) + 1
|
||||
self._smallestInt = -(2**31)
|
||||
self._largestInt = 2**31 - 1
|
||||
self._largestLongInt = 2 ** (limit * 7) - 1
|
||||
|
||||
def connectionReady(self):
|
||||
"""Surrogate for connectionMade
|
||||
Called after protocol negotiation.
|
||||
"""
|
||||
|
||||
def _selectDialect(self, dialect):
|
||||
self.currentDialect = dialect
|
||||
self.connectionReady()
|
||||
|
||||
def callExpressionReceived(self, obj):
|
||||
if self.currentDialect:
|
||||
self.expressionReceived(obj)
|
||||
else:
|
||||
# this is the first message we've received
|
||||
if self.isClient:
|
||||
# if I'm a client I have to respond
|
||||
for serverVer in obj:
|
||||
if serverVer in self.knownDialects:
|
||||
self.sendEncoded(serverVer)
|
||||
self._selectDialect(serverVer)
|
||||
break
|
||||
else:
|
||||
# I can't speak any of those dialects.
|
||||
log.msg(
|
||||
"The client doesn't speak any of the protocols "
|
||||
"offered by the server: disconnecting."
|
||||
)
|
||||
self.transport.loseConnection()
|
||||
else:
|
||||
if obj in self.knownDialects:
|
||||
self._selectDialect(obj)
|
||||
else:
|
||||
# the client just selected a protocol that I did not suggest.
|
||||
log.msg(
|
||||
"The client selected a protocol the server didn't "
|
||||
"suggest and doesn't know: disconnecting."
|
||||
)
|
||||
self.transport.loseConnection()
|
||||
|
||||
def connectionMade(self):
|
||||
self.setPrefixLimit(_PREFIX_LIMIT)
|
||||
self.currentDialect = None
|
||||
if not self.isClient:
|
||||
self.sendEncoded(self.knownDialects)
|
||||
|
||||
def gotItem(self, item):
|
||||
l = self.listStack
|
||||
if l:
|
||||
l[-1][1].append(item)
|
||||
else:
|
||||
self.callExpressionReceived(item)
|
||||
|
||||
buffer = b""
|
||||
|
||||
def dataReceived(self, chunk):
|
||||
buffer = self.buffer + chunk
|
||||
listStack = self.listStack
|
||||
gotItem = self.gotItem
|
||||
while buffer:
|
||||
assert self.buffer != buffer, "This ain't right: {} {}".format(
|
||||
repr(self.buffer),
|
||||
repr(buffer),
|
||||
)
|
||||
self.buffer = buffer
|
||||
pos = 0
|
||||
for ch in iterbytes(buffer):
|
||||
if ch >= HIGH_BIT_SET:
|
||||
break
|
||||
pos = pos + 1
|
||||
else:
|
||||
if pos > self.prefixLimit:
|
||||
raise BananaError(
|
||||
"Security precaution: more than %d bytes of prefix"
|
||||
% (self.prefixLimit,)
|
||||
)
|
||||
return
|
||||
num = buffer[:pos]
|
||||
typebyte = buffer[pos : pos + 1]
|
||||
rest = buffer[pos + 1 :]
|
||||
if len(num) > self.prefixLimit:
|
||||
raise BananaError(
|
||||
"Security precaution: longer than %d bytes worth of prefix"
|
||||
% (self.prefixLimit,)
|
||||
)
|
||||
if typebyte == LIST:
|
||||
num = b1282int(num)
|
||||
if num > SIZE_LIMIT:
|
||||
raise BananaError("Security precaution: List too long.")
|
||||
listStack.append((num, []))
|
||||
buffer = rest
|
||||
elif typebyte == STRING:
|
||||
num = b1282int(num)
|
||||
if num > SIZE_LIMIT:
|
||||
raise BananaError("Security precaution: String too long.")
|
||||
if len(rest) >= num:
|
||||
buffer = rest[num:]
|
||||
gotItem(rest[:num])
|
||||
else:
|
||||
return
|
||||
elif typebyte == INT:
|
||||
buffer = rest
|
||||
num = b1282int(num)
|
||||
gotItem(num)
|
||||
elif typebyte == LONGINT:
|
||||
buffer = rest
|
||||
num = b1282int(num)
|
||||
gotItem(num)
|
||||
elif typebyte == LONGNEG:
|
||||
buffer = rest
|
||||
num = b1282int(num)
|
||||
gotItem(-num)
|
||||
elif typebyte == NEG:
|
||||
buffer = rest
|
||||
num = -b1282int(num)
|
||||
gotItem(num)
|
||||
elif typebyte == VOCAB:
|
||||
buffer = rest
|
||||
num = b1282int(num)
|
||||
item = self.incomingVocabulary[num]
|
||||
if self.currentDialect == b"pb":
|
||||
# the sender issues VOCAB only for dialect pb
|
||||
gotItem(item)
|
||||
else:
|
||||
raise NotImplementedError(f"Invalid item for pb protocol {item!r}")
|
||||
elif typebyte == FLOAT:
|
||||
if len(rest) >= 8:
|
||||
buffer = rest[8:]
|
||||
gotItem(struct.unpack("!d", rest[:8])[0])
|
||||
else:
|
||||
return
|
||||
else:
|
||||
raise NotImplementedError(f"Invalid Type Byte {typebyte!r}")
|
||||
while listStack and (len(listStack[-1][1]) == listStack[-1][0]):
|
||||
item = listStack.pop()[1]
|
||||
gotItem(item)
|
||||
self.buffer = b""
|
||||
|
||||
def expressionReceived(self, lst):
|
||||
"""Called when an expression (list, string, or int) is received."""
|
||||
raise NotImplementedError()
|
||||
|
||||
outgoingVocabulary = {
|
||||
# Jelly Data Types
|
||||
b"None": 1,
|
||||
b"class": 2,
|
||||
b"dereference": 3,
|
||||
b"reference": 4,
|
||||
b"dictionary": 5,
|
||||
b"function": 6,
|
||||
b"instance": 7,
|
||||
b"list": 8,
|
||||
b"module": 9,
|
||||
b"persistent": 10,
|
||||
b"tuple": 11,
|
||||
b"unpersistable": 12,
|
||||
# PB Data Types
|
||||
b"copy": 13,
|
||||
b"cache": 14,
|
||||
b"cached": 15,
|
||||
b"remote": 16,
|
||||
b"local": 17,
|
||||
b"lcache": 18,
|
||||
# PB Protocol Messages
|
||||
b"version": 19,
|
||||
b"login": 20,
|
||||
b"password": 21,
|
||||
b"challenge": 22,
|
||||
b"logged_in": 23,
|
||||
b"not_logged_in": 24,
|
||||
b"cachemessage": 25,
|
||||
b"message": 26,
|
||||
b"answer": 27,
|
||||
b"error": 28,
|
||||
b"decref": 29,
|
||||
b"decache": 30,
|
||||
b"uncache": 31,
|
||||
}
|
||||
|
||||
incomingVocabulary = {}
|
||||
for k, v in outgoingVocabulary.items():
|
||||
incomingVocabulary[v] = k
|
||||
|
||||
def __init__(self, isClient=1):
|
||||
self.listStack = []
|
||||
self.outgoingSymbols = copy.copy(self.outgoingVocabulary)
|
||||
self.outgoingSymbolCount = 0
|
||||
self.isClient = isClient
|
||||
|
||||
def sendEncoded(self, obj):
|
||||
"""
|
||||
Send the encoded representation of the given object:
|
||||
|
||||
@param obj: An object to encode and send.
|
||||
|
||||
@raise BananaError: If the given object is not an instance of one of
|
||||
the types supported by Banana.
|
||||
|
||||
@return: L{None}
|
||||
"""
|
||||
encodeStream = BytesIO()
|
||||
self._encode(obj, encodeStream.write)
|
||||
value = encodeStream.getvalue()
|
||||
self.transport.write(value)
|
||||
|
||||
def _encode(self, obj, write):
|
||||
if isinstance(obj, (list, tuple)):
|
||||
if len(obj) > SIZE_LIMIT:
|
||||
raise BananaError("list/tuple is too long to send (%d)" % (len(obj),))
|
||||
int2b128(len(obj), write)
|
||||
write(LIST)
|
||||
for elem in obj:
|
||||
self._encode(elem, write)
|
||||
elif isinstance(obj, int):
|
||||
if obj < self._smallestLongInt or obj > self._largestLongInt:
|
||||
raise BananaError("int is too large to send (%d)" % (obj,))
|
||||
if obj < self._smallestInt:
|
||||
int2b128(-obj, write)
|
||||
write(LONGNEG)
|
||||
elif obj < 0:
|
||||
int2b128(-obj, write)
|
||||
write(NEG)
|
||||
elif obj <= self._largestInt:
|
||||
int2b128(obj, write)
|
||||
write(INT)
|
||||
else:
|
||||
int2b128(obj, write)
|
||||
write(LONGINT)
|
||||
elif isinstance(obj, float):
|
||||
write(FLOAT)
|
||||
write(struct.pack("!d", obj))
|
||||
elif isinstance(obj, bytes):
|
||||
# TODO: an API for extending banana...
|
||||
if self.currentDialect == b"pb" and obj in self.outgoingSymbols:
|
||||
symbolID = self.outgoingSymbols[obj]
|
||||
int2b128(symbolID, write)
|
||||
write(VOCAB)
|
||||
else:
|
||||
if len(obj) > SIZE_LIMIT:
|
||||
raise BananaError(
|
||||
"byte string is too long to send (%d)" % (len(obj),)
|
||||
)
|
||||
int2b128(len(obj), write)
|
||||
write(STRING)
|
||||
write(obj)
|
||||
else:
|
||||
raise BananaError(
|
||||
"Banana cannot send {} objects: {!r}".format(
|
||||
fullyQualifiedName(type(obj)), obj
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# For use from the interactive interpreter
|
||||
_i = Banana()
|
||||
_i.connectionMade()
|
||||
_i._selectDialect(b"none")
|
||||
|
||||
|
||||
def encode(lst):
|
||||
"""Encode a list s-expression."""
|
||||
encodeStream = BytesIO()
|
||||
_i.transport = encodeStream
|
||||
_i.sendEncoded(lst)
|
||||
return encodeStream.getvalue()
|
||||
|
||||
|
||||
def decode(st):
|
||||
"""
|
||||
Decode a banana-encoded string.
|
||||
"""
|
||||
l = []
|
||||
_i.expressionReceived = l.append
|
||||
try:
|
||||
_i.dataReceived(st)
|
||||
finally:
|
||||
_i.buffer = b""
|
||||
del _i.expressionReceived
|
||||
return l[0]
|
||||
Reference in New Issue
Block a user