diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/spread/jelly.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/spread/jelly.py')
-rw-r--r-- | contrib/python/Twisted/py2/twisted/spread/jelly.py | 1131 |
1 files changed, 1131 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/spread/jelly.py b/contrib/python/Twisted/py2/twisted/spread/jelly.py new file mode 100644 index 0000000000..39857e3b44 --- /dev/null +++ b/contrib/python/Twisted/py2/twisted/spread/jelly.py @@ -0,0 +1,1131 @@ +# -*- test-case-name: twisted.spread.test.test_jelly -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +S-expression-based persistence of python objects. + +It does something very much like L{Pickle<pickle>}; however, pickle's main goal +seems to be efficiency (both in space and time); jelly's main goals are +security, human readability, and portability to other environments. + +This is how Jelly converts various objects to s-expressions. + +Boolean:: + True --> ['boolean', 'true'] + +Integer:: + 1 --> 1 + +List:: + [1, 2] --> ['list', 1, 2] + +String:: + \"hello\" --> \"hello\" + +Float:: + 2.3 --> 2.3 + +Dictionary:: + {'a': 1, 'b': 'c'} --> ['dictionary', ['b', 'c'], ['a', 1]] + +Module:: + UserString --> ['module', 'UserString'] + +Class:: + UserString.UserString --> ['class', ['module', 'UserString'], 'UserString'] + +Function:: + string.join --> ['function', 'join', ['module', 'string']] + +Instance: s is an instance of UserString.UserString, with a __dict__ +{'data': 'hello'}:: + [\"UserString.UserString\", ['dictionary', ['data', 'hello']]] + +Class Method: UserString.UserString.center:: + ['method', 'center', ['None'], ['class', ['module', 'UserString'], + 'UserString']] + +Instance Method: s.center, where s is an instance of UserString.UserString:: + ['method', 'center', ['instance', ['reference', 1, ['class', + ['module', 'UserString'], 'UserString']], ['dictionary', ['data', 'd']]], + ['dereference', 1]] + +The C{set} builtin and the C{sets.Set} class are serialized to the same +thing, and unserialized to C{set} if available, else to C{sets.Set}. It means +that there's a possibility of type switching in the serialization process. The +solution is to always use C{set}. + +The same rule applies for C{frozenset} and C{sets.ImmutableSet}. + +@author: Glyph Lefkowitz +""" + +# System Imports +import types +import warnings +import decimal +from functools import reduce +import copy +import datetime + +try: + from types import (ClassType as _OldStyleClass, + InstanceType as _OldStyleInstance) +except ImportError: + # On Python 3 and higher, ClassType and InstanceType + # are gone. Use an empty tuple to pass to isinstance() + # tests without throwing an exception. + _OldStyleClass = () + _OldStyleInstance = () + +_SetTypes = [set] +_ImmutableSetTypes = [frozenset] + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=DeprecationWarning) + try: + import sets as _sets + except ImportError: + # sets module is deprecated in Python 2.6, and gone in + # Python 3 + _sets = None + else: + _SetTypes.append(_sets.Set) + _ImmutableSetTypes.append(_sets.ImmutableSet) + +from zope.interface import implementer + +# Twisted Imports +from twisted.python.compat import unicode, long, nativeString +from twisted.python.reflect import namedObject, qual, namedAny +from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod +from twisted.persisted.crefutil import _DictKeyAndValue, _Dereference +from twisted.persisted.crefutil import _Container + +from twisted.spread.interfaces import IJellyable, IUnjellyable + +from twisted.python.compat import _PY3 +from twisted.python.deprecate import deprecatedModuleAttribute +from incremental import Version + +DictTypes = (dict,) + +None_atom = b"None" # N +# code +class_atom = b"class" # c +module_atom = b"module" # m +function_atom = b"function" # f + +# references +dereference_atom = b'dereference' # D +persistent_atom = b'persistent' # p +reference_atom = b'reference' # r + +# mutable collections +dictionary_atom = b"dictionary" # d +list_atom = b'list' # l +set_atom = b'set' + +# immutable collections +# (assignment to __dict__ and __class__ still might go away!) +tuple_atom = b"tuple" # t +instance_atom = b'instance' # i +frozenset_atom = b'frozenset' + + +deprecatedModuleAttribute( + Version("Twisted", 15, 0, 0), + "instance_atom is unused within Twisted.", + "twisted.spread.jelly", "instance_atom") + +# errors +unpersistable_atom = b"unpersistable"# u +unjellyableRegistry = {} +unjellyableFactoryRegistry = {} + + + +def _createBlank(cls): + """ + Given an object, if that object is a type (or a legacy old-style class), + return a new, blank instance of that type which has not had C{__init__} + called on it. If the object is not a type, return C{None}. + + @param cls: The type (or class) to create an instance of. + @type cls: L{_OldStyleClass}, L{type}, or something else that cannot be + instantiated. + + @return: a new blank instance or L{None} if C{cls} is not a class or type. + """ + if isinstance(cls, type): + return cls.__new__(cls) + if not _PY3 and isinstance(cls, _OldStyleClass): + return _OldStyleInstance(cls) + + + +def _newInstance(cls, state): + """ + Make a new instance of a class without calling its __init__ method. + Supports both new- and old-style classes. + + @param state: A C{dict} used to update C{inst.__dict__} either directly or + via C{__setstate__}, if available. + + @return: A new instance of C{cls}. + """ + instance = _createBlank(cls) + def defaultSetter(state): + instance.__dict__ = state + setter = getattr(instance, "__setstate__", defaultSetter) + setter(state) + return instance + + + +def _maybeClass(classnamep): + isObject = isinstance(classnamep, type) + + if isObject or ((not _PY3) and isinstance(classnamep, _OldStyleClass)): + classnamep = qual(classnamep) + + if not isinstance(classnamep, bytes): + classnamep = classnamep.encode('utf-8') + + return classnamep + + + +def setUnjellyableForClass(classname, unjellyable): + """ + Set which local class will represent a remote type. + + If you have written a Copyable class that you expect your client to be + receiving, write a local "copy" class to represent it, then call:: + + jellier.setUnjellyableForClass('module.package.Class', MyCopier). + + Call this at the module level immediately after its class + definition. MyCopier should be a subclass of RemoteCopy. + + The classname may be a special tag returned by + 'Copyable.getTypeToCopyFor' rather than an actual classname. + + This call is also for cached classes, since there will be no + overlap. The rules are the same. + """ + + global unjellyableRegistry + classname = _maybeClass(classname) + unjellyableRegistry[classname] = unjellyable + globalSecurity.allowTypes(classname) + + + +def setUnjellyableFactoryForClass(classname, copyFactory): + """ + Set the factory to construct a remote instance of a type:: + + jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory) + + Call this at the module level immediately after its class definition. + C{copyFactory} should return an instance or subclass of + L{RemoteCopy<pb.RemoteCopy>}. + + Similar to L{setUnjellyableForClass} except it uses a factory instead + of creating an instance. + """ + + global unjellyableFactoryRegistry + classname = _maybeClass(classname) + unjellyableFactoryRegistry[classname] = copyFactory + globalSecurity.allowTypes(classname) + + + +def setUnjellyableForClassTree(module, baseClass, prefix=None): + """ + Set all classes in a module derived from C{baseClass} as copiers for + a corresponding remote class. + + When you have a hierarchy of Copyable (or Cacheable) classes on one + side, and a mirror structure of Copied (or RemoteCache) classes on the + other, use this to setUnjellyableForClass all your Copieds for the + Copyables. + + Each copyTag (the \"classname\" argument to getTypeToCopyFor, and + what the Copyable's getTypeToCopyFor returns) is formed from + adding a prefix to the Copied's class name. The prefix defaults + to module.__name__. If you wish the copy tag to consist of solely + the classname, pass the empty string \'\'. + + @param module: a module object from which to pull the Copied classes. + (passing sys.modules[__name__] might be useful) + + @param baseClass: the base class from which all your Copied classes derive. + + @param prefix: the string prefixed to classnames to form the + unjellyableRegistry. + """ + if prefix is None: + prefix = module.__name__ + + if prefix: + prefix = "%s." % prefix + + for name in dir(module): + loaded = getattr(module, name) + try: + yes = issubclass(loaded, baseClass) + except TypeError: + "It's not a class." + else: + if yes: + setUnjellyableForClass('%s%s' % (prefix, name), loaded) + + + +def getInstanceState(inst, jellier): + """ + Utility method to default to 'normal' state rules in serialization. + """ + if hasattr(inst, "__getstate__"): + state = inst.__getstate__() + else: + state = inst.__dict__ + sxp = jellier.prepare(inst) + sxp.extend([qual(inst.__class__).encode('utf-8'), jellier.jelly(state)]) + return jellier.preserve(inst, sxp) + + + +def setInstanceState(inst, unjellier, jellyList): + """ + Utility method to default to 'normal' state rules in unserialization. + """ + state = unjellier.unjelly(jellyList[1]) + if hasattr(inst, "__setstate__"): + inst.__setstate__(state) + else: + inst.__dict__ = state + return inst + + + +class Unpersistable: + """ + This is an instance of a class that comes back when something couldn't be + unpersisted. + """ + + def __init__(self, reason): + """ + Initialize an unpersistable object with a descriptive C{reason} string. + """ + self.reason = reason + + + def __repr__(self): + return "Unpersistable(%s)" % repr(self.reason) + + + +@implementer(IJellyable) +class Jellyable: + """ + Inherit from me to Jelly yourself directly with the `getStateFor' + convenience method. + """ + + def getStateFor(self, jellier): + return self.__dict__ + + + def jellyFor(self, jellier): + """ + @see: L{twisted.spread.interfaces.IJellyable.jellyFor} + """ + sxp = jellier.prepare(self) + sxp.extend([ + qual(self.__class__).encode('utf-8'), + jellier.jelly(self.getStateFor(jellier))]) + return jellier.preserve(self, sxp) + + + +@implementer(IUnjellyable) +class Unjellyable: + """ + Inherit from me to Unjelly yourself directly with the + C{setStateFor} convenience method. + """ + + def setStateFor(self, unjellier, state): + self.__dict__ = state + + + def unjellyFor(self, unjellier, jellyList): + """ + Perform the inverse operation of L{Jellyable.jellyFor}. + + @see: L{twisted.spread.interfaces.IUnjellyable.unjellyFor} + """ + state = unjellier.unjelly(jellyList[1]) + self.setStateFor(unjellier, state) + return self + + + +class _Jellier: + """ + (Internal) This class manages state for a call to jelly() + """ + + def __init__(self, taster, persistentStore, invoker): + """ + Initialize. + """ + self.taster = taster + # `preserved' is a dict of previously seen instances. + self.preserved = {} + # `cooked' is a dict of previously backreferenced instances to their + # `ref' lists. + self.cooked = {} + self.cooker = {} + self._ref_id = 1 + self.persistentStore = persistentStore + self.invoker = invoker + + + def _cook(self, object): + """ + (internal) Backreference an object. + + Notes on this method for the hapless future maintainer: If I've already + gone through the prepare/preserve cycle on the specified object (it is + being referenced after the serializer is \"done with\" it, e.g. this + reference is NOT circular), the copy-in-place of aList is relevant, + since the list being modified is the actual, pre-existing jelly + expression that was returned for that object. If not, it's technically + superfluous, since the value in self.preserved didn't need to be set, + but the invariant that self.preserved[id(object)] is a list is + convenient because that means we don't have to test and create it or + not create it here, creating fewer code-paths. that's why + self.preserved is always set to a list. + + Sorry that this code is so hard to follow, but Python objects are + tricky to persist correctly. -glyph + """ + aList = self.preserved[id(object)] + newList = copy.copy(aList) + # make a new reference ID + refid = self._ref_id + self._ref_id = self._ref_id + 1 + # replace the old list in-place, so that we don't have to track the + # previous reference to it. + aList[:] = [reference_atom, refid, newList] + self.cooked[id(object)] = [dereference_atom, refid] + return aList + + + def prepare(self, object): + """ + (internal) Create a list for persisting an object to. This will allow + backreferences to be made internal to the object. (circular + references). + + The reason this needs to happen is that we don't generate an ID for + every object, so we won't necessarily know which ID the object will + have in the future. When it is 'cooked' ( see _cook ), it will be + assigned an ID, and the temporary placeholder list created here will be + modified in-place to create an expression that gives this object an ID: + [reference id# [object-jelly]]. + """ + + # create a placeholder list to be preserved + self.preserved[id(object)] = [] + # keep a reference to this object around, so it doesn't disappear! + # (This isn't always necessary, but for cases where the objects are + # dynamically generated by __getstate__ or getStateToCopyFor calls, it + # is; id() will return the same value for a different object if it gets + # garbage collected. This may be optimized later.) + self.cooker[id(object)] = object + return [] + + + def preserve(self, object, sexp): + """ + (internal) Mark an object's persistent list for later referral. + """ + # if I've been cooked in the meanwhile, + if id(object) in self.cooked: + # replace the placeholder empty list with the real one + self.preserved[id(object)][2] = sexp + # but give this one back. + sexp = self.preserved[id(object)] + else: + self.preserved[id(object)] = sexp + return sexp + + constantTypes = {bytes: 1, unicode: 1, int: 1, float: 1, long: 1} + + + def _checkMutable(self,obj): + objId = id(obj) + if objId in self.cooked: + return self.cooked[objId] + if objId in self.preserved: + self._cook(obj) + return self.cooked[objId] + + + def jelly(self, obj): + if isinstance(obj, Jellyable): + preRef = self._checkMutable(obj) + if preRef: + return preRef + return obj.jellyFor(self) + objType = type(obj) + if self.taster.isTypeAllowed(qual(objType).encode('utf-8')): + # "Immutable" Types + if ((objType is bytes) or + (objType is int) or + (objType is long) or + (objType is float)): + return obj + elif objType is types.MethodType: + aSelf = obj.__self__ if _PY3 else obj.im_self + aFunc = obj.__func__ if _PY3 else obj.im_func + aClass = aSelf.__class__ if _PY3 else obj.im_class + return [b"method", aFunc.__name__, self.jelly(aSelf), + self.jelly(aClass)] + elif objType is unicode: + return [b'unicode', obj.encode('UTF-8')] + elif objType is type(None): + return [b'None'] + elif objType is types.FunctionType: + return [b'function', obj.__module__ + '.' + + (obj.__qualname__ if _PY3 else obj.__name__)] + elif objType is types.ModuleType: + return [b'module', obj.__name__] + elif objType is bool: + return [b'boolean', obj and b'true' or b'false'] + elif objType is datetime.datetime: + if obj.tzinfo: + raise NotImplementedError( + "Currently can't jelly datetime objects with tzinfo") + return [b'datetime', ' '.join([unicode(x) for x in ( + obj.year, obj.month, obj.day, obj.hour, + obj.minute, obj.second, obj.microsecond)] + ).encode('utf-8')] + elif objType is datetime.time: + if obj.tzinfo: + raise NotImplementedError( + "Currently can't jelly datetime objects with tzinfo") + return [b'time', '%s %s %s %s' % (obj.hour, obj.minute, + obj.second, obj.microsecond)] + elif objType is datetime.date: + return [b'date', '%s %s %s' % (obj.year, obj.month, obj.day)] + elif objType is datetime.timedelta: + return [b'timedelta', '%s %s %s' % (obj.days, obj.seconds, + obj.microseconds)] + elif issubclass(objType, (type, _OldStyleClass)): + return [b'class', qual(obj).encode('utf-8')] + elif objType is decimal.Decimal: + return self.jelly_decimal(obj) + else: + preRef = self._checkMutable(obj) + if preRef: + return preRef + # "Mutable" Types + sxp = self.prepare(obj) + if objType is list: + sxp.extend(self._jellyIterable(list_atom, obj)) + elif objType is tuple: + sxp.extend(self._jellyIterable(tuple_atom, obj)) + elif objType in DictTypes: + sxp.append(dictionary_atom) + for key, val in obj.items(): + sxp.append([self.jelly(key), self.jelly(val)]) + elif objType in _SetTypes: + sxp.extend(self._jellyIterable(set_atom, obj)) + elif objType in _ImmutableSetTypes: + sxp.extend(self._jellyIterable(frozenset_atom, obj)) + else: + className = qual(obj.__class__).encode('utf-8') + persistent = None + if self.persistentStore: + persistent = self.persistentStore(obj, self) + if persistent is not None: + sxp.append(persistent_atom) + sxp.append(persistent) + elif self.taster.isClassAllowed(obj.__class__): + sxp.append(className) + if hasattr(obj, "__getstate__"): + state = obj.__getstate__() + else: + state = obj.__dict__ + sxp.append(self.jelly(state)) + else: + self.unpersistable( + "instance of class %s deemed insecure" % + qual(obj.__class__), sxp) + return self.preserve(obj, sxp) + else: + if objType is _OldStyleInstance: + raise InsecureJelly("Class not allowed for instance: %s %s" % + (obj.__class__, obj)) + raise InsecureJelly("Type not allowed for object: %s %s" % + (objType, obj)) + + + def _jellyIterable(self, atom, obj): + """ + Jelly an iterable object. + + @param atom: the identifier atom of the object. + @type atom: C{str} + + @param obj: any iterable object. + @type obj: C{iterable} + + @return: a generator of jellied data. + @rtype: C{generator} + """ + yield atom + for item in obj: + yield self.jelly(item) + + + def jelly_decimal(self, d): + """ + Jelly a decimal object. + + @param d: a decimal object to serialize. + @type d: C{decimal.Decimal} + + @return: jelly for the decimal object. + @rtype: C{list} + """ + sign, guts, exponent = d.as_tuple() + value = reduce(lambda left, right: left * 10 + right, guts) + if sign: + value = -value + return [b'decimal', value, exponent] + + + def unpersistable(self, reason, sxp=None): + """ + (internal) Returns an sexp: (unpersistable "reason"). Utility method + for making note that a particular object could not be serialized. + """ + if sxp is None: + sxp = [] + sxp.append(unpersistable_atom) + if isinstance(reason, unicode): + reason = reason.encode("utf-8") + sxp.append(reason) + return sxp + + + +class _Unjellier: + + def __init__(self, taster, persistentLoad, invoker): + self.taster = taster + self.persistentLoad = persistentLoad + self.references = {} + self.postCallbacks = [] + self.invoker = invoker + + + def unjellyFull(self, obj): + o = self.unjelly(obj) + for m in self.postCallbacks: + m() + return o + + + def _maybePostUnjelly(self, unjellied): + """ + If the given object has support for the C{postUnjelly} hook, set it up + to be called at the end of deserialization. + + @param unjellied: an object that has already been unjellied. + + @return: C{unjellied} + """ + if hasattr(unjellied, 'postUnjelly'): + self.postCallbacks.append(unjellied.postUnjelly) + return unjellied + + + def unjelly(self, obj): + if type(obj) is not list: + return obj + jelTypeBytes = obj[0] + if not self.taster.isTypeAllowed(jelTypeBytes): + raise InsecureJelly(jelTypeBytes) + regClass = unjellyableRegistry.get(jelTypeBytes) + if regClass is not None: + method = getattr(_createBlank(regClass), "unjellyFor", regClass) + return self._maybePostUnjelly(method(self, obj)) + regFactory = unjellyableFactoryRegistry.get(jelTypeBytes) + if regFactory is not None: + return self._maybePostUnjelly(regFactory(self.unjelly(obj[1]))) + + jelTypeText = nativeString(jelTypeBytes) + thunk = getattr(self, '_unjelly_%s' % jelTypeText, None) + if thunk is not None: + return thunk(obj[1:]) + else: + nameSplit = jelTypeText.split('.') + modName = '.'.join(nameSplit[:-1]) + if not self.taster.isModuleAllowed(modName): + raise InsecureJelly( + "Module %s not allowed (in type %s)." % (modName, jelTypeText)) + clz = namedObject(jelTypeText) + if not self.taster.isClassAllowed(clz): + raise InsecureJelly("Class %s not allowed." % jelTypeText) + return self._genericUnjelly(clz, obj[1]) + + + def _genericUnjelly(self, cls, state): + """ + Unjelly a type for which no specific unjellier is registered, but which + is nonetheless allowed. + + @param cls: the class of the instance we are unjellying. + @type cls: L{_OldStyleClass} or L{type} + + @param state: The jellied representation of the object's state; its + C{__dict__} unless it has a C{__setstate__} that takes something + else. + @type state: L{list} + + @return: the new, unjellied instance. + """ + return self._maybePostUnjelly(_newInstance(cls, self.unjelly(state))) + + + def _unjelly_None(self, exp): + return None + + + def _unjelly_unicode(self, exp): + return unicode(exp[0], "UTF-8") + + + def _unjelly_decimal(self, exp): + """ + Unjelly decimal objects. + """ + value = exp[0] + exponent = exp[1] + if value < 0: + sign = 1 + else: + sign = 0 + guts = decimal.Decimal(value).as_tuple()[1] + return decimal.Decimal((sign, guts, exponent)) + + + def _unjelly_boolean(self, exp): + if bool: + assert exp[0] in (b'true', b'false') + return exp[0] == b'true' + else: + return Unpersistable("Could not unpersist boolean: %s" % (exp[0],)) + + + def _unjelly_datetime(self, exp): + return datetime.datetime(*map(int, exp[0].split())) + + + def _unjelly_date(self, exp): + return datetime.date(*map(int, exp[0].split())) + + + def _unjelly_time(self, exp): + return datetime.time(*map(int, exp[0].split())) + + + def _unjelly_timedelta(self, exp): + days, seconds, microseconds = map(int, exp[0].split()) + return datetime.timedelta( + days=days, seconds=seconds, microseconds=microseconds) + + + def unjellyInto(self, obj, loc, jel): + o = self.unjelly(jel) + if isinstance(o, NotKnown): + o.addDependant(obj, loc) + obj[loc] = o + return o + + + def _unjelly_dereference(self, lst): + refid = lst[0] + x = self.references.get(refid) + if x is not None: + return x + der = _Dereference(refid) + self.references[refid] = der + return der + + + def _unjelly_reference(self, lst): + refid = lst[0] + exp = lst[1] + o = self.unjelly(exp) + ref = self.references.get(refid) + if (ref is None): + self.references[refid] = o + elif isinstance(ref, NotKnown): + ref.resolveDependants(o) + self.references[refid] = o + else: + assert 0, "Multiple references with same ID!" + return o + + + def _unjelly_tuple(self, lst): + l = list(range(len(lst))) + finished = 1 + for elem in l: + if isinstance(self.unjellyInto(l, elem, lst[elem]), NotKnown): + finished = 0 + if finished: + return tuple(l) + else: + return _Tuple(l) + + + def _unjelly_list(self, lst): + l = list(range(len(lst))) + for elem in l: + self.unjellyInto(l, elem, lst[elem]) + return l + + + def _unjellySetOrFrozenset(self, lst, containerType): + """ + Helper method to unjelly set or frozenset. + + @param lst: the content of the set. + @type lst: C{list} + + @param containerType: the type of C{set} to use. + """ + l = list(range(len(lst))) + finished = True + for elem in l: + data = self.unjellyInto(l, elem, lst[elem]) + if isinstance(data, NotKnown): + finished = False + if not finished: + return _Container(l, containerType) + else: + return containerType(l) + + + def _unjelly_set(self, lst): + """ + Unjelly set using the C{set} builtin. + """ + return self._unjellySetOrFrozenset(lst, set) + + + def _unjelly_frozenset(self, lst): + """ + Unjelly frozenset using the C{frozenset} builtin. + """ + return self._unjellySetOrFrozenset(lst, frozenset) + + + def _unjelly_dictionary(self, lst): + d = {} + for k, v in lst: + kvd = _DictKeyAndValue(d) + self.unjellyInto(kvd, 0, k) + self.unjellyInto(kvd, 1, v) + return d + + + def _unjelly_module(self, rest): + moduleName = nativeString(rest[0]) + if type(moduleName) != str: + raise InsecureJelly( + "Attempted to unjelly a module with a non-string name.") + if not self.taster.isModuleAllowed(moduleName): + raise InsecureJelly( + "Attempted to unjelly module named %r" % (moduleName,)) + mod = __import__(moduleName, {}, {},"x") + return mod + + + def _unjelly_class(self, rest): + cname = nativeString(rest[0]) + clist = cname.split(nativeString('.')) + modName = nativeString('.').join(clist[:-1]) + if not self.taster.isModuleAllowed(modName): + raise InsecureJelly("module %s not allowed" % modName) + klaus = namedObject(cname) + objType = type(klaus) + if objType not in (_OldStyleClass, type): + raise InsecureJelly( + "class %r unjellied to something that isn't a class: %r" % ( + cname, klaus)) + if not self.taster.isClassAllowed(klaus): + raise InsecureJelly("class not allowed: %s" % qual(klaus)) + return klaus + + + def _unjelly_function(self, rest): + fname = nativeString(rest[0]) + modSplit = fname.split(nativeString('.')) + modName = nativeString('.').join(modSplit[:-1]) + if not self.taster.isModuleAllowed(modName): + raise InsecureJelly("Module not allowed: %s" % modName) + # XXX do I need an isFunctionAllowed? + function = namedAny(fname) + return function + + + def _unjelly_persistent(self, rest): + if self.persistentLoad: + pload = self.persistentLoad(rest[0], self) + return pload + else: + return Unpersistable("Persistent callback not found") + + + def _unjelly_instance(self, rest): + """ + (internal) Unjelly an instance. + + Called to handle the deprecated I{instance} token. + + @param rest: The s-expression representing the instance. + + @return: The unjellied instance. + """ + warnings.warn_explicit( + "Unjelly support for the instance atom is deprecated since " + "Twisted 15.0.0. Upgrade peer for modern instance support.", + category=DeprecationWarning, filename="", lineno=0) + + clz = self.unjelly(rest[0]) + if not _PY3 and type(clz) is not _OldStyleClass: + raise InsecureJelly("Legacy 'instance' found with new-style class") + return self._genericUnjelly(clz, rest[1]) + + + def _unjelly_unpersistable(self, rest): + return Unpersistable("Unpersistable data: %s" % (rest[0],)) + + + def _unjelly_method(self, rest): + """ + (internal) Unjelly a method. + """ + im_name = rest[0] + im_self = self.unjelly(rest[1]) + im_class = self.unjelly(rest[2]) + if not isinstance(im_class, (type, _OldStyleClass)): + raise InsecureJelly("Method found with non-class class.") + if im_name in im_class.__dict__: + if im_self is None: + im = getattr(im_class, im_name) + elif isinstance(im_self, NotKnown): + im = _InstanceMethod(im_name, im_self, im_class) + else: + im = types.MethodType(im_class.__dict__[im_name], im_self, + *([im_class] * (not _PY3))) + else: + raise TypeError('instance method changed') + return im + + + +#### Published Interface. + + +class InsecureJelly(Exception): + """ + This exception will be raised when a jelly is deemed `insecure'; e.g. it + contains a type, class, or module disallowed by the specified `taster' + """ + + + +class DummySecurityOptions: + """ + DummySecurityOptions() -> insecure security options + Dummy security options -- this class will allow anything. + """ + + def isModuleAllowed(self, moduleName): + """ + DummySecurityOptions.isModuleAllowed(moduleName) -> boolean + returns 1 if a module by that name is allowed, 0 otherwise + """ + return 1 + + + def isClassAllowed(self, klass): + """ + DummySecurityOptions.isClassAllowed(class) -> boolean + Assumes the module has already been allowed. Returns 1 if the given + class is allowed, 0 otherwise. + """ + return 1 + + + def isTypeAllowed(self, typeName): + """ + DummySecurityOptions.isTypeAllowed(typeName) -> boolean + Returns 1 if the given type is allowed, 0 otherwise. + """ + return 1 + + + +class SecurityOptions: + """ + This will by default disallow everything, except for 'none'. + """ + + basicTypes = ["dictionary", "list", "tuple", + "reference", "dereference", "unpersistable", + "persistent", "long_int", "long", "dict"] + + def __init__(self): + """ + SecurityOptions() initialize. + """ + # I don't believe any of these types can ever pose a security hazard, + # except perhaps "reference"... + self.allowedTypes = { + b"None": 1, b"bool": 1, b"boolean": 1, b"string": 1, b"str": 1, + b"int": 1, b"float": 1, b"datetime": 1, b"time": 1, b"date": 1, + b"timedelta": 1, b"NoneType": 1, b'unicode': 1, b'decimal': 1, + b'set': 1, b'frozenset': 1, + } + self.allowedModules = {} + self.allowedClasses = {} + + + def allowBasicTypes(self): + """ + Allow all `basic' types. (Dictionary and list. Int, string, and float + are implicitly allowed.) + """ + self.allowTypes(*self.basicTypes) + + + def allowTypes(self, *types): + """ + SecurityOptions.allowTypes(typeString): Allow a particular type, by its + name. + """ + for typ in types: + if isinstance(typ, unicode): + typ = typ.encode('utf-8') + if not isinstance(typ, bytes): + typ = qual(typ) + self.allowedTypes[typ] = 1 + + + def allowInstancesOf(self, *classes): + """ + SecurityOptions.allowInstances(klass, klass, ...): allow instances + of the specified classes + + This will also allow the 'instance', 'class' (renamed 'classobj' in + Python 2.3), and 'module' types, as well as basic types. + """ + self.allowBasicTypes() + self.allowTypes("instance", "class", "classobj", "module") + for klass in classes: + self.allowTypes(qual(klass)) + self.allowModules(klass.__module__) + self.allowedClasses[klass] = 1 + + + def allowModules(self, *modules): + """ + SecurityOptions.allowModules(module, module, ...): allow modules by + name. This will also allow the 'module' type. + """ + for module in modules: + if type(module) == types.ModuleType: + module = module.__name__ + + if not isinstance(module, bytes): + module = module.encode('utf-8') + + self.allowedModules[module] = 1 + + + def isModuleAllowed(self, moduleName): + """ + SecurityOptions.isModuleAllowed(moduleName) -> boolean + returns 1 if a module by that name is allowed, 0 otherwise + """ + if not isinstance(moduleName, bytes): + moduleName = moduleName.encode('utf-8') + + return moduleName in self.allowedModules + + + def isClassAllowed(self, klass): + """ + SecurityOptions.isClassAllowed(class) -> boolean + Assumes the module has already been allowed. Returns 1 if the given + class is allowed, 0 otherwise. + """ + return klass in self.allowedClasses + + + def isTypeAllowed(self, typeName): + """ + SecurityOptions.isTypeAllowed(typeName) -> boolean + Returns 1 if the given type is allowed, 0 otherwise. + """ + if not isinstance(typeName, bytes): + typeName = typeName.encode('utf-8') + + return (typeName in self.allowedTypes or b'.' in typeName) + + +globalSecurity = SecurityOptions() +globalSecurity.allowBasicTypes() + + + +def jelly(object, taster=DummySecurityOptions(), persistentStore=None, + invoker=None): + """ + Serialize to s-expression. + + Returns a list which is the serialized representation of an object. An + optional 'taster' argument takes a SecurityOptions and will mark any + insecure objects as unpersistable rather than serializing them. + """ + return _Jellier(taster, persistentStore, invoker).jelly(object) + + + +def unjelly(sexp, taster=DummySecurityOptions(), persistentLoad=None, + invoker=None): + """ + Unserialize from s-expression. + + Takes a list that was the result from a call to jelly() and unserializes + an arbitrary object from it. The optional 'taster' argument, an instance + of SecurityOptions, will cause an InsecureJelly exception to be raised if a + disallowed type, module, or class attempted to unserialize. + """ + return _Unjellier(taster, persistentLoad, invoker).unjellyFull(sexp) |