~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/spread/newjelly.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: twisted.test.test_newjelly -*-
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.9 $"[11:-2]
57
 
 
58
 
# System Imports
59
 
import string
60
 
import pickle
61
 
import sys
62
 
import types
63
 
from types import StringType
64
 
try:
65
 
    from types import UnicodeType
66
 
except ImportError:
67
 
    UnicodeType = None
68
 
from types import IntType
69
 
from types import TupleType
70
 
from types import ListType
71
 
from types import DictType
72
 
from types import LongType
73
 
from types import FloatType
74
 
from types import FunctionType
75
 
from types import MethodType
76
 
from types import ModuleType
77
 
from types import DictionaryType
78
 
from types import InstanceType
79
 
from types import NoneType
80
 
from types import ClassType
81
 
import copy
82
 
 
83
 
try:
84
 
    from types import BooleanType
85
 
except ImportError:
86
 
    BooleanType = None
87
 
 
88
 
from new import instance
89
 
from new import instancemethod
90
 
 
91
 
 
92
 
# Twisted Imports
93
 
from twisted.python.reflect import namedObject, namedModule, qual
94
 
from twisted.persisted.crefutil import NotKnown, _Tuple, _InstanceMethod, _DictKeyAndValue, _Dereference
95
 
from twisted.python import runtime
96
 
 
97
 
if runtime.platform.getType() == "java":
98
 
    from org.python.core import PyStringMap
99
 
    DictTypes = (DictionaryType, PyStringMap)
100
 
else:
101
 
    DictTypes = (DictionaryType,)
102
 
 
103
 
 
104
 
None_atom = "None"                  # N
105
 
# code
106
 
class_atom = "class"                # c
107
 
module_atom = "module"              # m
108
 
function_atom = "function"          # f
109
 
 
110
 
# references
111
 
dereference_atom = 'dereference'    # D
112
 
persistent_atom = 'persistent'      # p
113
 
reference_atom = 'reference'        # r
114
 
 
115
 
# mutable collections
116
 
dictionary_atom = "dictionary"      # d
117
 
list_atom = 'list'                  # l
118
 
 
119
 
# immutable collections
120
 
#   (assignment to __dict__ and __class__ still might go away!)
121
 
tuple_atom = "tuple"                # t
122
 
 
123
 
 
124
 
# errors
125
 
unpersistable_atom = "unpersistable"# u
126
 
unjellyableRegistry = {}
127
 
unjellyableFactoryRegistry = {}
128
 
 
129
 
def _maybeClass(classnamep):
130
 
    try:
131
 
        object
132
 
    except NameError:
133
 
        isObject = 0
134
 
    else:
135
 
        isObject = isinstance(classnamep, type)
136
 
    if isinstance(classnamep, ClassType) or isObject:
137
 
        return qual(classnamep)
138
 
    return classnamep
139
 
 
140
 
def setUnjellyableForClass(classname, unjellyable):
141
 
    """Set which local class will represent a remote type.
142
 
 
143
 
    If you have written a Copyable class that you expect your client to be
144
 
    receiving, write a local "copy" class to represent it, then call::
145
 
 
146
 
        jellier.setUnjellyableForClass('module.package.Class', MyJellier).
147
 
 
148
 
    Call this at the module level immediately after its class
149
 
    definition. MyCopier should be a subclass of RemoteCopy.
150
 
 
151
 
    The classname may be a special tag returned by
152
 
    'Copyable.getTypeToCopyFor' rather than an actual classname.
153
 
 
154
 
    This call is also for cached classes, since there will be no
155
 
    overlap.  The rules are the same.
156
 
    """
157
 
 
158
 
    global unjellyableRegistry
159
 
    classname = _maybeClass(classname)
160
 
    unjellyableRegistry[classname] = unjellyable
161
 
    globalSecurity.allowTypes(classname)
162
 
 
163
 
def setUnjellyableFactoryForClass(classname, copyFactory):
164
 
    """
165
 
    Set the factory to construct a remote instance of a type::
166
 
 
167
 
        jellier.setFactoryForClass('module.package.Class', MyFactory)
168
 
 
169
 
    Call this at the module level immediately after its class definition.
170
 
    C{copyFactory} should return an instance or subclass of
171
 
    L{RemoteCopy<pb.RemoteCopy>}.
172
 
 
173
 
    Similar to L{setUnjellyableForClass} except it uses a factory instead
174
 
    of creating an instance.
175
 
    """
