1 # -*- test-case-name: twisted.test.test_jelly -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 S-expression-based persistence of python objects.
8 It does something very much like L{Pickle<pickle>}; however, pickle's main goal
9 seems to be efficiency (both in space and time); jelly's main goals are
10 security, human readability, and portability to other environments.
12 This is how Jelly converts various objects to s-expressions.
15 True --> ['boolean', 'true']
21 [1, 2] --> ['list', 1, 2]
24 \"hello\" --> \"hello\"
30 {'a': 1, 'b': 'c'} --> ['dictionary', ['b', 'c'], ['a', 1]]
33 UserString --> ['module', 'UserString']
36 UserString.UserString --> ['class', ['module', 'UserString'], 'UserString']
39 string.join --> ['function', 'join', ['module', 'string']]
41 Instance: s is an instance of UserString.UserString, with a __dict__
43 [\"UserString.UserString\", ['dictionary', ['data', 'hello']]]
45 Class Method: UserString.UserString.center::
46 ['method', 'center', ['None'], ['class', ['module', 'UserString'],
49 Instance Method: s.center, where s is an instance of UserString.UserString::
50 ['method', 'center', ['instance', ['reference', 1, ['class',
51 ['module', 'UserString'], 'UserString']], ['dictionary', ['data', 'd']]],
54 The C{set} builtin and the C{sets.Set} class are serialized to the same
55 thing, and unserialized to C{set} if available, else to C{sets.Set}. It means
56 that there's a possibility of type switching in the serialization process. The
57 solution is to always use C{set} if possible, and only use C{sets.Set} under
58 Python 2.3; this can be accomplished by using L{twisted.python.compat.set}.
60 The same rule applies for C{frozenset} and C{sets.ImmutableSet}.
62 @author: Glyph Lefkowitz
69 from types import StringType
70 from types import UnicodeType
71 from types import IntType
72 from types import TupleType
73 from types import ListType
74 from types import LongType
75 from types import FloatType
76 from types import FunctionType
77 from types import MethodType
78 from types import ModuleType
79 from types import DictionaryType
80 from types import InstanceType
81 from types import NoneType
82 from types import ClassType
86 from types import BooleanType
99 # Filter out deprecation warning for Python >= 2.6
100 warnings.filterwarnings("ignore", category=DeprecationWarning,
101 message="the sets module is deprecated", append=True)
104 warnings.filters.pop()
107 from zope.interface import implements
110 from twisted.python.reflect import namedObject, qual
111 from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod
112 from twisted.persisted.crefutil import _DictKeyAndValue, _Dereference
113 from twisted.persisted.crefutil import _Container
114 from twisted.python.compat import reduce
116 from twisted.spread.interfaces import IJellyable, IUnjellyable
118 DictTypes = (DictionaryType,)
120 None_atom = "None" # N
122 class_atom = "class" # c
123 module_atom = "module" # m
124 function_atom = "function" # f
127 dereference_atom = 'dereference' # D
128 persistent_atom = 'persistent' # p
129 reference_atom = 'reference' # r
131 # mutable collections
132 dictionary_atom = "dictionary" # d
133 list_atom = 'list' # l
136 # immutable collections
137 # (assignment to __dict__ and __class__ still might go away!)
138 tuple_atom = "tuple" # t
139 instance_atom = 'instance' # i
140 frozenset_atom = 'frozenset'
144 unpersistable_atom = "unpersistable"# u
145 unjellyableRegistry = {}
146 unjellyableFactoryRegistry = {}
150 def _newInstance(cls, state=_NO_STATE):
152 Make a new instance of a class without calling its __init__ method.
153 Supports both new- and old-style classes.
155 @param state: A C{dict} used to update C{inst.__dict__} or C{_NO_STATE}
156 to skip this part of initialization.
158 @return: A new instance of C{cls}.
160 if not isinstance(cls, types.ClassType):
162 inst = cls.__new__(cls)
164 if state is not _NO_STATE:
165 inst.__dict__.update(state) # Copy 'instance' behaviour
167 if state is not _NO_STATE:
168 inst = InstanceType(cls, state)
170 inst = InstanceType(cls)
175 def _maybeClass(classnamep):
181 isObject = isinstance(classnamep, type)
182 if isinstance(classnamep, ClassType) or isObject:
183 return qual(classnamep)
188 def setUnjellyableForClass(classname, unjellyable):
190 Set which local class will represent a remote type.
192 If you have written a Copyable class that you expect your client to be
193 receiving, write a local "copy" class to represent it, then call::
195 jellier.setUnjellyableForClass('module.package.Class', MyCopier).
197 Call this at the module level immediately after its class
198 definition. MyCopier should be a subclass of RemoteCopy.
200 The classname may be a special tag returned by
201 'Copyable.getTypeToCopyFor' rather than an actual classname.
203 This call is also for cached classes, since there will be no
204 overlap. The rules are the same.
207 global unjellyableRegistry
208 classname = _maybeClass(classname)
209 unjellyableRegistry[classname] = unjellyable
210 globalSecurity.allowTypes(classname)
214 def setUnjellyableFactoryForClass(classname, copyFactory):
216 Set the factory to construct a remote instance of a type::
218 jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory)
220 Call this at the module level immediately after its class definition.
221 C{copyFactory} should return an instance or subclass of
222 L{RemoteCopy<pb.RemoteCopy>}.
224 Similar to L{setUnjellyableForClass} except it uses a factory instead
225 of creating an instance.
228 global unjellyableFactoryRegistry
229 classname = _maybeClass(classname)
230 unjellyableFactoryRegistry[classname] = copyFactory
231 globalSecurity.allowTypes(classname)
235 def setUnjellyableForClassTree(module, baseClass, prefix=None):
237 Set all classes in a module derived from C{baseClass} as copiers for
238 a corresponding remote class.
240 When you have a heirarchy of Copyable (or Cacheable) classes on one
241 side, and a mirror structure of Copied (or RemoteCache) classes on the
242 other, use this to setUnjellyableForClass all your Copieds for the
245 Each copyTag (the \"classname\" argument to getTypeToCopyFor, and
246 what the Copyable's getTypeToCopyFor returns) is formed from
247 adding a prefix to the Copied's class name. The prefix defaults
248 to module.__name__. If you wish the copy tag to consist of solely
249 the classname, pass the empty string \'\'.
251 @param module: a module object from which to pull the Copied classes.
252 (passing sys.modules[__name__] might be useful)
254 @param baseClass: the base class from which all your Copied classes derive.
256 @param prefix: the string prefixed to classnames to form the
260 prefix = module.__name__
263 prefix = "%s." % prefix
265 for i in dir(module):
266 i_ = getattr(module, i)
267 if type(i_) == types.ClassType:
268 if issubclass(i_, baseClass):
269 setUnjellyableForClass('%s%s' % (prefix, i), i_)
273 def getInstanceState(inst, jellier):
275 Utility method to default to 'normal' state rules in serialization.
277 if hasattr(inst, "__getstate__"):
278 state = inst.__getstate__()
280 state = inst.__dict__
281 sxp = jellier.prepare(inst)
282 sxp.extend([qual(inst.__class__), jellier.jelly(state)])
283 return jellier.preserve(inst, sxp)
287 def setInstanceState(inst, unjellier, jellyList):
289 Utility method to default to 'normal' state rules in unserialization.
291 state = unjellier.unjelly(jellyList[1])
292 if hasattr(inst, "__setstate__"):
293 inst.__setstate__(state)
295 inst.__dict__ = state
302 This is an instance of a class that comes back when something couldn't be
306 def __init__(self, reason):
308 Initialize an unpersistable object with a descriptive C{reason} string.
314 return "Unpersistable(%s)" % repr(self.reason)
320 Inherit from me to Jelly yourself directly with the `getStateFor'
323 implements(IJellyable)
325 def getStateFor(self, jellier):
329 def jellyFor(self, jellier):
331 @see: L{twisted.spread.interfaces.IJellyable.jellyFor}
333 sxp = jellier.prepare(self)
335 qual(self.__class__),
336 jellier.jelly(self.getStateFor(jellier))])
337 return jellier.preserve(self, sxp)
343 Inherit from me to Unjelly yourself directly with the
344 C{setStateFor} convenience method.
346 implements(IUnjellyable)
348 def setStateFor(self, unjellier, state):
349 self.__dict__ = state
352 def unjellyFor(self, unjellier, jellyList):
354 Perform the inverse operation of L{Jellyable.jellyFor}.
356 @see: L{twisted.spread.interfaces.IUnjellyable.unjellyFor}
358 state = unjellier.unjelly(jellyList[1])
359 self.setStateFor(unjellier, state)
366 (Internal) This class manages state for a call to jelly()
369 def __init__(self, taster, persistentStore, invoker):
374 # `preserved' is a dict of previously seen instances.
376 # `cooked' is a dict of previously backreferenced instances to their
381 self.persistentStore = persistentStore
382 self.invoker = invoker
385 def _cook(self, object):
387 (internal) Backreference an object.
389 Notes on this method for the hapless future maintainer: If I've already
390 gone through the prepare/preserve cycle on the specified object (it is
391 being referenced after the serializer is \"done with\" it, e.g. this
392 reference is NOT circular), the copy-in-place of aList is relevant,
393 since the list being modified is the actual, pre-existing jelly
394 expression that was returned for that object. If not, it's technically
395 superfluous, since the value in self.preserved didn't need to be set,
396 but the invariant that self.preserved[id(object)] is a list is
397 convenient because that means we don't have to test and create it or
398 not create it here, creating fewer code-paths. that's why
399 self.preserved is always set to a list.
401 Sorry that this code is so hard to follow, but Python objects are
402 tricky to persist correctly. -glyph
404 aList = self.preserved[id(object)]
405 newList = copy.copy(aList)
406 # make a new reference ID
408 self._ref_id = self._ref_id + 1
409 # replace the old list in-place, so that we don't have to track the
410 # previous reference to it.
411 aList[:] = [reference_atom, refid, newList]
412 self.cooked[id(object)] = [dereference_atom, refid]
416 def prepare(self, object):
418 (internal) Create a list for persisting an object to. This will allow
419 backreferences to be made internal to the object. (circular
422 The reason this needs to happen is that we don't generate an ID for
423 every object, so we won't necessarily know which ID the object will
424 have in the future. When it is 'cooked' ( see _cook ), it will be
425 assigned an ID, and the temporary placeholder list created here will be
426 modified in-place to create an expression that gives this object an ID:
427 [reference id# [object-jelly]].
430 # create a placeholder list to be preserved
431 self.preserved[id(object)] = []
432 # keep a reference to this object around, so it doesn't disappear!
433 # (This isn't always necessary, but for cases where the objects are
434 # dynamically generated by __getstate__ or getStateToCopyFor calls, it
435 # is; id() will return the same value for a different object if it gets
436 # garbage collected. This may be optimized later.)
437 self.cooker[id(object)] = object
441 def preserve(self, object, sexp):
443 (internal) Mark an object's persistent list for later referral.
445 # if I've been cooked in the meanwhile,
446 if id(object) in self.cooked:
447 # replace the placeholder empty list with the real one
448 self.preserved[id(object)][2] = sexp
449 # but give this one back.
450 sexp = self.preserved[id(object)]
452 self.preserved[id(object)] = sexp
455 constantTypes = {types.StringType : 1, types.IntType : 1,
456 types.FloatType : 1, types.LongType : 1}
459 def _checkMutable(self,obj):
461 if objId in self.cooked:
462 return self.cooked[objId]
463 if objId in self.preserved:
465 return self.cooked[objId]
468 def jelly(self, obj):
469 if isinstance(obj, Jellyable):
470 preRef = self._checkMutable(obj)
473 return obj.jellyFor(self)
475 if self.taster.isTypeAllowed(qual(objType)):
477 if ((objType is StringType) or
478 (objType is IntType) or
479 (objType is LongType) or
480 (objType is FloatType)):
482 elif objType is MethodType:
484 obj.im_func.__name__,
485 self.jelly(obj.im_self),
486 self.jelly(obj.im_class)]
488 elif UnicodeType and objType is UnicodeType:
489 return ['unicode', obj.encode('UTF-8')]
490 elif objType is NoneType:
492 elif objType is FunctionType:
494 return ['function', str(pickle.whichmodule(obj, obj.__name__))
497 elif objType is ModuleType:
498 return ['module', obj.__name__]
499 elif objType is BooleanType:
500 return ['boolean', obj and 'true' or 'false']
501 elif objType is datetime.datetime:
503 raise NotImplementedError(
504 "Currently can't jelly datetime objects with tzinfo")
505 return ['datetime', '%s %s %s %s %s %s %s' % (
506 obj.year, obj.month, obj.day, obj.hour,
507 obj.minute, obj.second, obj.microsecond)]
508 elif objType is datetime.time:
510 raise NotImplementedError(
511 "Currently can't jelly datetime objects with tzinfo")
512 return ['time', '%s %s %s %s' % (obj.hour, obj.minute,
513 obj.second, obj.microsecond)]
514 elif objType is datetime.date:
515 return ['date', '%s %s %s' % (obj.year, obj.month, obj.day)]
516 elif objType is datetime.timedelta:
517 return ['timedelta', '%s %s %s' % (obj.days, obj.seconds,
519 elif objType is ClassType or issubclass(objType, type):
520 return ['class', qual(obj)]
521 elif decimal is not None and objType is decimal.Decimal:
522 return self.jelly_decimal(obj)
524 preRef = self._checkMutable(obj)
528 sxp = self.prepare(obj)
529 if objType is ListType:
530 sxp.extend(self._jellyIterable(list_atom, obj))
531 elif objType is TupleType:
532 sxp.extend(self._jellyIterable(tuple_atom, obj))
533 elif objType in DictTypes:
534 sxp.append(dictionary_atom)
535 for key, val in obj.items():
536 sxp.append([self.jelly(key), self.jelly(val)])
537 elif (_set is not None and objType is set or
538 objType is _sets.Set):
539 sxp.extend(self._jellyIterable(set_atom, obj))
540 elif (_set is not None and objType is frozenset or
541 objType is _sets.ImmutableSet):
542 sxp.extend(self._jellyIterable(frozenset_atom, obj))
544 className = qual(obj.__class__)
546 if self.persistentStore:
547 persistent = self.persistentStore(obj, self)
548 if persistent is not None:
549 sxp.append(persistent_atom)
550 sxp.append(persistent)
551 elif self.taster.isClassAllowed(obj.__class__):
552 sxp.append(className)
553 if hasattr(obj, "__getstate__"):
554 state = obj.__getstate__()
557 sxp.append(self.jelly(state))
560 "instance of class %s deemed insecure" %
561 qual(obj.__class__), sxp)
562 return self.preserve(obj, sxp)
564 if objType is InstanceType:
565 raise InsecureJelly("Class not allowed for instance: %s %s" %
566 (obj.__class__, obj))
567 raise InsecureJelly("Type not allowed for object: %s %s" %
571 def _jellyIterable(self, atom, obj):
573 Jelly an iterable object.
575 @param atom: the identifier atom of the object.
578 @param obj: any iterable object.
579 @type obj: C{iterable}
581 @return: a generator of jellied data.
586 yield self.jelly(item)
589 def jelly_decimal(self, d):
591 Jelly a decimal object.
593 @param d: a decimal object to serialize.
594 @type d: C{decimal.Decimal}
596 @return: jelly for the decimal object.
599 sign, guts, exponent = d.as_tuple()
600 value = reduce(lambda left, right: left * 10 + right, guts)
603 return ['decimal', value, exponent]
606 def unpersistable(self, reason, sxp=None):
608 (internal) Returns an sexp: (unpersistable "reason"). Utility method
609 for making note that a particular object could not be serialized.
613 sxp.append(unpersistable_atom)
621 def __init__(self, taster, persistentLoad, invoker):
623 self.persistentLoad = persistentLoad
625 self.postCallbacks = []
626 self.invoker = invoker
629 def unjellyFull(self, obj):
630 o = self.unjelly(obj)
631 for m in self.postCallbacks:
636 def unjelly(self, obj):
637 if type(obj) is not types.ListType:
640 if not self.taster.isTypeAllowed(jelType):
641 raise InsecureJelly(jelType)
642 regClass = unjellyableRegistry.get(jelType)
643 if regClass is not None:
644 if isinstance(regClass, ClassType):
645 inst = _Dummy() # XXX chomp, chomp
646 inst.__class__ = regClass
647 method = inst.unjellyFor
648 elif isinstance(regClass, type):
649 # regClass.__new__ does not call regClass.__init__
650 inst = regClass.__new__(regClass)
651 method = inst.unjellyFor
653 method = regClass # this is how it ought to be done
654 val = method(self, obj)
655 if hasattr(val, 'postUnjelly'):
656 self.postCallbacks.append(inst.postUnjelly)
658 regFactory = unjellyableFactoryRegistry.get(jelType)
659 if regFactory is not None:
660 state = self.unjelly(obj[1])
661 inst = regFactory(state)
662 if hasattr(inst, 'postUnjelly'):
663 self.postCallbacks.append(inst.postUnjelly)
665 thunk = getattr(self, '_unjelly_%s'%jelType, None)
666 if thunk is not None:
669 nameSplit = jelType.split('.')
670 modName = '.'.join(nameSplit[:-1])
671 if not self.taster.isModuleAllowed(modName):
673 "Module %s not allowed (in type %s)." % (modName, jelType))
674 clz = namedObject(jelType)
675 if not self.taster.isClassAllowed(clz):
676 raise InsecureJelly("Class %s not allowed." % jelType)
677 if hasattr(clz, "__setstate__"):
678 ret = _newInstance(clz)
679 state = self.unjelly(obj[1])
680 ret.__setstate__(state)
682 state = self.unjelly(obj[1])
683 ret = _newInstance(clz, state)
684 if hasattr(clz, 'postUnjelly'):
685 self.postCallbacks.append(ret.postUnjelly)
689 def _unjelly_None(self, exp):
693 def _unjelly_unicode(self, exp):
695 return unicode(exp[0], "UTF-8")
697 return Unpersistable("Could not unpersist unicode: %s" % (exp[0],))
700 def _unjelly_decimal(self, exp):
702 Unjelly decimal objects, if decimal is available. If not, return a
703 L{Unpersistable} object instead.
706 return Unpersistable(
707 "Could not unpersist decimal: %s" % (exp[0] * (10**exp[1]),))
714 guts = decimal.Decimal(value).as_tuple()[1]
715 return decimal.Decimal((sign, guts, exponent))
718 def _unjelly_boolean(self, exp):
720 assert exp[0] in ('true', 'false')
721 return exp[0] == 'true'
723 return Unpersistable("Could not unpersist boolean: %s" % (exp[0],))
726 def _unjelly_datetime(self, exp):
727 return datetime.datetime(*map(int, exp[0].split()))
730 def _unjelly_date(self, exp):
731 return datetime.date(*map(int, exp[0].split()))
734 def _unjelly_time(self, exp):
735 return datetime.time(*map(int, exp[0].split()))
738 def _unjelly_timedelta(self, exp):
739 days, seconds, microseconds = map(int, exp[0].split())
740 return datetime.timedelta(
741 days=days, seconds=seconds, microseconds=microseconds)
744 def unjellyInto(self, obj, loc, jel):
745 o = self.unjelly(jel)
746 if isinstance(o, NotKnown):
747 o.addDependant(obj, loc)
752 def _unjelly_dereference(self, lst):
754 x = self.references.get(refid)
757 der = _Dereference(refid)
758 self.references[refid] = der
762 def _unjelly_reference(self, lst):
765 o = self.unjelly(exp)
766 ref = self.references.get(refid)
768 self.references[refid] = o
769 elif isinstance(ref, NotKnown):
770 ref.resolveDependants(o)
771 self.references[refid] = o
773 assert 0, "Multiple references with same ID!"
777 def _unjelly_tuple(self, lst):
781 if isinstance(self.unjellyInto(l, elem, lst[elem]), NotKnown):
789 def _unjelly_list(self, lst):
792 self.unjellyInto(l, elem, lst[elem])
796 def _unjellySetOrFrozenset(self, lst, containerType):
798 Helper method to unjelly set or frozenset.
800 @param lst: the content of the set.
803 @param containerType: the type of C{set} to use.
808 data = self.unjellyInto(l, elem, lst[elem])
809 if isinstance(data, NotKnown):
812 return _Container(l, containerType)
814 return containerType(l)
817 def _unjelly_set(self, lst):
819 Unjelly set using either the C{set} builtin if available, or
820 C{sets.Set} as fallback.
825 containerType = _sets.Set
826 return self._unjellySetOrFrozenset(lst, containerType)
829 def _unjelly_frozenset(self, lst):
831 Unjelly frozenset using either the C{frozenset} builtin if available,
832 or C{sets.ImmutableSet} as fallback.
835 containerType = frozenset
837 containerType = _sets.ImmutableSet
838 return self._unjellySetOrFrozenset(lst, containerType)
841 def _unjelly_dictionary(self, lst):
844 kvd = _DictKeyAndValue(d)
845 self.unjellyInto(kvd, 0, k)
846 self.unjellyInto(kvd, 1, v)
850 def _unjelly_module(self, rest):
852 if type(moduleName) != types.StringType:
854 "Attempted to unjelly a module with a non-string name.")
855 if not self.taster.isModuleAllowed(moduleName):
857 "Attempted to unjelly module named %r" % (moduleName,))
858 mod = __import__(moduleName, {}, {},"x")
862 def _unjelly_class(self, rest):
863 clist = rest[0].split('.')
864 modName = '.'.join(clist[:-1])
865 if not self.taster.isModuleAllowed(modName):
866 raise InsecureJelly("module %s not allowed" % modName)
867 klaus = namedObject(rest[0])
868 objType = type(klaus)
869 if objType not in (types.ClassType, types.TypeType):
871 "class %r unjellied to something that isn't a class: %r" % (
873 if not self.taster.isClassAllowed(klaus):
874 raise InsecureJelly("class not allowed: %s" % qual(klaus))
878 def _unjelly_function(self, rest):
879 modSplit = rest[0].split('.')
880 modName = '.'.join(modSplit[:-1])
881 if not self.taster.isModuleAllowed(modName):
882 raise InsecureJelly("Module not allowed: %s"% modName)
883 # XXX do I need an isFunctionAllowed?
884 function = namedObject(rest[0])
888 def _unjelly_persistent(self, rest):
889 if self.persistentLoad:
890 pload = self.persistentLoad(rest[0], self)
893 return Unpersistable("Persistent callback not found")
896 def _unjelly_instance(self, rest):
897 clz = self.unjelly(rest[0])
898 if type(clz) is not types.ClassType:
899 raise InsecureJelly("Instance found with non-class class.")
900 if hasattr(clz, "__setstate__"):
901 inst = _newInstance(clz, {})
902 state = self.unjelly(rest[1])
903 inst.__setstate__(state)
905 state = self.unjelly(rest[1])
906 inst = _newInstance(clz, state)
907 if hasattr(clz, 'postUnjelly'):
908 self.postCallbacks.append(inst.postUnjelly)
912 def _unjelly_unpersistable(self, rest):
913 return Unpersistable("Unpersistable data: %s" % (rest[0],))
916 def _unjelly_method(self, rest):
918 (internal) Unjelly a method.
921 im_self = self.unjelly(rest[1])
922 im_class = self.unjelly(rest[2])
923 if type(im_class) is not types.ClassType:
924 raise InsecureJelly("Method found with non-class class.")
925 if im_name in im_class.__dict__:
927 im = getattr(im_class, im_name)
928 elif isinstance(im_self, NotKnown):
929 im = _InstanceMethod(im_name, im_self, im_class)
931 im = MethodType(im_class.__dict__[im_name], im_self, im_class)
933 raise TypeError('instance method changed')
940 (Internal) Dummy class, used for unserializing instances.
945 class _DummyNewStyle(object):
947 (Internal) Dummy class, used for unserializing instances of new-style
952 def _newDummyLike(instance):
954 Create a new instance like C{instance}.
956 The new instance has the same class and instance dictionary as the given
959 @return: The new instance.
961 if isinstance(instance.__class__, type):
963 dummy = _DummyNewStyle()
967 dummy.__class__ = instance.__class__
968 dummy.__dict__ = instance.__dict__
972 #### Published Interface.
975 class InsecureJelly(Exception):
977 This exception will be raised when a jelly is deemed `insecure'; e.g. it
978 contains a type, class, or module disallowed by the specified `taster'
983 class DummySecurityOptions:
985 DummySecurityOptions() -> insecure security options
986 Dummy security options -- this class will allow anything.
989 def isModuleAllowed(self, moduleName):
991 DummySecurityOptions.isModuleAllowed(moduleName) -> boolean
992 returns 1 if a module by that name is allowed, 0 otherwise
997 def isClassAllowed(self, klass):
999 DummySecurityOptions.isClassAllowed(class) -> boolean
1000 Assumes the module has already been allowed. Returns 1 if the given
1001 class is allowed, 0 otherwise.
1006 def isTypeAllowed(self, typeName):
1008 DummySecurityOptions.isTypeAllowed(typeName) -> boolean
1009 Returns 1 if the given type is allowed, 0 otherwise.
1015 class SecurityOptions:
1017 This will by default disallow everything, except for 'none'.
1020 basicTypes = ["dictionary", "list", "tuple",
1021 "reference", "dereference", "unpersistable",
1022 "persistent", "long_int", "long", "dict"]
1026 SecurityOptions() initialize.
1028 # I don't believe any of these types can ever pose a security hazard,
1029 # except perhaps "reference"...
1030 self.allowedTypes = {"None": 1,
1042 if hasattr(types, 'UnicodeType'):
1043 self.allowedTypes['unicode'] = 1
1044 if decimal is not None:
1045 self.allowedTypes['decimal'] = 1
1046 self.allowedTypes['set'] = 1
1047 self.allowedTypes['frozenset'] = 1
1048 self.allowedModules = {}
1049 self.allowedClasses = {}
1052 def allowBasicTypes(self):
1054 Allow all `basic' types. (Dictionary and list. Int, string, and float
1055 are implicitly allowed.)
1057 self.allowTypes(*self.basicTypes)
1060 def allowTypes(self, *types):
1062 SecurityOptions.allowTypes(typeString): Allow a particular type, by its
1066 if not isinstance(typ, str):
1068 self.allowedTypes[typ] = 1
1071 def allowInstancesOf(self, *classes):
1073 SecurityOptions.allowInstances(klass, klass, ...): allow instances
1074 of the specified classes
1076 This will also allow the 'instance', 'class' (renamed 'classobj' in
1077 Python 2.3), and 'module' types, as well as basic types.
1079 self.allowBasicTypes()
1080 self.allowTypes("instance", "class", "classobj", "module")
1081 for klass in classes:
1082 self.allowTypes(qual(klass))
1083 self.allowModules(klass.__module__)
1084 self.allowedClasses[klass] = 1
1087 def allowModules(self, *modules):
1089 SecurityOptions.allowModules(module, module, ...): allow modules by
1090 name. This will also allow the 'module' type.
1092 for module in modules:
1093 if type(module) == types.ModuleType:
1094 module = module.__name__
1095 self.allowedModules[module] = 1
1098 def isModuleAllowed(self, moduleName):
1100 SecurityOptions.isModuleAllowed(moduleName) -> boolean
1101 returns 1 if a module by that name is allowed, 0 otherwise
1103 return moduleName in self.allowedModules
1106 def isClassAllowed(self, klass):
1108 SecurityOptions.isClassAllowed(class) -> boolean
1109 Assumes the module has already been allowed. Returns 1 if the given
1110 class is allowed, 0 otherwise.
1112 return klass in self.allowedClasses
1115 def isTypeAllowed(self, typeName):
1117 SecurityOptions.isTypeAllowed(typeName) -> boolean
1118 Returns 1 if the given type is allowed, 0 otherwise.
1120 return (typeName in self.allowedTypes or '.' in typeName)
1123 globalSecurity = SecurityOptions()
1124 globalSecurity.allowBasicTypes()
1128 def jelly(object, taster=DummySecurityOptions(), persistentStore=None,
1131 Serialize to s-expression.
1133 Returns a list which is the serialized representation of an object. An
1134 optional 'taster' argument takes a SecurityOptions and will mark any
1135 insecure objects as unpersistable rather than serializing them.
1137 return _Jellier(taster, persistentStore, invoker).jelly(object)
1141 def unjelly(sexp, taster=DummySecurityOptions(), persistentLoad=None,
1144 Unserialize from s-expression.
1146 Takes an list that was the result from a call to jelly() and unserializes
1147 an arbitrary object from it. The optional 'taster' argument, an instance
1148 of SecurityOptions, will cause an InsecureJelly exception to be raised if a
1149 disallowed type, module, or class attempted to unserialize.
1151 return _Unjellier(taster, persistentLoad, invoker).unjellyFull(sexp)