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