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