176
 
 
177
 
    global unjellyableFactoryRegistry
178
 
    classname = _maybeClass(classname)
179
 
    unjellyableFactoryRegistry[classname] = copyFactory
180
 
    globalSecurity.allowTypes(classname)
181
 
 
182
 
 
183
 
def setUnjellyableForClassTree(module, baseClass, prefix=None):
184
 
    """
185
 
    Set all classes in a module derived from C{baseClass} as copiers for
186
 
    a corresponding remote class.
187
 
 
188
 
    When you have a heirarchy of Copyable (or Cacheable) classes on
189
 
    one side, and a mirror structure of Copied (or RemoteCache)
190
 
    classes on the other, use this to setCopierForClass all your
191
 
    Copieds for the Copyables.
192
 
 
193
 
    Each copyTag (the \"classname\" argument to getTypeToCopyFor, and
194
 
    what the Copyable's getTypeToCopyFor returns) is formed from
195
 
    adding a prefix to the Copied's class name.  The prefix defaults
196
 
    to module.__name__.  If you wish the copy tag to consist of solely
197
 
    the classname, pass the empty string \'\'.
198
 
 
199
 
    @param module: a module object from which to pull the Copied classes.
200
 
        (passing sys.modules[__name__] might be useful)
201
 
 
202
 
    @param baseClass: the base class from which all your Copied classes derive.
203
 
 
204
 
    @param prefix: the string prefixed to classnames to form the
205
 
        unjellyableRegistry.
206
 
    """
207
 
    if prefix is None:
208
 
        prefix = module.__name__
209
 
 
210
 
    if prefix:
211
 
        prefix = "%s." % prefix
212
 
 
213
 
    for i in dir(module):
214
 
        i_ = getattr(module, i)
215
 
        if type(i_) == types.ClassType:
216
 
            if issubclass(i_, baseClass):
217
 
                setUnjellyableForClass('%s%s' % (prefix, i), i_)
218
 
 
219
 
def getInstanceState(inst, jellier):
220
 
    """Utility method to default to 'normal' state rules in serialization.
221
 
    """
222
 
    if hasattr(inst, "__getstate__"):
223
 
        state = inst.__getstate__()
224
 
    else:
225
 
        state = inst.__dict__
226
 
    sxp = jellier.prepare(inst)
227
 
    sxp.extend([qual(inst.__class__), jellier.jelly(state)])
228
 
    return jellier.preserve(inst, sxp)
229
 
 
230
 
def setInstanceState(inst, unjellier, jellyList):
231
 
    """Utility method to default to 'normal' state rules in unserialization.
232
 
    """
233
 
    state = unjellier.unjelly(jellyList[1])
234
 
    if hasattr(inst, "__setstate__"):
235
 
        inst.__setstate__(state)
236
 
    else:
237
 
        inst.__dict__ = state
238
 
    return inst
239
 
 
240
 
class Unpersistable:
241
 
    """
242
 
    This is an instance of a class that comes back when something couldn't be
243
 
    persisted.
244
 
    """
245
 
    def __init__(self, reason):
246
 
        """
247
 
        Initialize an unpersistable object with a descriptive `reason' string.
248
 
        """
249
 
        self.reason = reason
250
 
 
251
 
    def __repr__(self):
252
 
        return "Unpersistable(%s)" % repr(self.reason)
253
 
 
254
 
class Jellyable:
255
 
    """Inherit from me to Jelly yourself directly.
256
 
    """
257
 
    def getStateFor(self, jellier):
258
 
        return self.__dict__
259
 
 
260
 
    def jellyFor(self, jellier):
261
 
        sxp = jellier.prepare(self)
262
 
        sxp.extend([
263
 
            qual(self.__class__),
264
 
            jellier.jelly(self.getStateFor(jellier))])
265
 
        return jellier.preserve(self, sxp)
266
 
 
267
 
class Unjellyable:
268
 
    """Inherit from me to Unjelly yourself directly.
269
 
    """
270
 
    def setStateFor(self, unjellier, state):
271
 
        self.__dict__ = state
272
 
 
273
 
    def unjellyFor(self, unjellier, jellyList):
274
 
        state = unjellier.unjelly(jellyList[1])
275
 
        self.setStateFor(unjellier, state)
