| 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 |