diff options
| -rwxr-xr-x | indra/viewer_components/manager/SL_Launcher | 3 | ||||
| -rwxr-xr-x | indra/viewer_components/manager/llsd.py | 1052 | 
2 files changed, 1055 insertions, 0 deletions
| diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher index 1d4c19fa86..a96f2392a7 100755 --- a/indra/viewer_components/manager/SL_Launcher +++ b/indra/viewer_components/manager/SL_Launcher @@ -20,6 +20,9 @@  import argparse  import collections  import InstallerUserMessage +#NOTA BENE:  +#   For POSIX platforms, llsd.py will be imported from the same directory.   +#   For Windows, llsd.py will be compiled into the executable by pyinstaller  import llsd  import os  import platform diff --git a/indra/viewer_components/manager/llsd.py b/indra/viewer_components/manager/llsd.py new file mode 100755 index 0000000000..4527b115f9 --- /dev/null +++ b/indra/viewer_components/manager/llsd.py @@ -0,0 +1,1052 @@ +"""\ +@file llsd.py +@brief Types as well as parsing and formatting functions for handling LLSD. + +$LicenseInfo:firstyear=2006&license=mit$ + +Copyright (c) 2006-2009, Linden Research, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +$/LicenseInfo$ +""" + +import datetime +import base64 +import string +import struct +import time +import types +import re + +from indra.util.fastest_elementtree import ElementTreeError, fromstring +from indra.base import lluuid + +# cllsd.c in server/server-1.25 has memory leaks, +#   so disabling cllsd for now +#try: +#    import cllsd +#except ImportError: +#    cllsd = None +cllsd = None + +int_regex = re.compile(r"[-+]?\d+") +real_regex = re.compile(r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?") +alpha_regex = re.compile(r"[a-zA-Z]+") +date_regex = re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})T" +                        r"(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})" +                        r"(?P<second_float>(\.\d+)?)Z") +#date: d"YYYY-MM-DDTHH:MM:SS.FFFFFFZ" + +class LLSDParseError(Exception): +    pass + +class LLSDSerializationError(TypeError): +    pass + + +class binary(str): +    pass + +class uri(str): +    pass + + +BOOL_TRUE = ('1', '1.0', 'true') +BOOL_FALSE = ('0', '0.0', 'false', '') + + +def format_datestr(v): +    """ Formats a datetime or date object into the string format shared by xml and notation serializations.""" +    if hasattr(v, 'microsecond'): +        return v.isoformat() + 'Z' +    else: +        return v.strftime('%Y-%m-%dT%H:%M:%SZ') + +def parse_datestr(datestr): +    """Parses a datetime object from the string format shared by xml and notation serializations.""" +    if datestr == "": +        return datetime.datetime(1970, 1, 1) +     +    match = re.match(date_regex, datestr) +    if not match: +        raise LLSDParseError("invalid date string '%s'." % datestr) +     +    year = int(match.group('year')) +    month = int(match.group('month')) +    day = int(match.group('day')) +    hour = int(match.group('hour')) +    minute = int(match.group('minute')) +    second = int(match.group('second')) +    seconds_float = match.group('second_float') +    microsecond = 0 +    if seconds_float: +        microsecond = int(float('0' + seconds_float) * 1e6) +    return datetime.datetime(year, month, day, hour, minute, second, microsecond) + + +def bool_to_python(node): +    val = node.text or '' +    if val in BOOL_TRUE: +        return True +    else: +        return False + +def int_to_python(node): +    val = node.text or '' +    if not val.strip(): +        return 0 +    return int(val) + +def real_to_python(node): +    val = node.text or '' +    if not val.strip(): +        return 0.0 +    return float(val) + +def uuid_to_python(node): +    return lluuid.UUID(node.text) + +def str_to_python(node): +    return node.text or '' + +def bin_to_python(node): +    return binary(base64.decodestring(node.text or '')) + +def date_to_python(node): +    val = node.text or '' +    if not val: +        val = "1970-01-01T00:00:00Z" +    return parse_datestr(val) +     + +def uri_to_python(node): +    val = node.text or '' +    if not val: +        return None +    return uri(val) + +def map_to_python(node): +    result = {} +    for index in range(len(node))[::2]: +        result[node[index].text] = to_python(node[index+1]) +    return result + +def array_to_python(node): +    return [to_python(child) for child in node] + + +NODE_HANDLERS = dict( +    undef=lambda x: None, +    boolean=bool_to_python, +    integer=int_to_python, +    real=real_to_python, +    uuid=uuid_to_python, +    string=str_to_python, +    binary=bin_to_python, +    date=date_to_python, +    uri=uri_to_python, +    map=map_to_python, +    array=array_to_python, +    ) + +def to_python(node): +    return NODE_HANDLERS[node.tag](node) + +class Nothing(object): +    pass + + +class LLSDXMLFormatter(object): +    def __init__(self): +        self.type_map = { +            type(None) : self.UNDEF, +            bool : self.BOOLEAN, +            int : self.INTEGER, +            long : self.INTEGER, +            float : self.REAL, +            lluuid.UUID : self.UUID, +            binary : self.BINARY, +            str : self.STRING, +            unicode : self.STRING, +            uri : self.URI, +            datetime.datetime : self.DATE, +            datetime.date : self.DATE, +            list : self.ARRAY, +            tuple : self.ARRAY, +            types.GeneratorType : self.ARRAY, +            dict : self.MAP, +            LLSD : self.LLSD +        } + +    def elt(self, name, contents=None): +        if(contents is None or contents is ''): +            return "<%s />" % (name,) +        else: +            if type(contents) is unicode: +                contents = contents.encode('utf-8') +            return "<%s>%s</%s>" % (name, contents, name) + +    def xml_esc(self, v): +        if type(v) is unicode: +            v = v.encode('utf-8') +        return v.replace('&', '&').replace('<', '<').replace('>', '>') + +    def LLSD(self, v): +        return self.generate(v.thing) +    def UNDEF(self, v): +        return self.elt('undef') +    def BOOLEAN(self, v): +        if v: +            return self.elt('boolean', 'true') +        else: +            return self.elt('boolean', 'false') +    def INTEGER(self, v): +        return self.elt('integer', v) +    def REAL(self, v): +        return self.elt('real', v) +    def UUID(self, v): +        if(v.isNull()): +            return self.elt('uuid') +        else: +            return self.elt('uuid', v) +    def BINARY(self, v): +        return self.elt('binary', base64.encodestring(v)) +    def STRING(self, v): +        return self.elt('string', self.xml_esc(v)) +    def URI(self, v): +        return self.elt('uri', self.xml_esc(str(v))) +    def DATE(self, v): +        return self.elt('date', format_datestr(v)) +    def ARRAY(self, v): +        return self.elt('array', ''.join([self.generate(item) for item in v])) +    def MAP(self, v): +        return self.elt( +            'map', +            ''.join(["%s%s" % (self.elt('key', self.xml_esc(str(key))), self.generate(value)) +             for key, value in v.items()])) + +    typeof = type +    def generate(self, something): +        t = self.typeof(something) +        if self.type_map.has_key(t): +            return self.type_map[t](something) +        else: +            raise LLSDSerializationError("Cannot serialize unknown type: %s (%s)" % ( +                t, something)) + +    def _format(self, something): +        return '<?xml version="1.0" ?>' + self.elt("llsd", self.generate(something)) + +    def format(self, something): +        if cllsd: +            return cllsd.llsd_to_xml(something) +        return self._format(something) + +_g_xml_formatter = None +def format_xml(something): +    global _g_xml_formatter +    if _g_xml_formatter is None: +        _g_xml_formatter = LLSDXMLFormatter() +    return _g_xml_formatter.format(something) + +class LLSDXMLPrettyFormatter(LLSDXMLFormatter): +    def __init__(self, indent_atom = None): +        # Call the super class constructor so that we have the type map +        super(LLSDXMLPrettyFormatter, self).__init__() + +        # Override the type map to use our specialized formatters to +        # emit the pretty output. +        self.type_map[list] = self.PRETTY_ARRAY +        self.type_map[tuple] = self.PRETTY_ARRAY +        self.type_map[types.GeneratorType] = self.PRETTY_ARRAY, +        self.type_map[dict] = self.PRETTY_MAP + +        # Private data used for indentation. +        self._indent_level = 1 +        if indent_atom is None: +            self._indent_atom = '  ' +        else: +            self._indent_atom = indent_atom + +    def _indent(self): +        "Return an indentation based on the atom and indentation level." +        return self._indent_atom * self._indent_level + +    def PRETTY_ARRAY(self, v): +        rv = [] +        rv.append('<array>\n') +        self._indent_level = self._indent_level + 1 +        rv.extend(["%s%s\n" % +                   (self._indent(), +                    self.generate(item)) +                   for item in v]) +        self._indent_level = self._indent_level - 1 +        rv.append(self._indent()) +        rv.append('</array>') +        return ''.join(rv) + +    def PRETTY_MAP(self, v): +        rv = [] +        rv.append('<map>\n') +        self._indent_level = self._indent_level + 1 +        keys = v.keys() +        keys.sort() +        rv.extend(["%s%s\n%s%s\n" % +                   (self._indent(), +                    self.elt('key', key), +                    self._indent(), +                    self.generate(v[key])) +                   for key in keys]) +        self._indent_level = self._indent_level - 1 +        rv.append(self._indent()) +        rv.append('</map>') +        return ''.join(rv) + +    def format(self, something): +        data = [] +        data.append('<?xml version="1.0" ?>\n<llsd>') +        data.append(self.generate(something)) +        data.append('</llsd>\n') +        return '\n'.join(data) + +def format_pretty_xml(something): +    """@brief Serialize a python object as 'pretty' llsd xml. + +    The output conforms to the LLSD DTD, unlike the output from the +    standard python xml.dom DOM::toprettyxml() method which does not +    preserve significant whitespace.  +    This function is not necessarily suited for serializing very large +    objects. It is not optimized by the cllsd module, and sorts on +    dict (llsd map) keys alphabetically to ease human reading. +    """ +    return LLSDXMLPrettyFormatter().format(something) + +class LLSDNotationFormatter(object): +    def __init__(self): +        self.type_map = { +            type(None) : self.UNDEF, +            bool : self.BOOLEAN, +            int : self.INTEGER, +            long : self.INTEGER, +            float : self.REAL, +            lluuid.UUID : self.UUID, +            binary : self.BINARY, +            str : self.STRING, +            unicode : self.STRING, +            uri : self.URI, +            datetime.datetime : self.DATE, +            datetime.date : self.DATE, +            list : self.ARRAY, +            tuple : self.ARRAY, +            types.GeneratorType : self.ARRAY, +            dict : self.MAP, +            LLSD : self.LLSD +        } + +    def LLSD(self, v): +        return self.generate(v.thing) +    def UNDEF(self, v): +        return '!' +    def BOOLEAN(self, v): +        if v: +            return 'true' +        else: +            return 'false' +    def INTEGER(self, v): +        return "i%s" % v +    def REAL(self, v): +        return "r%s" % v +    def UUID(self, v): +        return "u%s" % v +    def BINARY(self, v): +        return 'b64"' + base64.encodestring(v) + '"' +    def STRING(self, v): +        if isinstance(v, unicode): +            v = v.encode('utf-8') +        return "'%s'" % v.replace("\\", "\\\\").replace("'", "\\'") +    def URI(self, v): +        return 'l"%s"' % str(v).replace("\\", "\\\\").replace('"', '\\"') +    def DATE(self, v): +        return 'd"%s"' % format_datestr(v) +    def ARRAY(self, v): +        return "[%s]" % ','.join([self.generate(item) for item in v]) +    def MAP(self, v): +        def fix(key): +            if isinstance(key, unicode): +                return key.encode('utf-8') +            return key +        return "{%s}" % ','.join(["'%s':%s" % (fix(key).replace("\\", "\\\\").replace("'", "\\'"), self.generate(value)) +             for key, value in v.items()]) + +    def generate(self, something): +        t = type(something) +        handler = self.type_map.get(t) +        if handler: +            return handler(something) +        else: +            try: +                return self.ARRAY(iter(something)) +            except TypeError: +                raise LLSDSerializationError( +                    "Cannot serialize unknown type: %s (%s)" % (t, something)) + +    def format(self, something): +        return self.generate(something) + +def format_notation(something): +    return LLSDNotationFormatter().format(something) + +def _hex_as_nybble(hex): +    if (hex >= '0') and (hex <= '9'): +        return ord(hex) - ord('0') +    elif (hex >= 'a') and (hex <='f'): +        return 10 + ord(hex) - ord('a') +    elif (hex >= 'A') and (hex <='F'): +        return 10 + ord(hex) - ord('A'); + +class LLSDBinaryParser(object): +    def __init__(self): +        pass + +    def parse(self, buffer, ignore_binary = False): +        """ +        This is the basic public interface for parsing. + +        @param buffer the binary data to parse in an indexable sequence. +        @param ignore_binary parser throws away data in llsd binary nodes. +        @return returns a python object. +        """ +        self._buffer = buffer +        self._index = 0 +        self._keep_binary = not ignore_binary +        return self._parse() + +    def _parse(self): +        cc = self._buffer[self._index] +        self._index += 1 +        if cc == '{': +            return self._parse_map() +        elif cc == '[': +            return self._parse_array() +        elif cc == '!': +            return None +        elif cc == '0': +            return False +        elif cc == '1': +            return True +        elif cc == 'i': +            # 'i' = integer +            idx = self._index +            self._index += 4 +            return struct.unpack("!i", self._buffer[idx:idx+4])[0] +        elif cc == ('r'): +            # 'r' = real number +            idx = self._index +            self._index += 8 +            return struct.unpack("!d", self._buffer[idx:idx+8])[0] +        elif cc == 'u': +            # 'u' = uuid +            idx = self._index +            self._index += 16 +            return lluuid.uuid_bits_to_uuid(self._buffer[idx:idx+16]) +        elif cc == 's': +            # 's' = string +            return self._parse_string() +        elif cc in ("'", '"'): +            # delimited/escaped string +            return self._parse_string_delim(cc) +        elif cc == 'l': +            # 'l' = uri +            return uri(self._parse_string()) +        elif cc == ('d'): +            # 'd' = date in seconds since epoch +            idx = self._index +            self._index += 8 +            seconds = struct.unpack("!d", self._buffer[idx:idx+8])[0] +            return datetime.datetime.fromtimestamp(seconds) +        elif cc == 'b': +            binary = self._parse_string() +            if self._keep_binary: +                return binary +            # *NOTE: maybe have a binary placeholder which has the +            # length. +            return None +        else: +            raise LLSDParseError("invalid binary token at byte %d: %d" % ( +                self._index - 1, ord(cc))) + +    def _parse_map(self): +        rv = {} +        size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] +        self._index += 4 +        count = 0 +        cc = self._buffer[self._index] +        self._index += 1 +        key = '' +        while (cc != '}') and (count < size): +            if cc == 'k': +                key = self._parse_string() +            elif cc in ("'", '"'): +                key = self._parse_string_delim(cc) +            else: +                raise LLSDParseError("invalid map key at byte %d." % ( +                    self._index - 1,)) +            value = self._parse() +            rv[key] = value +            count += 1 +            cc = self._buffer[self._index] +            self._index += 1 +        if cc != '}': +            raise LLSDParseError("invalid map close token at byte %d." % ( +                self._index,)) +        return rv + +    def _parse_array(self): +        rv = [] +        size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] +        self._index += 4 +        count = 0 +        cc = self._buffer[self._index] +        while (cc != ']') and (count < size): +            rv.append(self._parse()) +            count += 1 +            cc = self._buffer[self._index] +        if cc != ']': +            raise LLSDParseError("invalid array close token at byte %d." % ( +                self._index,)) +        self._index += 1 +        return rv + +    def _parse_string(self): +        size = struct.unpack("!i", self._buffer[self._index:self._index+4])[0] +        self._index += 4 +        rv = self._buffer[self._index:self._index+size] +        self._index += size +        return rv + +    def _parse_string_delim(self, delim): +        list = [] +        found_escape = False +        found_hex = False +        found_digit = False +        byte = 0 +        while True: +            cc = self._buffer[self._index] +            self._index += 1 +            if found_escape: +                if found_hex: +                    if found_digit: +                        found_escape = False +                        found_hex = False +                        found_digit = False +                        byte <<= 4 +                        byte |= _hex_as_nybble(cc) +                        list.append(chr(byte)) +                        byte = 0 +                    else: +                        found_digit = True +                        byte = _hex_as_nybble(cc) +                elif cc == 'x': +                    found_hex = True +                else: +                    if cc == 'a': +                        list.append('\a') +                    elif cc == 'b': +                        list.append('\b') +                    elif cc == 'f': +                        list.append('\f') +                    elif cc == 'n': +                        list.append('\n') +                    elif cc == 'r': +                        list.append('\r') +                    elif cc == 't': +                        list.append('\t') +                    elif cc == 'v': +                        list.append('\v') +                    else: +                        list.append(cc) +                    found_escape = False +            elif cc == '\\': +                found_escape = True +            elif cc == delim: +                break +            else: +                list.append(cc) +        return ''.join(list) + +class LLSDNotationParser(object): +    """ Parse LLSD notation: +    map: { string:object, string:object } +    array: [ object, object, object ] +    undef: ! +    boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE +    integer: i#### +    real: r#### +    uuid: u#### +    string: "g\'day" | 'have a "nice" day' | s(size)"raw data" +    uri: l"escaped" +    date: d"YYYY-MM-DDTHH:MM:SS.FFZ" +    binary: b##"ff3120ab1" | b(size)"raw data" +    """ +    def __init__(self): +        pass + +    def parse(self, buffer, ignore_binary = False): +        """ +        This is the basic public interface for parsing. + +        @param buffer the notation string to parse. +        @param ignore_binary parser throws away data in llsd binary nodes. +        @return returns a python object. +        """ +        if buffer == "": +            return False + +        self._buffer = buffer +        self._index = 0 +        return self._parse() + +    def _parse(self): +        cc = self._buffer[self._index] +        self._index += 1 +        if cc == '{': +            return self._parse_map() +        elif cc == '[': +            return self._parse_array() +        elif cc == '!': +            return None +        elif cc == '0': +            return False +        elif cc == '1': +            return True +        elif cc in ('F', 'f'): +            self._skip_alpha() +            return False +        elif cc in ('T', 't'): +            self._skip_alpha() +            return True +        elif cc == 'i': +            # 'i' = integer +            return self._parse_integer() +        elif cc == ('r'): +            # 'r' = real number +            return self._parse_real() +        elif cc == 'u': +            # 'u' = uuid +            return self._parse_uuid() +        elif cc in ("'", '"', 's'): +            return self._parse_string(cc) +        elif cc == 'l': +            # 'l' = uri +            delim = self._buffer[self._index] +            self._index += 1 +            val = uri(self._parse_string(delim)) +            if len(val) == 0: +                return None +            return val +        elif cc == ('d'): +            # 'd' = date in seconds since epoch +            return self._parse_date() +        elif cc == 'b': +            return self._parse_binary() +        else: +            raise LLSDParseError("invalid token at index %d: %d" % ( +                self._index - 1, ord(cc))) + +    def _parse_binary(self): +        i = self._index +        if self._buffer[i:i+2] == '64': +            q = self._buffer[i+2] +            e = self._buffer.find(q, i+3) +            try: +                return base64.decodestring(self._buffer[i+3:e]) +            finally: +                self._index = e + 1 +        else: +            raise LLSDParseError('random horrible binary format not supported') + +    def _parse_map(self): +        """ map: { string:object, string:object } """ +        rv = {} +        cc = self._buffer[self._index] +        self._index += 1 +        key = '' +        found_key = False +        while (cc != '}'): +            if not found_key: +                if cc in ("'", '"', 's'): +                    key = self._parse_string(cc) +                    found_key = True +                elif cc.isspace() or cc == ',': +                    cc = self._buffer[self._index] +                    self._index += 1 +                else: +                    raise LLSDParseError("invalid map key at byte %d." % ( +                                        self._index - 1,)) +            elif cc.isspace() or cc == ':': +                cc = self._buffer[self._index] +                self._index += 1 +                continue +            else: +                self._index += 1 +                value = self._parse() +                rv[key] = value +                found_key = False +                cc = self._buffer[self._index] +                self._index += 1 + +        return rv + +    def _parse_array(self): +        """ array: [ object, object, object ] """ +        rv = [] +        cc = self._buffer[self._index] +        while (cc != ']'): +            if cc.isspace() or cc == ',': +                self._index += 1 +                cc = self._buffer[self._index] +                continue +            rv.append(self._parse()) +            cc = self._buffer[self._index] + +        if cc != ']': +            raise LLSDParseError("invalid array close token at index %d." % ( +                self._index,)) +        self._index += 1 +        return rv + +    def _parse_uuid(self): +        match = re.match(lluuid.UUID.uuid_regex, self._buffer[self._index:]) +        if not match: +            raise LLSDParseError("invalid uuid token at index %d." % self._index) + +        (start, end) = match.span() +        start += self._index +        end += self._index +        self._index = end +        return lluuid.UUID(self._buffer[start:end]) + +    def _skip_alpha(self): +        match = re.match(alpha_regex, self._buffer[self._index:]) +        if match: +            self._index += match.end() +             +    def _parse_date(self): +        delim = self._buffer[self._index] +        self._index += 1 +        datestr = self._parse_string(delim) +        return parse_datestr(datestr) + +    def _parse_real(self): +        match = re.match(real_regex, self._buffer[self._index:]) +        if not match: +            raise LLSDParseError("invalid real token at index %d." % self._index) + +        (start, end) = match.span() +        start += self._index +        end += self._index +        self._index = end +        return float( self._buffer[start:end] ) + +    def _parse_integer(self): +        match = re.match(int_regex, self._buffer[self._index:]) +        if not match: +            raise LLSDParseError("invalid integer token at index %d." % self._index) + +        (start, end) = match.span() +        start += self._index +        end += self._index +        self._index = end +        return int( self._buffer[start:end] ) + +    def _parse_string(self, delim): +        """ string: "g\'day" | 'have a "nice" day' | s(size)"raw data" """ +        rv = "" + +        if delim in ("'", '"'): +            rv = self._parse_string_delim(delim) +        elif delim == 's': +            rv = self._parse_string_raw() +        else: +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        return rv + + +    def _parse_string_delim(self, delim): +        """ string: "g'day 'un" | 'have a "nice" day' """ +        list = [] +        found_escape = False +        found_hex = False +        found_digit = False +        byte = 0 +        while True: +            cc = self._buffer[self._index] +            self._index += 1 +            if found_escape: +                if found_hex: +                    if found_digit: +                        found_escape = False +                        found_hex = False +                        found_digit = False +                        byte <<= 4 +                        byte |= _hex_as_nybble(cc) +                        list.append(chr(byte)) +                        byte = 0 +                    else: +                        found_digit = True +                        byte = _hex_as_nybble(cc) +                elif cc == 'x': +                    found_hex = True +                else: +                    if cc == 'a': +                        list.append('\a') +                    elif cc == 'b': +                        list.append('\b') +                    elif cc == 'f': +                        list.append('\f') +                    elif cc == 'n': +                        list.append('\n') +                    elif cc == 'r': +                        list.append('\r') +                    elif cc == 't': +                        list.append('\t') +                    elif cc == 'v': +                        list.append('\v') +                    else: +                        list.append(cc) +                    found_escape = False +            elif cc == '\\': +                found_escape = True +            elif cc == delim: +                break +            else: +                list.append(cc) +        return ''.join(list) + +    def _parse_string_raw(self): +        """ string: s(size)"raw data" """ +        # Read the (size) portion. +        cc = self._buffer[self._index] +        self._index += 1 +        if cc != '(': +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        rparen = self._buffer.find(')', self._index) +        if rparen == -1: +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        size = int(self._buffer[self._index:rparen]) + +        self._index = rparen + 1 +        delim = self._buffer[self._index] +        self._index += 1 +        if delim not in ("'", '"'): +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        rv = self._buffer[self._index:(self._index + size)] +        self._index += size +        cc = self._buffer[self._index] +        self._index += 1 +        if cc != delim: +            raise LLSDParseError("invalid string token at index %d." % self._index) + +        return rv +         +def format_binary(something): +    return '<?llsd/binary?>\n' + _format_binary_recurse(something) + +def _format_binary_recurse(something): +    def _format_list(something): +        array_builder = [] +        array_builder.append('[' + struct.pack('!i', len(something))) +        for item in something: +            array_builder.append(_format_binary_recurse(item)) +        array_builder.append(']') +        return ''.join(array_builder) + +    if something is None: +        return '!' +    elif isinstance(something, LLSD): +        return _format_binary_recurse(something.thing) +    elif isinstance(something, bool): +        if something: +            return '1' +        else: +            return '0' +    elif isinstance(something, (int, long)): +        return 'i' + struct.pack('!i', something) +    elif isinstance(something, float): +        return 'r' + struct.pack('!d', something) +    elif isinstance(something, lluuid.UUID): +        return 'u' + something._bits +    elif isinstance(something, binary): +        return 'b' + struct.pack('!i', len(something)) + something +    elif isinstance(something, str): +        return 's' + struct.pack('!i', len(something)) + something +    elif isinstance(something, unicode): +        something = something.encode('utf-8') +        return 's' + struct.pack('!i', len(something)) + something +    elif isinstance(something, uri): +        return 'l' + struct.pack('!i', len(something)) + something +    elif isinstance(something, datetime.datetime): +        seconds_since_epoch = time.mktime(something.timetuple()) +        return 'd' + struct.pack('!d', seconds_since_epoch) +    elif isinstance(something, (list, tuple)): +        return _format_list(something) +    elif isinstance(something, dict): +        map_builder = [] +        map_builder.append('{' + struct.pack('!i', len(something))) +        for key, value in something.items(): +            if isinstance(key, unicode): +                key = key.encode('utf-8') +            map_builder.append('k' + struct.pack('!i', len(key)) + key) +            map_builder.append(_format_binary_recurse(value)) +        map_builder.append('}') +        return ''.join(map_builder) +    else: +        try: +            return _format_list(list(something)) +        except TypeError: +            raise LLSDSerializationError( +                "Cannot serialize unknown type: %s (%s)" % +                (type(something), something)) + + +def parse_binary(binary): +    if binary.startswith('<?llsd/binary?>'): +        just_binary = binary.split('\n', 1)[1] +    else: +        just_binary = binary +    return LLSDBinaryParser().parse(just_binary) + +def parse_xml(something): +    try: +        return to_python(fromstring(something)[0]) +    except ElementTreeError, err: +        raise LLSDParseError(*err.args) + +def parse_notation(something): +    return LLSDNotationParser().parse(something) + +def parse(something): +    try: +        something = string.lstrip(something)   #remove any pre-trailing whitespace +        if something.startswith('<?llsd/binary?>'): +            return parse_binary(something) +        # This should be better. +        elif something.startswith('<'): +            return parse_xml(something) +        else: +            return parse_notation(something) +    except KeyError, e: +        raise Exception('LLSD could not be parsed: %s' % (e,)) + +class LLSD(object): +    def __init__(self, thing=None): +        self.thing = thing + +    def __str__(self): +        return self.toXML(self.thing) + +    parse = staticmethod(parse) +    toXML = staticmethod(format_xml) +    toPrettyXML = staticmethod(format_pretty_xml) +    toBinary = staticmethod(format_binary) +    toNotation = staticmethod(format_notation) + + +undef = LLSD(None) + +XML_MIME_TYPE = 'application/llsd+xml' +BINARY_MIME_TYPE = 'application/llsd+binary' + +# register converters for llsd in mulib, if it is available +try: +    from mulib import stacked, mu +    stacked.NoProducer()  # just to exercise stacked +    mu.safe_load(None)    # just to exercise mu +except: +    # mulib not available, don't print an error message since this is normal +    pass +else: +    mu.add_parser(parse, XML_MIME_TYPE) +    mu.add_parser(parse, 'application/llsd+binary') + +    def llsd_convert_xml(llsd_stuff, request): +        request.write(format_xml(llsd_stuff)) + +    def llsd_convert_binary(llsd_stuff, request): +        request.write(format_binary(llsd_stuff)) + +    for typ in [LLSD, dict, list, tuple, str, int, long, float, bool, unicode, type(None)]: +        stacked.add_producer(typ, llsd_convert_xml, XML_MIME_TYPE) +        stacked.add_producer(typ, llsd_convert_xml, 'application/xml') +        stacked.add_producer(typ, llsd_convert_xml, 'text/xml') + +        stacked.add_producer(typ, llsd_convert_binary, 'application/llsd+binary') + +    stacked.add_producer(LLSD, llsd_convert_xml, '*/*') + +    # in case someone is using the legacy mu.xml wrapper, we need to +    # tell mu to produce application/xml or application/llsd+xml +    # (based on the accept header) from raw xml. Phoenix 2008-07-21 +    stacked.add_producer(mu.xml, mu.produce_raw, XML_MIME_TYPE) +    stacked.add_producer(mu.xml, mu.produce_raw, 'application/xml') + + + +# mulib wsgi stuff +# try: +#     from mulib import mu, adapters +# +#     # try some known attributes from mulib to be ultra-sure we've imported it +#     mu.get_current +#     adapters.handlers +# except: +#     # mulib not available, don't print an error message since this is normal +#     pass +# else: +#     def llsd_xml_handler(content_type): +#         def handle_llsd_xml(env, start_response): +#             llsd_stuff, _ = mu.get_current(env) +#             result = format_xml(llsd_stuff) +#             start_response("200 OK", [('Content-Type', content_type)]) +#             env['mu.negotiated_type'] = content_type +#             yield result +#         return handle_llsd_xml +#     +#     def llsd_binary_handler(content_type): +#         def handle_llsd_binary(env, start_response): +#             llsd_stuff, _ = mu.get_current(env) +#             result = format_binary(llsd_stuff) +#             start_response("200 OK", [('Content-Type', content_type)]) +#             env['mu.negotiated_type'] = content_type +#             yield result +#         return handle_llsd_binary +# +#     adapters.DEFAULT_PARSERS[XML_MIME_TYPE] = parse +     +#     for typ in [LLSD, dict, list, tuple, str, int, float, bool, unicode, type(None)]: +#         for content_type in (XML_MIME_TYPE, 'application/xml'): +#             adapters.handlers.set_handler(typ, llsd_xml_handler(content_type), content_type) +# +#         adapters.handlers.set_handler(typ, llsd_binary_handler(BINARY_MIME_TYPE), BINARY_MIME_TYPE) +# +#     adapters.handlers.set_handler(LLSD, llsd_xml_handler(XML_MIME_TYPE), '*/*') | 
