~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/spread/jelly.py

  • Committer: Thomas Hervé
  • Date: 2009-07-08 13:52:04 UTC
  • Revision ID: thomas@canonical.com-20090708135204-df5eesrthifpylf8
Remove twisted copy

Show diffs side-by-side

added added

removed removed

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