276
 
 
277
 
 
278
 
class _Jellier:
279
 
    """(Internal) This class manages state for a call to jelly()
280
 
    """
281
 
    def __init__(self, taster, persistentStore, invoker):
282
 
        """Initialize.
283
 
        """
284
 
        self.taster = taster
285
 
        self.seen = {} # maps from object ID to reference number
286
 
        self._ref_id = 0
287
 
        #self.persistentStore = persistentStore  # ignored
288
 
        self.invoker = invoker
289
 
 
290
 
    constantTypes = {types.StringType : 1, types.IntType : 1,
291
 
                     types.FloatType : 1, types.LongType : 1}
292
 
 
293
 
    # XXX ancient horrible backwards-compatibility
294
 
    
295
 
    def prepare(self, obj):
296
 
        return []
297
 
 
298
 
    def preserve(self, obj, jlist):
299
 
        return jlist
300
 
 
301
 
    def _checkMutable(self, obj, refId):
302
 
        objId = id(obj)
303
 
        if self.seen.has_key(objId):
304
 
            objCheck, derefKey = self.seen[objId]
305
 
            return [dereference_atom, derefKey]
306
 
        self.seen[objId] = obj, refId
307
 
 
308
 
    def jelly(self, obj):
309
 
        if isinstance(obj, Jellyable):
310
 
            refId = self._ref_id
311
 
            self._ref_id += 1
312
 
            preRef = self._checkMutable(obj, refId)
313
 
            if preRef:
314
 
                return preRef
315
 
            return obj.jellyFor(self)
316
 
        objType = type(obj)
317
 
        if self.taster.isTypeAllowed(
318
 
            string.replace(objType.__name__, ' ', '_')):
319
 
            # "Immutable" Types
320
 
            if ((objType is StringType) or
321
 
                (objType is IntType) or
322
 
                (objType is LongType) or
323
 
                (objType is FloatType)):
324
 
                return obj
325
 
 
326
 
            refId = self._ref_id
327
 
            self._ref_id += 1
328
 
            
329
 
            if objType is MethodType:
330
 
                return ["method",
331
 
                        obj.im_func.__name__,
332
 
                        self.jelly(obj.im_self),
333
 
                        self.jelly(obj.im_class)]
334
 
 
335
 
            elif UnicodeType and objType is UnicodeType:
336
 
                return ['unicode', obj.encode('UTF-8')]
337
 
            elif objType is NoneType:
338
 
                return ['None']
339
 
            elif objType is FunctionType:
340
 
                name = obj.__name__
341
 
                return ['function', str(pickle.whichmodule(obj, obj.__name__))
342
 
                        + '.' +
343
 
                        name]
344
 
            elif objType is ModuleType:
345
 
                return ['module', obj.__name__]
346
 
            elif objType is BooleanType:
347
 
                return ['boolean', obj and 'true' or 'false']
348
 
            elif objType is ClassType or issubclass(objType, type):
349
 
                return ['class', qual(obj)]
350
 
            else:
351
 
                # "Mutable" Types
352
 
                preRef = self._checkMutable(obj, refId)
353
 
                if preRef:
354
 
                    return preRef
355
 
                sxp = []
356
 
                if objType is ListType:
357
 
                    sxp.append(list_atom)
358
 
                    for item in obj:
359
 
                        sxp.append(self.jelly(item))
360
 
                elif objType is TupleType:
361
 
                    sxp.append(tuple_atom)
362
 
                    for item in obj:
363
 
                        sxp.append(self.jelly(item))
364
 
                elif objType in DictTypes:
365
 
                    sxp.append(dictionary_atom)
366
 
                    for key, val in obj.items():
367
 
                        self._ref_id += 1
368
 
                        sxp.append([self.jelly(key), self.jelly(val)])
369
 
                elif objType is InstanceType:
370
 
                    className = qual(obj.__class__)
371
 
                    if self.taster.isClassAllowed(obj.__class__):
372
 
                        sxp.append(className)
373
 
                        if hasattr(obj, "__getstate__"):
374
 
                            state = obj.__getstate__()
375
 
                        else:
376
 
                            state = obj.__dict__
377
 
                        sxp.append(self.jelly(state))
378
 
                    else:
379
 
                        self.unpersistable(
380
 
                            "instance of class %s deemed insecure" %
381
 
                            qual(obj.__class__), sxp)
