root / trunk / twisted / spread / jelly.py

Revision 26752, 35.3 kB (checked in by exarkun, 2 months ago)

Merge type-jelly-2950-2

Author: james@tri-tech.com, therve
Reviewer: bigdog, exarkun
Fixes: #2950

Change jelly to consider instances of type to be classes, as it already
considered instances of ClassType to be classes. This causes it to
support unjellying new-style classes in addition to classic classes.

Line 
1 # -*- test-case-name: twisted.test.test_jelly -*-
2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 S-expression-based persistence of python objects.
7
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.
11
12 This is how Jelly converts various objects to s-expressions.
13
14 Boolean::
15     True --> ['boolean', 'true']
16
17 Integer::
18     1 --> 1
19
20 List::
21     [1, 2] --> ['list', 1, 2]
22
23 String::
24     \"hello\" --> \"hello\"
25
26 Float::
27     2.3 --> 2.3
28
29 Dictionary::
30     {'a': 1, 'b': 'c'} --> ['dictionary', ['b', 'c'], ['a', 1]]
31
32 Module::
33     UserString --> ['module', 'UserString']
34
35 Class::
36     UserString.UserString --> ['class', ['module', 'UserString'], 'UserString']
37
38 Function::
39     string.join --> ['function', 'join', ['module', 'string']]
40
41 Instance: s is an instance of UserString.UserString, with a __dict__
42 {'data': 'hello'}::
43     [\"UserString.UserString\", ['dictionary', ['data', 'hello']]]
44
45 Class Method: UserString.UserString.center::
46     ['method', 'center', ['None'], ['class', ['module', 'UserString'],
47      'UserString']]
48
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']]],
52     ['dereference', 1]]
53
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}.
59
60 The same rule applies for C{frozenset} and C{sets.ImmutableSet}.
61
62 @author: Glyph Lefkowitz
63 """
64
65 # System Imports
66 import pickle
67 import types
68 import warnings
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
83 import copy
84
85 import datetime
86 from types import BooleanType
87
88 try:
89     import decimal
90 except ImportError:
91     decimal = None
92
93 try:
94     _set = set
95 except NameError:
96     _set = None
97
98 try:
99     # Filter out deprecation warning for Python >= 2.6
100     warnings.filterwarnings("ignore", category=DeprecationWarning,
101         message="the sets module is deprecated", append=True)
102     import sets as _sets
103 finally:
104     warnings.filters.pop()
105
106
107 from new import instance
108 from new import instancemethod
109 from zope.interface import implements
110
111 # Twisted Imports
112 from twisted.python.reflect import namedObject, qual
113 from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod
114 from twisted.persisted.crefutil import _DictKeyAndValue, _Dereference
115 from twisted.persisted.crefutil import _Container
116 from twisted.python import runtime
117
118 from twisted.spread.interfaces import IJellyable, IUnjellyable
119
120
121 if runtime.platform.getType() == "java":
122     from org.python.core import PyStringMap
123     DictTypes = (DictionaryType, PyStringMap)
124 else:
125     DictTypes = (DictionaryType,)
126
127
128 None_atom = "None"                  # N
129 # code
130 class_atom = "class"                # c
131 module_atom = "module"              # m
132 function_atom = "function"          # f
133
134 # references
135 dereference_atom = 'dereference'    # D
136 persistent_atom = 'persistent'      # p
137 reference_atom = 'reference'        # r
138
139 # mutable collections
140 dictionary_atom = "dictionary"      # d
141 list_atom = 'list'                  # l
142 set_atom = 'set'
143
144 # immutable collections
145 #   (assignment to __dict__ and __class__ still might go away!)
146 tuple_atom = "tuple"                # t
147 instance_atom = 'instance'          # i
148 frozenset_atom = 'frozenset'
149
150
151 # errors
152 unpersistable_atom = "unpersistable"# u
153 unjellyableRegistry = {}
154 unjellyableFactoryRegistry = {}
155
156
157
158 def _newInstance(cls, state):
159     """
160     Make a new instance of a class without calling its __init__ method.
161     'state' will be used to update inst.__dict__ . Supports both new- and
162     old-style classes.
163     """
164     if not isinstance(cls, types.ClassType):
165         # new-style
166         inst = cls.__new__(cls)
167         inst.__dict__.update(state) # Copy 'instance' behaviour
168     else:
169         inst = instance(cls, state)
170     return inst
171
172
173
174 def _maybeClass(classnamep):
175     try:
176         object
177     except NameError:
178         isObject = 0
179     else:
180         isObject = isinstance(classnamep, type)
181     if isinstance(classnamep, ClassType) or isObject:
182         return qual(classnamep)
183     return classnamep
184
185
186
187 def setUnjellyableForClass(classname, unjellyable):
188     """
189     Set which local class will represent a remote type.
190
191     If you have written a Copyable class that you expect your client to be
192     receiving, write a local "copy" class to represent it, then call::
193
194         jellier.setUnjellyableForClass('module.package.Class', MyCopier).
195
196     Call this at the module level immediately after its class
197     definition. MyCopier should be a subclass of RemoteCopy.
198
199     The classname may be a special tag returned by
200     'Copyable.getTypeToCopyFor' rather than an actual classname.
201
202     This call is also for cached classes, since there will be no
203     overlap.  The rules are the same.
204     """
205
206     global unjellyableRegistry
207     classname = _maybeClass(classname)
208     unjellyableRegistry[classname] = unjellyable
209     globalSecurity.allowTypes(classname)
210
211
212
213 def setUnjellyableFactoryForClass(classname, copyFactory):
214     """
215     Set the factory to construct a remote instance of a type::
216
217       jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory)
218
219     Call this at the module level immediately after its class definition.
220     C{copyFactory} should return an instance or subclass of
221     L{RemoteCopy<pb.RemoteCopy>}.
222
223     Similar to L{setUnjellyableForClass} except it uses a factory instead
224     of creating an instance.
225     """
226
227     global unjellyableFactoryRegistry
228     classname = _maybeClass(classname)
229     unjellyableFactoryRegistry[classname] = copyFactory
230     globalSecurity.allowTypes(classname)
231
232
233
234 def setUnjellyableForClassTree(module, baseClass, prefix=None):
235     """
236     Set all classes in a module derived from C{baseClass} as copiers for
237     a corresponding remote class.
238
239     When you have a heirarchy of Copyable (or Cacheable) classes on one
240     side, and a mirror structure of Copied (or RemoteCache) classes on the
241     other, use this to setUnjellyableForClass all your Copieds for the
242     Copyables.
243
244     Each copyTag (the \"classname\" argument to getTypeToCopyFor, and
245     what the Copyable's getTypeToCopyFor returns) is formed from
246     adding a prefix to the Copied's class name.  The prefix defaults
247     to module.__name__.  If you wish the copy tag to consist of solely
248     the classname, pass the empty string \'\'.
249
250     @param module: a module object from which to pull the Copied classes.
251         (passing sys.modules[__name__] might be useful)
252
253     @param baseClass: the base class from which all your Copied classes derive.
254
255     @param prefix: the string prefixed to classnames to form the
256         unjellyableRegistry.
257     """
258     if prefix is None:
259         prefix = module.__name__
260
261     if prefix:
262         prefix = "%s." % prefix
263
264     for i in dir(module):
265         i_ = getattr(module, i)
266         if type(i_) == types.ClassType:
267             if issubclass(i_, baseClass):
268                 setUnjellyableForClass('%s%s' % (prefix, i), i_)
269
270
271
272 def getInstanceState(inst, jellier):
273     """
274     Utility method to default to 'normal' state rules in serialization.
275     """
276     if hasattr(inst, "__getstate__"):
277         state = inst.__getstate__()
278     else:
279         state = inst.__dict__
280     sxp = jellier.prepare(inst)
281     sxp.extend([qual(inst.__class__), jellier.jelly(state)])
282     return jellier.preserve(inst, sxp)
283
284
285
286 def setInstanceState(inst, unjellier, jellyList):
287     """
288     Utility method to default to 'normal' state rules in unserialization.
289     """
290     state = unjellier.unjelly(jellyList[1])
291     if hasattr(inst, "__setstate__"):
292         inst.__setstate__(state)
293     else:
294         inst.__dict__ = state
295     return inst
296
297
298
299 class Unpersistable:
300     """
301     This is an instance of a class that comes back when something couldn't be
302     unpersisted.
303     """
304
305     def __init__(self, reason):
306         """
307         Initialize an unpersistable object with a descriptive C{reason} string.
308         """
309         self.reason = reason
310
311
312     def __repr__(self):
313         return "Unpersistable(%s)" % repr(self.reason)
314
315
316
317 class Jellyable:
318     """
319     Inherit from me to Jelly yourself directly with the `getStateFor'
320     convenience method.
321     """
322     implements(IJellyable)
323
324     def getStateFor(self, jellier):
325         return self.__dict__
326
327
328     def jellyFor(self, jellier):
329         """
330         @see: L{twisted.spread.interfaces.IJellyable.jellyFor}
331         """
332         sxp = jellier.prepare(self)
333         sxp.extend([
334             qual(self.__class__),
335             jellier.jelly(self.getStateFor(jellier))])
336         return jellier.preserve(self, sxp)
337
338
339
340 class Unjellyable:
341     """
342     Inherit from me to Unjelly yourself directly with the
343     C{setStateFor} convenience method.
344     """
345     implements(IUnjellyable)
346
347     def setStateFor(self, unjellier, state):
348         self.__dict__ = state
349
350
351     def unjellyFor(self, unjellier, jellyList):
352         """
353         Perform the inverse operation of L{Jellyable.jellyFor}.
354
355         @see: L{twisted.spread.interfaces.IUnjellyable.unjellyFor}
356         """
357         state = unjellier.unjelly(jellyList[1])
358         self.setStateFor(unjellier, state)
359         return self
360
361
362
363 class _Jellier:
364     """
365     (Internal) This class manages state for a call to jelly()
366     """
367
368     def __init__(self, taster, persistentStore, invoker):
369         """
370         Initialize.
371         """
372         self.taster = taster
373         # `preserved' is a dict of previously seen instances.
374         self.preserved = {}
375         # `cooked' is a dict of previously backreferenced instances to their
376         # `ref' lists.
377         self.cooked = {}
378         self.cooker = {}
379         self._ref_id = 1
380         self.persistentStore = persistentStore
381         self.invoker = invoker
382
383
384     def _cook(self, object):
385         """
386         (internal) Backreference an object.
387
388         Notes on this method for the hapless future maintainer: If I've already
389         gone through the prepare/preserve cycle on the specified object (it is
390         being referenced after the serializer is \"done with\" it, e.g. this
391         reference is NOT circular), the copy-in-place of aList is relevant,
392         since the list being modified is the actual, pre-existing jelly
393         expression that was returned for that object. If not, it's technically
394         superfluous, since the value in self.preserved didn't need to be set,
395         but the invariant that self.preserved[id(object)] is a list is
396         convenient because that means we don't have to test and create it or
397         not create it here, creating fewer code-paths.  that's why
398         self.preserved is always set to a list.
399
400         Sorry that this code is so hard to follow, but Python objects are
401         tricky to persist correctly. -glyph
402         """
403         aList = self.preserved[id(object)]
404         newList = copy.copy(aList)
405         # make a new reference ID
406         refid = self._ref_id
407         self._ref_id = self._ref_id + 1
408         # replace the old list in-place, so that we don't have to track the
409         # previous reference to it.
410         aList[:] = [reference_atom, refid, newList]
411         self.cooked[id(object)] = [dereference_atom, refid]
412         return aList
413
414
415     def prepare(self, object):
416         """
417         (internal) Create a list for persisting an object to.  This will allow
418         backreferences to be made internal to the object. (circular
419         references).
420
421         The reason this needs to happen is that we don't generate an ID for
422         every object, so we won't necessarily know which ID the object will
423         have in the future.  When it is 'cooked' ( see _cook ), it will be
424         assigned an ID, and the temporary placeholder list created here will be
425         modified in-place to create an expression that gives this object an ID:
426         [reference id# [object-jelly]].
427         """
428
429         # create a placeholder list to be preserved
430         self.preserved[id(object)] = []
431         # keep a reference to this object around, so it doesn't disappear!
432         # (This isn't always necessary, but for cases where the objects are
433         # dynamically generated by __getstate__ or getStateToCopyFor calls, it
434         # is; id() will return the same value for a different object if it gets
435         # garbage collected.  This may be optimized later.)
436         self.cooker[id(object)] = object
437         return []
438
439
440     def preserve(self, object, sexp):
441         """
442         (internal) Mark an object's persistent list for later referral.
443         """
444         # if I've been cooked in the meanwhile,
445         if id(object) in self.cooked:
446             # replace the placeholder empty list with the real one
447             self.preserved[id(object)][2] = sexp
448             # but give this one back.
449             sexp = self.preserved[id(object)]
450         else:
451             self.preserved[id(object)] = sexp
452         return sexp
453
454     constantTypes = {types.StringType : 1, types.IntType : 1,
455                      types.FloatType : 1, types.LongType : 1}
456
457
458     def _checkMutable(self,obj):
459         objId = id(obj)
460         if objId in self.cooked:
461             return self.cooked[objId]
462         if objId in self.preserved:
463             self._cook(obj)
464             return self.cooked[objId]
465
466
467     def jelly(self, obj):
468         if isinstance(obj, Jellyable):
469             preRef = self._checkMutable(obj)
470             if preRef:
471                 return preRef
472             return obj.jellyFor(self)
473         objType = type(obj)
474         if self.taster.isTypeAllowed(qual(objType)):
475             # "Immutable" Types
476             if ((objType is StringType) or
477                 (objType is IntType) or
478                 (objType is LongType) or
479                 (objType is FloatType)):
480                 return obj
481             elif objType is MethodType:
482                 return ["method",
483                         obj.im_func.__name__,
484                         self.jelly(obj.im_self),
485                         self.jelly(obj.im_class)]
486
487             elif UnicodeType and objType is UnicodeType:
488                 return ['unicode', obj.encode('UTF-8')]
489             elif objType is NoneType:
490                 return ['None']
491             elif objType is FunctionType:
492                 name = obj.__name__
493                 return ['function', str(pickle.whichmodule(obj, obj.__name__))
494                         + '.' +
495                         name]
496             elif objType is ModuleType:
497                 return ['module', obj.__name__]
498             elif objType is BooleanType:
499                 return ['boolean', obj and 'true' or 'false']
500             elif objType is datetime.datetime:
501                 if obj.tzinfo:
502                     raise NotImplementedError(
503                         "Currently can't jelly datetime objects with tzinfo")
504                 return ['datetime', '%s %s %s %s %s %s %s' % (
505                     obj.year, obj.month, obj.day, obj.hour,
506                     obj.minute, obj.second, obj.microsecond)]
507             elif objType is datetime.time:
508                 if obj.tzinfo:
509                     raise NotImplementedError(
510                         "Currently can't jelly datetime objects with tzinfo")
511                 return ['time', '%s %s %s %s' % (obj.hour, obj.minute,
512                                                  obj.second, obj.microsecond)]
513             elif objType is datetime.date:
514                 return ['date', '%s %s %s' % (obj.year, obj.month, obj.day)]
515             elif objType is datetime.timedelta:
516                 return ['timedelta', '%s %s %s' % (obj.days, obj.seconds,
517                                                    obj.microseconds)]
518             elif objType is ClassType or issubclass(objType, type):
519                 return ['class', qual(obj)]
520             elif decimal is not None and objType is decimal.Decimal:
521                 return self.jelly_decimal(obj)
522             else:
523                 preRef = self._checkMutable(obj)
524                 if preRef:
525                     return preRef
526                 # "Mutable" Types
527                 sxp = self.prepare(obj)
528                 if objType is ListType:
529                     sxp.extend(self._jellyIterable(list_atom, obj))
530                 elif objType is TupleType:
531                     sxp.extend(self._jellyIterable(tuple_atom, obj))
532                 elif objType in DictTypes:
533                     sxp.append(dictionary_atom)
534                     for key, val in obj.items():
535                         sxp.append([self.jelly(key), self.jelly(val)])
536                 elif (_set is not None and objType is set or
537                       objType is _sets.Set):
538                     sxp.extend(self._jellyIterable(set_atom, obj))
539                 elif (_set is not None and objType is frozenset or
540                       objType is _sets.ImmutableSet):
541                     sxp.extend(self._jellyIterable(frozenset_atom, obj))
542                 else:
543                     className = qual(obj.__class__)
544                     persistent = None
545                     if self.persistentStore:
546                         persistent = self.persistentStore(obj, self)
547                     if persistent is not None:
548                         sxp.append(persistent_atom)
549                         sxp.append(persistent)
550                     elif self.taster.isClassAllowed(obj.__class__):
551                         sxp.append(className)
552                         if hasattr(obj, "__getstate__"):
553                             state = obj.__getstate__()
554                         else:
555                             state = obj.__dict__
556                         sxp.append(self.jelly(state))
557                     else:
558                         self.unpersistable(
559                             "instance of class %s deemed insecure" %
560                             qual(obj.__class__), sxp)
561                 return self.preserve(obj, sxp)
562         else:
563             if objType is InstanceType:
564                 raise InsecureJelly("Class not allowed for instance: %s %s" %
565                                     (obj.__class__, obj))
566             raise InsecureJelly("Type not allowed for object: %s %s" %
567                                 (objType, obj))
568
569
570     def _jellyIterable(self, atom, obj):
571         """
572         Jelly an iterable object.
573
574         @param atom: the identifier atom of the object.
575         @type atom: C{str}
576
577         @param obj: any iterable object.
578         @type obj: C{iterable}
579
580         @return: a generator of jellied data.
581         @rtype: C{generator}
582         """
583         yield atom
584         for item in obj:
585             yield self.jelly(item)
586
587
588     def jelly_decimal(self, d):
589         """
590         Jelly a decimal object.
591
592         @param d: a decimal object to serialize.
593         @type d: C{decimal.Decimal}
594
595         @return: jelly for the decimal object.
596         @rtype: C{list}
597         """
598         sign, guts, exponent = d.as_tuple()
599         value = reduce(lambda left, right: left * 10 + right, guts)
600         if sign:
601             value = -value
602         return ['decimal', value, exponent]
603
604
605     def unpersistable(self, reason, sxp=None):
606         """
607         (internal) Returns an sexp: (unpersistable "reason").  Utility method
608         for making note that a particular object could not be serialized.
609         """
610         if sxp is None:
611             sxp = []
612         sxp.append(unpersistable_atom)
613         sxp.append(reason)
614         return sxp
615
616
617
618 class _Unjellier:
619
620     def __init__(self, taster, persistentLoad, invoker):
621         self.taster = taster
622         self.persistentLoad = persistentLoad
623         self.references = {}
624         self.postCallbacks = []
625         self.invoker = invoker
626
627
628     def unjellyFull(self, obj):
629         o = self.unjelly(obj)
630         for m in self.postCallbacks:
631             m()
632         return o
633
634
635     def unjelly(self, obj):
636         if type(obj) is not types.ListType:
637             return obj
638         jelType = obj[0]
639         if not self.taster.isTypeAllowed(jelType):
640             raise InsecureJelly(jelType)
641         regClass = unjellyableRegistry.get(jelType)
642         if regClass is not None:
643             if isinstance(regClass, ClassType):
644                 inst = _Dummy() # XXX chomp, chomp
645                 inst.__class__ = regClass
646                 method = inst.unjellyFor
647             elif isinstance(regClass, type):
648                 # regClass.__new__ does not call regClass.__init__
649                 inst = regClass.__new__(regClass)
650                 method = inst.unjellyFor
651             else:
652                 method = regClass # this is how it ought to be done
653             val = method(self, obj)
654             if hasattr(val, 'postUnjelly'):
655                 self.postCallbacks.append(inst.postUnjelly)
656             return val
657         regFactory = unjellyableFactoryRegistry.get(jelType)
658         if regFactory is not None:
659             state = self.unjelly(obj[1])
660             inst = regFactory(state)
661             if hasattr(inst, 'postUnjelly'):
662                 self.postCallbacks.append(inst.postUnjelly)
663             return inst
664         thunk = getattr(self, '_unjelly_%s'%jelType, None)
665         if thunk is not None:
666             ret = thunk(obj[1:])
667         else:
668             nameSplit = jelType.split('.')
669             modName = '.'.join(nameSplit[:-1])
670             if not self.taster.isModuleAllowed(modName):
671                 raise InsecureJelly(
672                     "Module %s not allowed (in type %s)." % (modName, jelType))
673             clz = namedObject(jelType)
674             if not self.taster.isClassAllowed(clz):
675                 raise InsecureJelly("Class %s not allowed." % jelType)
676             if hasattr(clz, "__setstate__"):
677                 ret = _newInstance(clz, {})
678                 state = self.unjelly(obj[1])
679                 ret.__setstate__(state)
680             else:
681                 state = self.unjelly(obj[1])
682                 ret = _newInstance(clz, state)
683             if hasattr(clz, 'postUnjelly'):
684                 self.postCallbacks.append(ret.postUnjelly)
685         return ret
686
687
688     def _unjelly_None(self, exp):
689         return None
690
691
692     def _unjelly_unicode(self, exp):
693         if UnicodeType:
694             return unicode(exp[0], "UTF-8")
695         else:
696             return Unpersistable("Could not unpersist unicode: %s" % (exp[0],))
697
698
699     def _unjelly_decimal(self, exp):
700         """
701         Unjelly decimal objects, if decimal is available. If not, return a
702         L{Unpersistable} object instead.
703         """
704         if decimal is None:
705             return Unpersistable(
706                 "Could not unpersist decimal: %s" % (exp[0] * (10**exp[1]),))
707         value = exp[0]
708         exponent = exp[1]
709         if value < 0:
710             sign = 1
711         else:
712             sign = 0
713         guts = decimal.Decimal(value).as_tuple()[1]
714         return decimal.Decimal((sign, guts, exponent))
715
716
717     def _unjelly_boolean(self, exp):
718         if BooleanType:
719             assert exp[0] in ('true', 'false')
720             return exp[0] == 'true'
721         else:
722             return Unpersistable("Could not unpersist boolean: %s" % (exp[0],))
723
724
725     def _unjelly_datetime(self, exp):
726         return datetime.datetime(*map(int, exp[0].split()))
727
728
729     def _unjelly_date(self, exp):
730         return datetime.date(*map(int, exp[0].split()))
731
732
733     def _unjelly_time(self, exp):
734         return datetime.time(*map(int, exp[0].split()))
735
736
737     def _unjelly_timedelta(self, exp):
738         days, seconds, microseconds = map(int, exp[0].split())
739         return datetime.timedelta(
740             days=days, seconds=seconds, microseconds=microseconds)
741
742
743     def unjellyInto(self, obj, loc, jel):
744         o = self.unjelly(jel)
745         if isinstance(o, NotKnown):
746             o.addDependant(obj, loc)
747         obj[loc] = o
748         return o
749
750
751     def _unjelly_dereference(self, lst):
752         refid = lst[0]
753         x = self.references.get(refid)
754         if x is not None:
755             return x
756         der = _Dereference(refid)
757         self.references[refid] = der
758         return der
759
760
761     def _unjelly_reference(self, lst):
762         refid = lst[0]
763         exp = lst[1]
764         o = self.unjelly(exp)
765         ref = self.references.get(refid)
766         if (ref is None):
767             self.references[refid] = o
768         elif isinstance(ref, NotKnown):
769             ref.resolveDependants(o)
770             self.references[refid] = o
771         else:
772             assert 0, "Multiple references with same ID!"
773         return o
774
775
776     def _unjelly_tuple(self, lst):
777         l = range(len(lst))
778         finished = 1
779         for elem in l:
780             if isinstance(self.unjellyInto(l, elem, lst[elem]), NotKnown):
781                 finished = 0
782         if finished:
783             return tuple(l)
784         else:
785             return _Tuple(l)
786
787
788     def _unjelly_list(self, lst):
789         l = range(len(lst))
790         for elem in l:
791             self.unjellyInto(l, elem, lst[elem])
792         return l
793
794
795     def _unjellySetOrFrozenset(self, lst, containerType):
796         """
797         Helper method to unjelly set or frozenset.
798
799         @param lst: the content of the set.
800         @type lst: C{list}
801
802         @param containerType: the type of C{set} to use.
803         """
804         l = range(len(lst))
805         finished = True
806         for elem in l:
807             data = self.unjellyInto(l, elem, lst[elem])
808             if isinstance(data, NotKnown):
809                 finished = False
810         if not finished:
811             return _Container(l, containerType)
812         else:
813             return containerType(l)
814
815
816     def _unjelly_set(self, lst):
817         """
818         Unjelly set using either the C{set} builtin if available, or
819         C{sets.Set} as fallback.
820         """
821         if _set is not None:
822             containerType = set
823         else:
824             containerType = _sets.Set
825         return self._unjellySetOrFrozenset(lst, containerType)
826
827
828     def _unjelly_frozenset(self, lst):
829         """
830         Unjelly frozenset using either the C{frozenset} builtin if available,
831         or C{sets.ImmutableSet} as fallback.
832         """
833         if _set is not None:
834             containerType = frozenset
835         else:
836             containerType = _sets.ImmutableSet
837         return self._unjellySetOrFrozenset(lst, containerType)
838
839
840     def _unjelly_dictionary(self, lst):
841         d = {}
842         for k, v in lst:
843             kvd = _DictKeyAndValue(d)
844             self.unjellyInto(kvd, 0, k)
845             self.unjellyInto(kvd, 1, v)
846         return d
847
848
849     def _unjelly_module(self, rest):
850         moduleName = rest[0]
851         if type(moduleName) != types.StringType:
852             raise InsecureJelly(
853                 "Attempted to unjelly a module with a non-string name.")
854         if not self.taster.isModuleAllowed(moduleName):
855             raise InsecureJelly(
856                 "Attempted to unjelly module named %r" % (moduleName,))
857         mod = __import__(moduleName, {}, {},"x")
858         return mod
859
860
861     def _unjelly_class(self, rest):
862         clist = rest[0].split('.')
863         modName = '.'.join(clist[:-1])
864         if not self.taster.isModuleAllowed(modName):
865             raise InsecureJelly("module %s not allowed" % modName)
866         klaus = namedObject(rest[0])
867         objType = type(klaus)
868         if objType not in (types.ClassType, types.TypeType):
869             raise InsecureJelly(
870                 "class %r unjellied to something that isn't a class: %r" % (
871                     rest[0], klaus))
872         if not self.taster.isClassAllowed(klaus):
873             raise InsecureJelly("class not allowed: %s" % qual(klaus))
874         return klaus
875
876
877     def _unjelly_function(self, rest):
878         modSplit = rest[0].split('.')
879         modName = '.'.join(modSplit[:-1])
880         if not self.taster.isModuleAllowed(modName):
881             raise InsecureJelly("Module not allowed: %s"% modName)
882         # XXX do I need an isFunctionAllowed?
883         function = namedObject(rest[0])
884         return function
885
886
887     def _unjelly_persistent(self, rest):
888         if self.persistentLoad:
889             pload = self.persistentLoad(rest[0], self)
890             return pload
891         else:
892             return Unpersistable("Persistent callback not found")
893
894
895     def _unjelly_instance(self, rest):
896         clz = self.unjelly(rest[0])
897         if type(clz) is not types.ClassType:
898             raise InsecureJelly("Instance found with non-class class.")
899         if hasattr(clz, "__setstate__"):
900             inst = _newInstance(clz, {})
901             state = self.unjelly(rest[1])
902             inst.__setstate__(state)
903         else:
904             state = self.unjelly(rest[1])
905             inst = _newInstance(clz, state)
906         if hasattr(clz, 'postUnjelly'):
907             self.postCallbacks.append(inst.postUnjelly)
908         return inst
909
910
911     def _unjelly_unpersistable(self, rest):
912         return Unpersistable("Unpersistable data: %s" % (rest[0],))
913
914
915     def _unjelly_method(self, rest):
916         """
917         (internal) Unjelly a method.
918         """
919         im_name = rest[0]
920         im_self = self.unjelly(rest[1])
921         im_class = self.unjelly(rest[2])
922         if type(im_class) is not types.ClassType:
923             raise InsecureJelly("Method found with non-class class.")
924         if im_name in im_class.__dict__:
925             if im_self is None:
926                 im = getattr(im_class, im_name)
927             elif isinstance(im_self, NotKnown):
928                 im = _InstanceMethod(im_name, im_self, im_class)
929             else:
930                 im = instancemethod(im_class.__dict__[im_name],
931                                     im_self,
932                                     im_class)
933         else:
934             raise TypeError('instance method changed')
935         return im
936
937
938
939 class _Dummy:
940     """
941     (Internal) Dummy class, used for unserializing instances.
942     """
943
944
945
946 class _DummyNewStyle(object):
947     """
948     (Internal) Dummy class, used for unserializing instances of new-style
949     classes.
950     """
951
952
953
954 #### Published Interface.
955
956
957 class InsecureJelly(Exception):
958     """
959     This exception will be raised when a jelly is deemed `insecure'; e.g. it
960     contains a type, class, or module disallowed by the specified `taster'
961     """
962
963
964
965 class DummySecurityOptions:
966     """
967     DummySecurityOptions() -> insecure security options
968     Dummy security options -- this class will allow anything.
969     """
970
971     def isModuleAllowed(self, moduleName):
972         """
973         DummySecurityOptions.isModuleAllowed(moduleName) -> boolean
974         returns 1 if a module by that name is allowed, 0 otherwise
975         """
976         return 1
977
978
979     def isClassAllowed(self, klass):
980         """
981         DummySecurityOptions.isClassAllowed(class) -> boolean
982         Assumes the module has already been allowed.  Returns 1 if the given
983         class is allowed, 0 otherwise.
984         """
985         return 1
986
987
988     def isTypeAllowed(self, typeName):
989         """
990         DummySecurityOptions.isTypeAllowed(typeName) -> boolean
991         Returns 1 if the given type is allowed, 0 otherwise.
992         """
993         return 1
994
995
996
997 class SecurityOptions:
998     """
999     This will by default disallow everything, except for 'none'.
1000     """
1001
1002     basicTypes = ["dictionary", "list", "tuple",
1003                   "reference", "dereference", "unpersistable",
1004                   "persistent", "long_int", "long", "dict"]
1005
1006     def __init__(self):
1007         """
1008         SecurityOptions() initialize.
1009         """
1010         # I don't believe any of these types can ever pose a security hazard,
1011         # except perhaps "reference"...
1012         self.allowedTypes = {"None": 1,
1013                              "bool": 1,
1014                              "boolean": 1,
1015                              "string": 1,
1016                              "str": 1,
1017                              "int": 1,
1018                              "float": 1,
1019                              "datetime": 1,
1020                              "time": 1,
1021                              "date": 1,
1022                              "timedelta": 1,
1023                              "NoneType": 1}
1024         if hasattr(types, 'UnicodeType'):
1025             self.allowedTypes['unicode'] = 1
1026         if decimal is not None:
1027             self.allowedTypes['decimal'] = 1
1028         self.allowedTypes['set'] = 1
1029         self.allowedTypes['frozenset'] = 1
1030         self.allowedModules = {}
1031         self.allowedClasses = {}
1032
1033
1034     def allowBasicTypes(self):
1035         """
1036         Allow all `basic' types.  (Dictionary and list.  Int, string, and float
1037         are implicitly allowed.)
1038         """
1039         self.allowTypes(*self.basicTypes)
1040
1041
1042     def allowTypes(self, *types):
1043         """
1044         SecurityOptions.allowTypes(typeString): Allow a particular type, by its
1045         name.
1046         """
1047         for typ in types:
1048             if not isinstance(typ, str):
1049                 typ = qual(typ)
1050             self.allowedTypes[typ] = 1
1051
1052
1053     def allowInstancesOf(self, *classes):
1054         """
1055         SecurityOptions.allowInstances(klass, klass, ...): allow instances
1056         of the specified classes
1057
1058         This will also allow the 'instance', 'class' (renamed 'classobj' in
1059         Python 2.3), and 'module' types, as well as basic types.
1060         """
1061         self.allowBasicTypes()
1062         self.allowTypes("instance", "class", "classobj", "module")
1063         for klass in classes:
1064             self.allowTypes(qual(klass))
1065             self.allowModules(klass.__module__)
1066             self.allowedClasses[klass] = 1
1067
1068
1069     def allowModules(self, *modules):
1070         """
1071         SecurityOptions.allowModules(module, module, ...): allow modules by
1072         name. This will also allow the 'module' type.
1073         """
1074         for module in modules:
1075             if type(module) == types.ModuleType:
1076                 module = module.__name__
1077             self.allowedModules[module] = 1
1078
1079
1080     def isModuleAllowed(self, moduleName):
1081         """
1082         SecurityOptions.isModuleAllowed(moduleName) -> boolean
1083         returns 1 if a module by that name is allowed, 0 otherwise
1084         """
1085         return moduleName in self.allowedModules
1086
1087
1088     def isClassAllowed(self, klass):
1089         """
1090         SecurityOptions.isClassAllowed(class) -> boolean
1091         Assumes the module has already been allowed.  Returns 1 if the given
1092         class is allowed, 0 otherwise.
1093         """
1094         return klass in self.allowedClasses
1095
1096
1097     def isTypeAllowed(self, typeName):
1098         """
1099         SecurityOptions.isTypeAllowed(typeName) -> boolean
1100         Returns 1 if the given type is allowed, 0 otherwise.
1101         """
1102         return (typeName in self.allowedTypes or '.' in typeName)
1103
1104
1105 globalSecurity = SecurityOptions()
1106 globalSecurity.allowBasicTypes()
1107
1108
1109
1110 def jelly(object, taster=DummySecurityOptions(), persistentStore=None,
1111           invoker=None):
1112     """
1113     Serialize to s-expression.
1114
1115     Returns a list which is the serialized representation of an object.  An
1116     optional 'taster' argument takes a SecurityOptions and will mark any
1117     insecure objects as unpersistable rather than serializing them.
1118     """
1119     return _Jellier(taster, persistentStore, invoker).jelly(object)
1120
1121
1122
1123 def unjelly(sexp, taster=DummySecurityOptions(), persistentLoad=None,
1124             invoker=None):
1125     """
1126     Unserialize from s-expression.
1127
1128     Takes an list that was the result from a call to jelly() and unserializes
1129     an arbitrary object from it.  The optional 'taster' argument, an instance
1130     of SecurityOptions, will cause an InsecureJelly exception to be raised if a
1131     disallowed type, module, or class attempted to unserialize.
1132     """
1133     return _Unjellier(taster, persistentLoad, invoker).unjellyFull(sexp)
Note: See TracBrowser for help on using the browser.