import calendar import datetime import struct import typing import uuid import zlib from llsd import * # So we can directly reference the original wrapper funcs where necessary import llsd as base_llsd from llsd.base import is_string, is_unicode from hippolyzer.lib.base.datatypes import * class HippoLLSDBaseFormatter(base_llsd.base.LLSDBaseFormatter): UUID: callable ARRAY: callable BINARY: callable def __init__(self): super().__init__() self.type_map[UUID] = self.UUID self.type_map[JankStringyBytes] = self.BINARY self.type_map[Vector2] = self.TUPLECOORD self.type_map[Vector3] = self.TUPLECOORD self.type_map[Vector4] = self.TUPLECOORD self.type_map[Quaternion] = self.TUPLECOORD def TUPLECOORD(self, v: TupleCoord): return self.ARRAY(v.data()) class HippoLLSDXMLFormatter(base_llsd.serde_xml.LLSDXMLFormatter, HippoLLSDBaseFormatter): def __init__(self): super().__init__() class HippoLLSDXMLPrettyFormatter(base_llsd.serde_xml.LLSDXMLPrettyFormatter, HippoLLSDBaseFormatter): def __init__(self): super().__init__() def format_pretty_xml(val: typing.Any) -> bytes: return HippoLLSDXMLPrettyFormatter().format(val) def format_xml(val: typing.Any) -> bytes: return HippoLLSDXMLFormatter().format(val) class HippoLLSDNotationFormatter(base_llsd.serde_notation.LLSDNotationFormatter, HippoLLSDBaseFormatter): def __init__(self): super().__init__() def STRING(self, v): # llbase's notation LLSD encoder isn't suitable for generating line-delimited # LLSD because the string formatter leaves \n unencoded, unlike indra's llcommon. # Add our own escaping rule. return super().STRING(v).replace(b"\n", b"\\n") def format_notation(val: typing.Any) -> bytes: return HippoLLSDNotationFormatter().format(val) def format_binary(val: typing.Any, with_header=True) -> bytes: val = _format_binary_recurse(val) if with_header: return b'\n' + val return val # This is copied almost wholesale from https://bitbucket.org/lindenlab/llbase/src/master/llbase/llsd.py # With a few minor changes to make serialization round-trip correctly. It's evil. def _format_binary_recurse(something) -> bytes: """Binary formatter workhorse.""" def _format_list(list_something): array_builder = [b'[' + struct.pack('!i', len(list_something))] for item in list_something: array_builder.append(_format_binary_recurse(item)) array_builder.append(b']') return b''.join(array_builder) if something is None: return b'!' elif isinstance(something, LLSD): return _format_binary_recurse(something.thing) elif isinstance(something, bool): if something: return b'1' else: return b'0' elif isinstance(something, int): try: return b'i' + struct.pack('!i', something) except (OverflowError, struct.error) as exc: raise LLSDSerializationError(str(exc), something) elif isinstance(something, float): try: return b'r' + struct.pack('!d', something) except SystemError as exc: raise LLSDSerializationError(str(exc), something) elif isinstance(something, uuid.UUID): return b'u' + something.bytes elif isinstance(something, (binary, JankStringyBytes)): return b'b' + struct.pack('!i', len(something)) + something elif is_string(something): if is_unicode(something): something = something.encode("utf8") return b's' + struct.pack('!i', len(something)) + something elif isinstance(something, uri): return b'l' + struct.pack('!i', len(something)) + something.encode("utf8") elif isinstance(something, datetime.datetime): return b'd' + struct.pack('', b'') def parse_binary(data: bytes): if any(data.startswith(x) for x in _BINARY_HEADERS): data = data.split(b'\n', 1)[1] return HippoLLSDBinaryParser().parse(data) def parse_xml(data: bytes): return base_llsd.parse_xml(data) def parse_notation(data: bytes): return base_llsd.parse_notation(data) def zip_llsd(val: typing.Any): return zlib.compress(format_binary(val, with_header=False), level=zlib.Z_BEST_COMPRESSION) def unzip_llsd(data: bytes): return parse_binary(zlib.decompress(data)) def parse(data: bytes): # You always have to content-type sniff because the provided # content-type is usually nonsense. try: data = data.lstrip() if any(data.startswith(x) for x in _BINARY_HEADERS): return parse_binary(data) elif data.startswith(b'<'): return parse_xml(data) else: return parse_notation(data) except KeyError as e: raise base_llsd.LLSDParseError('LLSD could not be parsed: %s' % (e,)) except TypeError as e: raise base_llsd.LLSDParseError('Input stream not of type bytes. %s' % (e,))