382
 
                else:
383
 
                    raise NotImplementedError("Don't know the type: %s" % objType)
384
 
                return sxp
385
 
        else:
386
 
            if objType is types.InstanceType:
387
 
                raise InsecureJelly("Class not allowed for instance: %s %s" %
388
 
                                    (obj.__class__, obj))
389
 
            raise InsecureJelly("Type not allowed for object: %s %s" %
390
 
                                (objType, obj))
391
 
 
392
 
    def unpersistable(self, reason, sxp=None):
393
 
        '''(internal)
394
 
        Returns an sexp: (unpersistable "reason").  Utility method for making
395
 
        note that a particular object could not be serialized.
396
 
        '''
397
 
        if sxp is None:
398
 
            sxp = []
399
 
        sxp.append(unpersistable_atom)
400
 
        sxp.append(reason)
401
 
        return sxp
402
 
 
403
 
 
404
 
class NullReference(Exception):
405
 
    """
406
 
    This object is a marker for when a reference is made to an object that
407
 
    can't be referenced, i.e.::
408
 
 
409
 
        (tuple (tuple (reference 1)))
410
 
 
411
 
    """
412
 
 
413
 
_theNullRef = NullReference()
414
 
 
415
 
class _Unjellier:
416
 
    def __init__(self, taster, persistentLoad, invoker):
417
 
        self.taster = taster
418
 
        #self.persistentLoad = persistentLoad  # ignored
419
 
        self.references = []
420
 
        self.postCallbacks = []
421
 
        self.invoker = invoker
422
 
 
423
 
    def unjellyFull(self, obj):
424
 
        o = self.unjelly(obj)
425
 
        for m in self.postCallbacks:
426
 
            m()
427
 
        return o
428
 
 
429
 
    def unjelly(self, obj):
430
 
        if type(obj) is not types.ListType:
431
 
            return obj
432
 
        self.references.append(_theNullRef)
433
 
        jelType = obj[0]
434
 
        if not self.taster.isTypeAllowed(jelType):
435
 
            raise InsecureJelly(jelType)
436
 
        regClass = unjellyableRegistry.get(jelType)
437
 
        if regClass is not None:
438
 
            if isinstance(regClass, ClassType):
439
 
                inst = _Dummy() # XXX chomp, chomp
440
 
                self.resolveReference(inst)
441
 
                inst.__class__ = regClass
442
 
                val = inst.unjellyFor(self,obj)
443
 
            else:
444
 
                refid = self.getRefId()
445
 
                self.resolveReference(NotKnown())
446
 
                val = regClass(self, obj) # this is how it ought to be done
447
 
                self.resolveReference(val, refid)
448
 
            if hasattr(val, 'postUnjelly'):
449
 
                self.postCallbacks.append(inst.postUnjelly)
450
 
            return val
451
 
        regFactory = unjellyableFactoryRegistry.get(jelType)
452
 
        if regFactory is not None:
453
 
            refid = self.getRefId()
454
 
            self.resolveReference(NotKnown())
455
 
            state = self.unjelly(obj[1])
456
 
            inst = regFactory(state)
457
 
            if hasattr(inst, 'postUnjelly'):
458
 
                self.postCallbacks.append(inst.postUnjelly)
459
 
            return self.resolveReference(inst, refid)
460
 
        thunk = getattr(self, '_unjelly_%s'%jelType, None)
461
 
        if thunk is not None:
462
 
            ret = thunk(obj[1:])
463
 
        else:
464
 
            nameSplit = string.split(jelType, '.')
465
 
            modName = string.join(nameSplit[:-1], '.')
466
 
            if not self.taster.isModuleAllowed(modName):
467
 
                raise InsecureJelly("Module %s not allowed." % modName)
468
 
            clz = namedObject(jelType)
469
 
            if not self.taster.isClassAllowed(clz):
470
 
                raise InsecureJelly("Class %s not allowed." % jelType)
471
 
            if hasattr(clz, "__setstate__"):
472
 
                ret = instance(clz, {})
473
 
                self.resolveReference(ret)
474
 
                state = self.unjelly(obj[1])
475
 
                ret.__setstate__(state)
476
 
            else:
477
 
                ret = instance(clz, {})
478
 
                self.resolveReference(ret)
479
 
                state = self.unjelly(obj[1])
