Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / spread / jelly.py
1 # -*- test-case-name: twisted.test.test_jelly -*-
2 # Copyright (c) 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 zope.interface import implements
108
109 # Twisted Imports
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
115
116 from twisted.spread.interfaces import IJellyable, IUnjellyable
117
118 DictTypes = (DictionaryType,)
119
120 None_atom = "None"                  # N
121 # code
122 class_atom = "class"                # c
123 module_atom = "module"              # m
124 function_atom = "function"          # f
125
126 # references
127 dereference_atom = 'dereference'    # D
128 persistent_atom = 'persistent'      # p
129 reference_atom = 'reference'        # r
130
131 # mutable collections
132 dictionary_atom = "dictionary"      # d
133 list_atom = 'list'                  # l
134 set_atom = 'set'
135
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'
141
142
143 # errors
144 unpersistable_atom = "unpersistable"# u
145 unjellyableRegistry = {}
146 unjellyableFactoryRegistry = {}
147
148 _NO_STATE = object()
149
150 def _newInstance(cls, state=_NO_STATE):
151     """
152     Make a new instance of a class without calling its __init__ method.
153     Supports both new- and old-style classes.
154
155     @param state: A C{dict} used to update C{inst.__dict__} or C{_NO_STATE}
156         to skip this part of initialization.
157
158     @return: A new instance of C{cls}.
159     """
160     if not isinstance(cls, types.ClassType):
161         # new-style
162         inst = cls.__new__(cls)
163
164         if state is not _NO_STATE:
165             inst.__dict__.update(state) # Copy 'instance' behaviour
166     else:
167         if state is not _NO_STATE:
168             inst = InstanceType(cls, state)
169         else:
170             inst = InstanceType(cls)
171     return inst
172
173
174
175 def _maybeClass(classnamep):
176     try:
177         object
178     except NameError:
179         isObject = 0
180     else:
181         isObject = isinstance(classnamep, type)
182     if isinstance(classnamep, ClassType) or isObject:
183         return qual(classnamep)
184     return classnamep
185
186
187
188 def setUnjellyableForClass(classname, unjellyable):
189     """
190     Set which local class will represent a remote type.
191
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::
194
195         jellier.setUnjellyableForClass('module.package.Class', MyCopier).
196
197     Call this at the module level immediately after its class
198     definition. MyCopier should be a subclass of RemoteCopy.
199
200     The classname may be a special tag returned by
201     'Copyable.getTypeToCopyFor' rather than an actual classname.
202
203     This call is also for cached classes, since there will be no
204     overlap.  The rules are the same.
205     """
206
207     global unjellyableRegistry
208     classname = _maybeClass(classname)
209     unjellyableRegistry[classname] = unjellyable
210     globalSecurity.allowTypes(classname)
211
212
213
214 def setUnjellyableFactoryForClass(classname, copyFactory):
215     """
216     Set the factory to construct a remote instance of a type::
217
218       jellier.setUnjellyableFactoryForClass('module.package.Class', MyFactory)
219
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>}.
223
224     Similar to L{setUnjellyableForClass} except it uses a factory instead
225     of creating an instance.
226     """
227
228     global unjellyableFactoryRegistry
229     classname = _maybeClass(classname)
230     unjellyableFactoryRegistry[classname] = copyFactory
231     globalSecurity.allowTypes(classname)
232
233
234
235 def setUnjellyableForClassTree(module, baseClass, prefix=None):
236     """
237     Set all classes in a module derived from C{baseClass} as copiers for
238     a corresponding remote class.
239
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
243     Copyables.
244
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 \'\'.
250
251     @param module: a module object from which to pull the Copied classes.
252         (passing sys.modules[__name__] might be useful)
253
254     @param baseClass: the base class from which all your Copied classes derive.
255
256     @param prefix: the string prefixed to classnames to form the
257         unjellyableRegistry.
258     """
259     if prefix is None:
260         prefix = module.__name__
261
262     if prefix:
263         prefix = "%s." % prefix
264
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_)
270
271
272
273 def getInstanceState(inst, jellier):
274     """
275     Utility method to default to 'normal' state rules in serialization.
276     """
277     if hasattr(inst, "__getstate__"):
278         state = inst.__getstate__()
279     else:
280         state = inst.__dict__
281     sxp = jellier.prepare(inst)
282     sxp.extend([qual(inst.__class__), jellier.jelly(state)])
283     return jellier.preserve(inst, sxp)
284
285
286
287 def setInstanceState(inst, unjellier, jellyList):
288     """
289     Utility method to default to 'normal' state rules in unserialization.
290     """
291     state = unjellier.unjelly(jellyList[1])
292     if hasattr(inst, "__setstate__"):
293         inst.__setstate__(state)
294     else:
295         inst.__dict__ = state
296     return inst
297
298
299
300 class Unpersistable:
301     """
302     This is an instance of a class that comes back when something couldn't be
303     unpersisted.
304     """
305
306     def __init__(self, reason):
307         """
308         Initialize an unpersistable object with a descriptive C{reason} string.
309         """
310         self.reason = reason
311
312
313     def __repr__(self):
314         return "Unpersistable(%s)" % repr(self.reason)
315
316
317
318 class Jellyable:
319     """
320     Inherit from me to Jelly yourself directly with the `getStateFor'
321     convenience method.
322     """
323     implements(IJellyable)
324
325     def getStateFor(self, jellier):
326         return self.__dict__
327
328
329     def jellyFor(self, jellier):
330         """
331         @see: L{twisted.spread.interfaces.IJellyable.jellyFor}
332         """
333         sxp = jellier.prepare(self)
334         sxp.extend([
335             qual(self.__class__),
336             jellier.jelly(self.getStateFor(jellier))])
337         return jellier.preserve(self, sxp)
338
339
340
341 class Unjellyable:
342     """
343     Inherit from me to Unjelly yourself directly with the
344     C{setStateFor} convenience method.
345     """
346     implements(IUnjellyable)
347
348     def setStateFor(self, unjellier, state):
349         self.__dict__ = state
350
351
352     def unjellyFor(self, unjellier, jellyList):
353         """
354         Perform the inverse operation of L{Jellyable.jellyFor}.
355
356         @see: L{twisted.spread.interfaces.IUnjellyable.unjellyFor}
357         """
358         state = unjellier.unjelly(jellyList[1])
359         self.setStateFor(unjellier, state)
360         return self
361
362
363
364 class _Jellier:
365     """
366     (Internal) This class manages state for a call to jelly()
367     """
368
369     def __init__(self, taster, persistentStore, invoker):
370         """
371         Initialize.
372         """
373         self.taster = taster
374         # `preserved' is a dict of previously seen instances.
375         self.preserved = {}
376         # `cooked' is a dict of previously backreferenced instances to their
377         # `ref' lists.
378         self.cooked = {}
379         self.cooker = {}
380         self._ref_id = 1
381         self.persistentStore = persistentStore
382         self.invoker = invoker
383
384
385     def _cook(self, object):
386         """
387         (internal) Backreference an object.
388
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.
400
401         Sorry that this code is so hard to follow, but Python objects are
402         tricky to persist correctly. -glyph
403         """
404         aList = self.preserved[id(object)]
405         newList = copy.copy(aList)
406         # make a new reference ID
407         refid = self._ref_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]
413         return aList
414
415
416     def prepare(self, object):
417         """
418         (internal) Create a list for persisting an object to.  This will allow
419         backreferences to be made internal to the object. (circular
420         references).
421
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]].
428         """
429
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
438         return []
439
440
441     def preserve(self, object, sexp):
442         """
443         (internal) Mark an object's persistent list for later referral.
444         """
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)]
451         else:
452             self.preserved[id(object)] = sexp
453         return sexp
454
455     constantTypes = {types.StringType : 1, types.IntType : 1,
456                      types.FloatType : 1, types.LongType : 1}
457
458
459     def _checkMutable(self,obj):
460         objId = id(obj)
461         if objId in self.cooked:
462             return self.cooked[objId]
463         if objId in self.preserved:
464             self._cook(obj)
465             return self.cooked[objId]
466
467
468     def jelly(self, obj):
469         if isinstance(obj, Jellyable):
470             preRef = self._checkMutable(obj)
471             if preRef:
472                 return preRef
473             return obj.jellyFor(self)
474         objType = type(obj)
475         if self.taster.isTypeAllowed(qual(objType)):
476             # "Immutable" Types
477             if ((objType is StringType) or
478                 (objType is IntType) or
479                 (objType is LongType) or
480                 (objType is FloatType)):
481                 return obj
482             elif objType is MethodType:
483                 return ["method",
484                         obj.im_func.__name__,
485                         self.jelly(obj.im_self),
486                         self.jelly(obj.im_class)]
487
488             elif UnicodeType and objType is UnicodeType:
489                 return ['unicode', obj.encode('UTF-8')]
490             elif objType is NoneType:
491                 return ['None']
492             elif objType is FunctionType:
493                 name = obj.__name__
494                 return ['function', str(pickle.whichmodule(obj, obj.__name__))
495                         + '.' +
496                         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:
502                 if obj.tzinfo:
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:
509                 if obj.tzinfo:
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,
518                                                    obj.microseconds)]
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)
523             else:
524                 preRef = self._checkMutable(obj)
525                 if preRef:
526                     return preRef
527                 # "Mutable" Types
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))
543                 else:
544                     className = qual(obj.__class__)
545                     persistent = None
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__()
555                         else:
556                             state = obj.__dict__
557                         sxp.append(self.jelly(state))
558                     else:
559                         self.unpersistable(
560                             "instance of class %s deemed insecure" %
561                             qual(obj.__class__), sxp)
562                 return self.preserve(obj, sxp)
563         else:
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" %
568                                 (objType, obj))
569
570
571     def _jellyIterable(self, atom, obj):
572         """
573         Jelly an iterable object.
574
575         @param atom: the identifier atom of the object.
576         @type atom: C{str}
577
578         @param obj: any iterable object.
579         @type obj: C{iterable}
580
581         @return: a generator of jellied data.
582         @rtype: C{generator}
583         """
584         yield atom
585         for item in obj:
586             yield self.jelly(item)
587
588
589     def jelly_decimal(self, d):
590         """
591         Jelly a decimal object.
592
593         @param d: a decimal object to serialize.
594         @type d: C{decimal.Decimal}
595
596         @return: jelly for the decimal object.
597         @rtype: C{list}
598         """
599         sign, guts, exponent = d.as_tuple()
600         value = reduce(lambda left, right: left * 10 + right, guts)
601         if sign:
602             value = -value
603         return ['decimal', value, exponent]
604
605
606     def unpersistable(self, reason, sxp=None):
607         """
608         (internal) Returns an sexp: (unpersistable "reason").  Utility method
609         for making note that a particular object could not be serialized.
610         """
611         if sxp is None:
612             sxp = []
613         sxp.append(unpersistable_atom)
614         sxp.append(reason)
615         return sxp
616
617
618
619 class _Unjellier:
620
621     def __init__(self, taster, persistentLoad, invoker):
622         self.taster = taster
623         self.persistentLoad = persistentLoad
624         self.references = {}
625         self.postCallbacks = []
626         self.invoker = invoker
627
628
629     def unjellyFull(self, obj):
630         o = self.unjelly(obj)
631         for m in self.postCallbacks:
632             m()
633         return o
634
635
636     def unjelly(self, obj):
637         if type(obj) is not types.ListType:
638             return obj
639         jelType = obj[0]
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
652             else:
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)
657             return val
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)
664             return inst
665         thunk = getattr(self, '_unjelly_%s'%jelType, None)
666         if thunk is not None:
667             ret = thunk(obj[1:])
668         else:
669             nameSplit = jelType.split('.')
670             modName = '.'.join(nameSplit[:-1])
671             if not self.taster.isModuleAllowed(modName):
672                 raise InsecureJelly(
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)
681             else:
682                 state = self.unjelly(obj[1])
683                 ret = _newInstance(clz, state)
684             if hasattr(clz, 'postUnjelly'):
685                 self.postCallbacks.append(ret.postUnjelly)
686         return ret
687
688
689     def _unjelly_None(self, exp):
690         return None
691
692
693     def _unjelly_unicode(self, exp):
694         if UnicodeType:
695             return unicode(exp[0], "UTF-8")
696         else:
697             return Unpersistable("Could not unpersist unicode: %s" % (exp[0],))
698
699
700     def _unjelly_decimal(self, exp):
701         """
702         Unjelly decimal objects, if decimal is available. If not, return a
703         L{Unpersistable} object instead.
704         """
705         if decimal is None:
706             return Unpersistable(
707                 "Could not unpersist decimal: %s" % (exp[0] * (10**exp[1]),))
708         value = exp[0]
709         exponent = exp[1]
710         if value < 0:
711             sign = 1
712         else:
713             sign = 0
714         guts = decimal.Decimal(value).as_tuple()[1]
715         return decimal.Decimal((sign, guts, exponent))
716
717
718     def _unjelly_boolean(self, exp):
719         if BooleanType:
720             assert exp[0] in ('true', 'false')
721             return exp[0] == 'true'
722         else:
723             return Unpersistable("Could not unpersist boolean: %s" % (exp[0],))
724
725
726     def _unjelly_datetime(self, exp):
727         return datetime.datetime(*map(int, exp[0].split()))
728
729
730     def _unjelly_date(self, exp):
731         return datetime.date(*map(int, exp[0].split()))
732
733
734     def _unjelly_time(self, exp):
735         return datetime.time(*map(int, exp[0].split()))
736
737
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)
742
743
744     def unjellyInto(self, obj, loc, jel):
745         o = self.unjelly(jel)
746         if isinstance(o, NotKnown):
747             o.addDependant(obj, loc)
748         obj[loc] = o
749         return o
750
751
752     def _unjelly_dereference(self, lst):
753         refid = lst[0]
754         x = self.references.get(refid)
755         if x is not None:
756             return x
757         der = _Dereference(refid)
758         self.references[refid] = der
759         return der
760
761
762     def _unjelly_reference(self, lst):
763         refid = lst[0]
764         exp = lst[1]
765         o = self.unjelly(exp)
766         ref = self.references.get(refid)
767         if (ref is None):
768             self.references[refid] = o
769         elif isinstance(ref, NotKnown):
770             ref.resolveDependants(o)
771             self.references[refid] = o
772         else:
773             assert 0, "Multiple references with same ID!"
774         return o
775
776
777     def _unjelly_tuple(self, lst):
778         l = range(len(lst))
779         finished = 1
780         for elem in l:
781             if isinstance(self.unjellyInto(l, elem, lst[elem]), NotKnown):
782                 finished = 0
783         if finished:
784             return tuple(l)
785         else:
786             return _Tuple(l)
787
788
789     def _unjelly_list(self, lst):
790         l = range(len(lst))
791         for elem in l:
792             self.unjellyInto(l, elem, lst[elem])
793         return l
794
795
796     def _unjellySetOrFrozenset(self, lst, containerType):
797         """
798         Helper method to unjelly set or frozenset.
799
800         @param lst: the content of the set.
801         @type lst: C{list}
802
803         @param containerType: the type of C{set} to use.
804         """
805         l = range(len(lst))
806         finished = True
807         for elem in l:
808             data = self.unjellyInto(l, elem, lst[elem])
809             if isinstance(data, NotKnown):
810                 finished = False
811         if not finished:
812             return _Container(l, containerType)
813         else:
814             return containerType(l)
815
816
817     def _unjelly_set(self, lst):
818         """
819         Unjelly set using either the C{set} builtin if available, or
820         C{sets.Set} as fallback.
821         """
822         if _set is not None:
823             containerType = set
824         else:
825             containerType = _sets.Set
826         return self._unjellySetOrFrozenset(lst, containerType)
827
828
829     def _unjelly_frozenset(self, lst):
830         """
831         Unjelly frozenset using either the C{frozenset} builtin if available,
832         or C{sets.ImmutableSet} as fallback.
833         """
834         if _set is not None:
835             containerType = frozenset
836         else:
837             containerType = _sets.ImmutableSet
838         return self._unjellySetOrFrozenset(lst, containerType)
839
840
841     def _unjelly_dictionary(self, lst):
842         d = {}
843         for k, v in lst:
844             kvd = _DictKeyAndValue(d)
845             self.unjellyInto(kvd, 0, k)
846             self.unjellyInto(kvd, 1, v)
847         return d
848
849
850     def _unjelly_module(self, rest):
851         moduleName = rest[0]
852         if type(moduleName) != types.StringType:
853             raise InsecureJelly(
854                 "Attempted to unjelly a module with a non-string name.")
855         if not self.taster.isModuleAllowed(moduleName):
856             raise InsecureJelly(
857                 "Attempted to unjelly module named %r" % (moduleName,))
858         mod = __import__(moduleName, {}, {},"x")
859         return mod
860
861
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):
870             raise InsecureJelly(
871                 "class %r unjellied to something that isn't a class: %r" % (
872                     rest[0], klaus))
873         if not self.taster.isClassAllowed(klaus):
874             raise InsecureJelly("class not allowed: %s" % qual(klaus))
875         return klaus
876
877
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])
885         return function
886
887
888     def _unjelly_persistent(self, rest):
889         if self.persistentLoad:
890             pload = self.persistentLoad(rest[0], self)
891             return pload
892         else:
893             return Unpersistable("Persistent callback not found")
894
895
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)
904         else:
905             state = self.unjelly(rest[1])
906             inst = _newInstance(clz, state)
907         if hasattr(clz, 'postUnjelly'):
908             self.postCallbacks.append(inst.postUnjelly)
909         return inst
910
911
912     def _unjelly_unpersistable(self, rest):
913         return Unpersistable("Unpersistable data: %s" % (rest[0],))
914
915
916     def _unjelly_method(self, rest):
917         """
918         (internal) Unjelly a method.
919         """
920         im_name = rest[0]
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__:
926             if im_self is None:
927                 im = getattr(im_class, im_name)
928             elif isinstance(im_self, NotKnown):
929                 im = _InstanceMethod(im_name, im_self, im_class)
930             else:
931                 im = MethodType(im_class.__dict__[im_name], im_self, im_class)
932         else:
933             raise TypeError('instance method changed')
934         return im
935
936
937
938 class _Dummy:
939     """
940     (Internal) Dummy class, used for unserializing instances.
941     """
942
943
944
945 class _DummyNewStyle(object):
946     """
947     (Internal) Dummy class, used for unserializing instances of new-style
948     classes.
949     """
950
951
952 def _newDummyLike(instance):
953     """
954     Create a new instance like C{instance}.
955
956     The new instance has the same class and instance dictionary as the given
957     instance.
958
959     @return: The new instance.
960     """
961     if isinstance(instance.__class__, type):
962         # New-style class
963         dummy = _DummyNewStyle()
964     else:
965         # Classic class
966         dummy = _Dummy()
967     dummy.__class__ = instance.__class__
968     dummy.__dict__ = instance.__dict__
969     return dummy
970
971
972 #### Published Interface.
973
974
975 class InsecureJelly(Exception):
976     """
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'
979     """
980
981
982
983 class DummySecurityOptions:
984     """
985     DummySecurityOptions() -> insecure security options
986     Dummy security options -- this class will allow anything.
987     """
988
989     def isModuleAllowed(self, moduleName):
990         """
991         DummySecurityOptions.isModuleAllowed(moduleName) -> boolean
992         returns 1 if a module by that name is allowed, 0 otherwise
993         """
994         return 1
995
996
997     def isClassAllowed(self, klass):
998         """
999         DummySecurityOptions.isClassAllowed(class) -> boolean
1000         Assumes the module has already been allowed.  Returns 1 if the given
1001         class is allowed, 0 otherwise.
1002         """
1003         return 1
1004
1005
1006     def isTypeAllowed(self, typeName):
1007         """
1008         DummySecurityOptions.isTypeAllowed(typeName) -> boolean
1009         Returns 1 if the given type is allowed, 0 otherwise.
1010         """
1011         return 1
1012
1013
1014
1015 class SecurityOptions:
1016     """
1017     This will by default disallow everything, except for 'none'.
1018     """
1019
1020     basicTypes = ["dictionary", "list", "tuple",
1021                   "reference", "dereference", "unpersistable",
1022                   "persistent", "long_int", "long", "dict"]
1023
1024     def __init__(self):
1025         """
1026         SecurityOptions() initialize.
1027         """
1028         # I don't believe any of these types can ever pose a security hazard,
1029         # except perhaps "reference"...
1030         self.allowedTypes = {"None": 1,
1031                              "bool": 1,
1032                              "boolean": 1,
1033                              "string": 1,
1034                              "str": 1,
1035                              "int": 1,
1036                              "float": 1,
1037                              "datetime": 1,
1038                              "time": 1,
1039                              "date": 1,
1040                              "timedelta": 1,
1041                              "NoneType": 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 = {}
1050
1051
1052     def allowBasicTypes(self):
1053         """
1054         Allow all `basic' types.  (Dictionary and list.  Int, string, and float
1055         are implicitly allowed.)
1056         """
1057         self.allowTypes(*self.basicTypes)
1058
1059
1060     def allowTypes(self, *types):
1061         """
1062         SecurityOptions.allowTypes(typeString): Allow a particular type, by its
1063         name.
1064         """
1065         for typ in types:
1066             if not isinstance(typ, str):
1067                 typ = qual(typ)
1068             self.allowedTypes[typ] = 1
1069
1070
1071     def allowInstancesOf(self, *classes):
1072         """
1073         SecurityOptions.allowInstances(klass, klass, ...): allow instances
1074         of the specified classes
1075
1076         This will also allow the 'instance', 'class' (renamed 'classobj' in
1077         Python 2.3), and 'module' types, as well as basic types.
1078         """
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
1085
1086
1087     def allowModules(self, *modules):
1088         """
1089         SecurityOptions.allowModules(module, module, ...): allow modules by
1090         name. This will also allow the 'module' type.
1091         """
1092         for module in modules:
1093             if type(module) == types.ModuleType:
1094                 module = module.__name__
1095             self.allowedModules[module] = 1
1096
1097
1098     def isModuleAllowed(self, moduleName):
1099         """
1100         SecurityOptions.isModuleAllowed(moduleName) -> boolean
1101         returns 1 if a module by that name is allowed, 0 otherwise
1102         """
1103         return moduleName in self.allowedModules
1104
1105
1106     def isClassAllowed(self, klass):
1107         """
1108         SecurityOptions.isClassAllowed(class) -> boolean
1109         Assumes the module has already been allowed.  Returns 1 if the given
1110         class is allowed, 0 otherwise.
1111         """
1112         return klass in self.allowedClasses
1113
1114
1115     def isTypeAllowed(self, typeName):
1116         """
1117         SecurityOptions.isTypeAllowed(typeName) -> boolean
1118         Returns 1 if the given type is allowed, 0 otherwise.
1119         """
1120         return (typeName in self.allowedTypes or '.' in typeName)
1121
1122
1123 globalSecurity = SecurityOptions()
1124 globalSecurity.allowBasicTypes()
1125
1126
1127
1128 def jelly(object, taster=DummySecurityOptions(), persistentStore=None,
1129           invoker=None):
1130     """
1131     Serialize to s-expression.
1132
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.
1136     """
1137     return _Jellier(taster, persistentStore, invoker).jelly(object)
1138
1139
1140
1141 def unjelly(sexp, taster=DummySecurityOptions(), persistentLoad=None,
1142             invoker=None):
1143     """
1144     Unserialize from s-expression.
1145
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.
1150     """
1151     return _Unjellier(taster, persistentLoad, invoker).unjellyFull(sexp)