480
 
                ret.__dict__ = state
481
 
            if hasattr(clz, 'postUnjelly'):
482
 
                self.postCallbacks.append(ret.postUnjelly)
483
 
        return ret
484
 
 
485
 
    def resolveReference(self, obj, index=-1):
486
 
        if isinstance(self.references[index], NotKnown):
487
 
            assert not isinstance(obj, NotKnown)
488
 
            self.references[index].resolveDependants(obj)
489
 
        self.references[index] = obj
490
 
        return obj
491
 
 
492
 
    def _unjelly_None(self, exp):
493
 
        return self.resolveReference(None)
494
 
 
495
 
    def _unjelly_unicode(self, exp):
496
 
        if UnicodeType:
497
 
            return self.resolveReference(unicode(exp[0], "UTF-8"))
498
 
        else:
499
 
            return self.resolveReference(Unpersistable(exp[0]))
500
 
 
501
 
    def _unjelly_boolean(self, exp):
502
 
        if BooleanType:
503
 
            assert exp[0] in ('true', 'false')
504
 
            return self.resolveReference(exp[0] == 'true')
505
 
        else:
506
 
            return self.resolveReference(Unpersistable(exp[0]))
507
 
 
508
 
    def unjellyInto(self, obj, loc, jel):
509
 
        o = self.unjelly(jel)
510
 
        if isinstance(o, NotKnown):
511
 
            o.addDependant(obj, loc)
512
 
        obj[loc] = o
513
 
        return o
514
 
 
515
 
    def _unjelly_dereference(self, lst):
516
 
        refid = lst[0]
517
 
        return self.references[refid]
518
 
 
519
 
    def getRefId(self):
520
 
        return len(self.references) - 1
521
 
 
522
 
    def _unjelly_tuple(self, lst):
523
 
        l = [NotKnown()] * len(lst)
524
 
        result = None
525
 
        preTuple = _Tuple(l)
526
 
        refid = self.getRefId()
527
 
        self.resolveReference(preTuple)
528
 
        for elem in xrange(len(l)):
529
 
            self.unjellyInto(preTuple, elem, lst[elem])
530
 
        # zero-length tuples are false!!
531
 
        # return preTuple.resolvedObject or preTuple
532
 
        if preTuple.resolvedObject is None:
533
 
            return preTuple
534
 
        else:
535
 
            return preTuple.resolvedObject
536
 
    
537
 
    def _unjelly_list(self, lst):
538
 
        l = range(len(lst))
539
 
        self.resolveReference(l)
540
 
        for elem in l:
541
 
            self.unjellyInto(l, elem, lst[elem])
542
 
        return l
543
 
 
544
 
    def _unjelly_dictionary(self, lst):
545
 
        d = {}
546
 
        self.resolveReference(d)
547
 
        for k, v in lst:
548
 
            self.references.append(_theNullRef)
549
 
            kvd = _DictKeyAndValue(d)
550
 
            self.unjellyInto(kvd, 0, k)
551
 
            self.unjellyInto(kvd, 1, v)
552
 
        return d
553
 
 
554
 
 
555
 
    def _unjelly_module(self, rest):
556
 
        moduleName = rest[0]
557
 
        # if len(rest) > 0: warn("reference numbers will be out of sync")
558
 
        if type(moduleName) != types.StringType:
559
 
            raise InsecureJelly("Attempted to unjelly a module with a non-string name.")
560
 
        if not self.taster.isModuleAllowed(moduleName):
561
 
            raise InsecureJelly("Attempted to unjelly module named %s" % repr(moduleName))
562
 
        mod = __import__(moduleName, {}, {},"x")
563
 
        self.resolveReference(mod)
564
 
        return mod
565
 
 
566
 
    def _unjelly_class(self, rest):
567
 
        clist = string.split(rest[0], '.')
568
 
        # if len(rest) > 0: warn("reference numbers will be out of sync")
569
 
        modName = string.join(clist[:-1], '.')
570
 
        if not self.taster.isModuleAllowed(modName):
571
 
            raise InsecureJelly("module %s not allowed" % modName)
572
 
        klaus = namedObject(rest[0])
573
 
        if type(klaus) is not types.ClassType:
574
 
            raise InsecureJelly("class %s unjellied to something that isn't a class: %s" % (repr(name), repr(klaus)))
575
 
        if not self.taster.isClassAllowed(klaus):
576
 
            raise InsecureJelly("class not allowed: %s" % qual(klaus))
577
 
        self.resolveReference(klaus)
578
 
        return klaus
579
 
 
580
 
    def _unjelly_function(self, rest):
581
 
        modSplit = string.split(rest[0], '.')
582
 
        # if len(rest) > 0: warn("reference numbers will be out of sync")
583
 
        modName = string.join(modSplit[:-1], '.')
584
 
        if not self.taster.isModuleAllowed(modName):
585
 
            raise InsecureJelly("Module not allowed: %s"% modName)
586
 
        # XXX do I need an isFunctionAllowed?
587
 
        function = namedObject(rest[0])
588
 
        self.resolveReference(function)
589
 
        return function
590
 
 
591
 
    def _unjelly_unpersistable(self, rest):
592
 
        return self.resolveReference(Unpersistable(rest[0]))
593
 
 
594
 
    def _unjelly_method(self, rest):
595
 
        ''' (internal) unjelly a method
596
 
        '''
597
 
        nk = NotKnown()
598
 
        rid = self.getRefId()
599
 
        self.resolveReference(nk)
600
 
        im_name = rest[0]
601
 
        im_self = self.unjelly(rest[1])
602
 
        im_class = self.unjelly(rest[2])
603
 
        assert not isinstance(im_self, NotKnown)
604
 
        if type(im_class) is not types.ClassType:
605
 
            raise InsecureJelly("Method found with non-class class.")
606
 
        if im_class.__dict__.has_key(im_name):
607
 
            if im_self is None:
608
 
                im = getattr(im_class, im_name)
609
 
            else:
610
 
                im = instancemethod(im_class.__dict__[im_name],
611
 
                                    im_self,
612
 
                                    im_class)
613
 
        else:
614
 
            raise 'instance method changed'
615
 
        return self.resolveReference(im, rid)
616
 
 
617
 
 
618
 
class _Dummy:
619
 
    """(Internal)
620
 
    Dummy class, used for unserializing instances.
621
 
    """
622
 
 
623
 
 
624
 
 
625
 
 
626
 
#### Published Interface.
627
 
 
628
 
 
629
 
class InsecureJelly(Exception):
630
 
    """
631
 
    This exception will be raised when a jelly is deemed `insecure'; e.g. it
632
 
    contains a type, class, or module disallowed by the specified `taster'
633
 
    """
634
 
 
635
 
 
636
 
 
637
 
class DummySecurityOptions:
638
 
    """DummySecurityOptions() -> insecure security options
639
 
    Dummy security options -- this class will allow anything.
640
 
    """
641
 
    def isModuleAllowed(self, moduleName):
642
 
        """DummySecurityOptions.isModuleAllowed(moduleName) -> boolean
643
 
        returns 1 if a module by that name is allowed, 0 otherwise
644
 
        """
645
 
        return 1
646
 
 
647
 
    def isClassAllowed(self, klass):
648
 
        """DummySecurityOptions.isClassAllowed(class) -> boolean
649
 
        Assumes the module has already been allowed.  Returns 1 if the given
650
 
        class is allowed, 0 otherwise.
651
 
        """
652
 
        return 1
653
 
 
654
 
    def isTypeAllowed(self, typeName):
655
 
        """DummySecurityOptions.isTypeAllowed(typeName) -> boolean
656
 
        Returns 1 if the given type is allowed, 0 otherwise.
657
 
        """
658
 
        return 1
659
 
 
660
 
 
661
 
 
662
 
class SecurityOptions:
663
 
    """
664
 
    This will by default disallow everything, except for 'none'.
665
 
    """
666
 
 
667
 
    basicTypes = ["dictionary", "list", "tuple",
668
 
                  "reference", "dereference", "unpersistable",
669
 
                  "persistent", "long_int", "long", "dict"]
670
 
 
671
 
    def __init__(self):
672
 
        """SecurityOptions()
673
 
        Initialize.
674
 
        """
675
 
        # I don't believe any of these types can ever pose a security hazard,
676
 
        # except perhaps "reference"...
677
 
        self.allowedTypes = {"None": 1,
678
 
                             "bool": 1,
679
 
                             "boolean": 1,
680
 
                             "string": 1,
681
 
                             "str": 1,
682
 
                             "int": 1,
683
 
                             "float": 1,
684
 
                             "NoneType": 1}
685
 
        if hasattr(types, 'UnicodeType'):
686
 
            self.allowedTypes['unicode'] = 1
687
 
        self.allowedModules = {}
688
 
        self.allowedClasses = {}
689
 
 
690
 
    def allowBasicTypes(self):
691
 
        """SecurityOptions.allowBasicTypes()
692
 
        Allow all `basic' types.  (Dictionary and list.  Int, string, and float are implicitly allowed.)
693
 
        """
694
 
        self.allowTypes(*self.basicTypes)
695
 
 
696
 
    def allowTypes(self, *types):
697
 
        """SecurityOptions.allowTypes(typeString): Allow a particular type, by its name.
698
 
        """
699
 
        for typ in types:
700
 
            self.allowedTypes[string.replace(typ, ' ', '_')]=1
701
 
 
702
 
    def allowInstancesOf(self, *classes):
703
 
        """SecurityOptions.allowInstances(klass, klass, ...): allow instances
704
 
        of the specified classes
705
 
 
706
 
        This will also allow the 'instance', 'class' (renamed 'classobj' in
707
 
        Python 2.3), and 'module' types, as well as basic types.
708
 
        """
709
 
        self.allowBasicTypes()
710
 
        self.allowTypes("instance", "class", "classobj", "module")
711
 
        for klass in classes:
712
 
            self.allowTypes(qual(klass))
713
 
            self.allowModules(klass.__module__)
714
 
            self.allowedClasses[klass] = 1
715
 
 
716
 
    def allowModules(self, *modules):
717
 
        """SecurityOptions.allowModules(module, module, ...): allow modules by name
718
 
        This will also allow the 'module' type.
719
 
        """
720
 
        for module in modules:
721
 
            if type(module) == types.ModuleType:
722
 
                module = module.__name__
723
 
            self.allowedModules[module] = 1
724
 
 
725
 
    def isModuleAllowed(self, moduleName):
726
 
        """SecurityOptions.isModuleAllowed(moduleName) -> boolean
727
 
        returns 1 if a module by that name is allowed, 0 otherwise
728
 
        """
729
 
        return self.allowedModules.has_key(moduleName)
730
 
 
731
 
    def isClassAllowed(self, klass):
732
 
        """SecurityOptions.isClassAllowed(class) -> boolean
733
 
        Assumes the module has already been allowed.  Returns 1 if the given
734
 
        class is allowed, 0 otherwise.
735
 
        """
736
 
        return self.allowedClasses.has_key(klass)
737
 
 
738
 
    def isTypeAllowed(self, typeName):
739
 
        """SecurityOptions.isTypeAllowed(typeName) -> boolean
740
 
        Returns 1 if the given type is allowed, 0 otherwise.
741
 
        """
742
 
        return (self.allowedTypes.has_key(typeName) or
743
 
                '.' in typeName)
744
 
 
745
 
 
746
 
globalSecurity = SecurityOptions()
747
 
globalSecurity.allowBasicTypes()
748
 
 
749
 
debugCrap = []
750
 
 
751
 
def jelly(object, taster = DummySecurityOptions(), persistentStore=None, invoker=None):
752
 
    """Serialize to s-expression.
753
 
 
754
 
    Returns a list which is the serialized representation of an object.  An
755
 
    optional 'taster' argument takes a SecurityOptions and will mark any
756
 
    insecure objects as unpersistable rather than serializing them.
757
 
    """
758
 
    jr = _Jellier(taster, persistentStore, invoker)
759
 
    jel = jr.jelly(object)
760
 
    # jr.__dict__.clear()
761
 
    return jel
762
 
 
763
 
 
764
 
def unjelly(sexp, taster = DummySecurityOptions(), persistentLoad=None, invoker=None):
765
 
    """Unserialize from s-expression.
766
 
 
767
 
    Takes an list that was the result from a call to jelly() and unserializes
768
 
    an arbitrary object from it.  The optional 'taster' argument, an instance
769
 
    of SecurityOptions, will cause an InsecureJelly exception to be raised if a
770
 
    disallowed type, module, or class attempted to unserialize.
771
 
    """
772
 
    ujr = _Unjellier(taster, persistentLoad, invoker)
773
 
    result = ujr.unjellyFull(sexp)
774
 
    # debugCrap.append(ujr.references)
775
 
    return result
776