"Modjelly is Jelly hacked up into a thousand pieces and then put back together again." import pickle import types class UnjellyError(Exception): "Base class from which errors involving unjellying objects inherit" class UnjellyingError(UnjellyError): "Represents an error during the process of unjellying" class JellyError(Exception): "Base class from which errors involving jellying objects inherit" class JellyingError(JellyError): "Represents an error during the process of jellying" class NullReference(Exception): """This object is a marker for when a reference is made to an object that can't be referenced, i.e. (tuple (tuple (reference 1))) """ #Not sure what this does. That's what happens when you take apart code #without knowing how it works in general. NULLREF = NullReference() _disassemblers = {} _markers = {} external_modules = {'__builtins__':__builtins__}#The external module system sucks external_modules = {} cls_marker = 'class' mod_marker = 'module' code_marker = 'code' func_marker = 'function' inst_marker = 'instance' meth_marker = 'method' unbd_marker = 'unbound method' type_marker = 'type' external_reference_atom= 'external reference'#The external module system sucks disassembled_atom = 'disassembled' dereference_atom = 'dereference' dict_atom = 'dict' boolean_atom = 'boolean' unicode_atom = 'unicode' tuple_atom = 'tuple' none_atom = 'None' list_atom = 'list' class ModJellier: def __init__(self, disassemblers=None,external_modules=None): self.disassemblers = disassemblers or {} self.seen = {} self._ref_id = 0 self.external_modules = external_modules or {} def get_atom(self, type): if self.disassemblers.has_key(type) or issubclass(type, types.TypeType): atom = disassembled_atom elif type in (types.BuiltinMethodType, types.BuiltinFunctionType): atom = external_reference_atom else: d = {types.NoneType: none_atom, types.BooleanType: boolean_atom, types.UnicodeType: unicode_atom, types.ListType: list_atom, types.TupleType: tuple_atom, types.DictType: dict_atom} try: atom = d[type] except KeyError: raise JellyingError,'Unknown object type %s encountered' % type return atom def check_ref(self, obj, ref_id): obj_id = id(obj) if self.seen.has_key(obj_id): return [dereference_atom, self.seen[obj_id], self.get_atom(type(obj))] self.seen[obj_id] = ref_id def jelly(self, obj): #Only secure if the user doesn't have access to Jellyable! if isinstance(obj, Jellyable): ref_id = self._ref_id self._ref_id += 1 prev_ref = self.check_ref(obj, ref_id) if prev_ref is not None: return prev_ref return obj.jelly_for(self) obj_type = type(obj) #These should be immutable if obj_type in (types.StringType,types.IntType,types.LongType,types.FloatType): return obj ref_id = self._ref_id self._ref_id += 1 if self.disassemblers.has_key(obj_type): prev_ref = self.check_ref(obj, ref_id) if prev_ref is not None: return prev_ref marker, args = self.disassemblers[obj_type](obj) sexp = [disassembled_atom,self.jelly(marker),[tuple_atom]] self._ref_id += 1 sexp[-1].extend(self.jelly(arg) for arg in args) return sexp elif obj_type in (types.BuiltinFunctionType,types.BuiltinMethodType):#The external module system sucks try: self.external_modules[obj.__module__][obj.__name__] except: raise JellyingError,'Unknown builtin function-or-method %s from module %s encountered' % (obj.__name__,obj.__module__) else: return [external_reference_atom, obj.__name__, obj.__module__] elif issubclass(obj_type,types.TypeType): prev_ref = self.check_ref(obj, ref_id) if prev_ref is not None: return prev_ref #I should really be more careful with this, it would be bad if someone made a #metaclass, I think. Or changed the __class__ to something malicious. #or used properties/descriptors return [disassembled_atom, type_marker, self.jelly((obj.__class__.__name__, obj.__bases__, dict(obj.__dict__)))] elif obj_type is types.NoneType: return [none_atom] elif obj_type is types.BooleanType: return [boolean_atom, obj and 'True' or 'False'] elif obj_type is types.UnicodeType: return [unicode_atom, obj.encode('UTF-8'),'UTF-8'] else: #Mutable Types prev_ref = self.check_ref(obj, ref_id) if prev_ref is not None: return prev_ref jelly = [] if obj_type is types.ListType: jelly.append(list_atom) jelly.extend(self.jelly(element) for element in obj) elif obj_type is types.TupleType: jelly.append(tuple_atom) jelly.extend(self.jelly(element) for element in obj) elif obj_type is types.DictType: jelly.append(dict_atom) for k,v in obj.items(): self._ref_id += 1 jelly.append([self.jelly(k), self.jelly(v)]) else: raise JellyingError,"Don't know how to jelly objects of type %s" % obj_type return jelly def register_dis(self, obj_type, disassembler): self.disassemblers[obj_type] = disassembler class ModUnjellier: def __init__(self, markers=None,external_modules=None): self.markers = markers or {} self.references = [] self.jarred_types = [tuple_atom, disassembled_atom] self.external_modules = external_modules or {}#The external module system sucks def unjelly(self, jelly): if type(jelly) is not types.ListType:#Never unjelly untrusted (read: maliciously formed) data! return jelly self.references.append(NULLREF)#Still not sure why I need this... jelly_type = jelly[0] if jelly_type == disassembled_atom: try: reassemble = self.markers[self.unjelly(jelly[1])] except KeyError: raise UnjellyingError,"Unknown marker %s encountered" % jelly[1] jar = [NULLREF] self.resolve_ref(jar) jar[0] = reassemble(*self.unjelly(jelly[2])) return jar[0] elif jelly_type == external_reference_atom:#The external module system sucks try: return self.external_modules[jelly[2]][jelly[1]] except KeyError: raise UnjellyingError,'Unknown external builtin-function-or-method %s from module %s encountered' % (jelly[1],jelly[2]) elif jelly_type == dereference_atom: ref = self.references[jelly[1]]#jelly[1] is the id ref_type = jelly[2] #jelly[2] is the atom if ref_type in self.jarred_types: return ref[0] return ref elif jelly_type == tuple_atom: jar = [NULLREF] self.resolve_ref(jar) tup = tuple(self.unjelly(element) for element in jelly[1:]) jar[0] = tup return tup elif jelly_type == list_atom: almost_unjellied = [] self.resolve_ref(almost_unjellied) almost_unjellied.extend(self.unjelly(element) for element in jelly[1:]) return almost_unjellied elif jelly_type == dict_atom: d = {} self.resolve_ref(d) for k,v in jelly[1:]:#No, really, I don't know what it's for self.references.append(NULLREF) d[self.unjelly(k)] = self.unjelly(v) return d elif jelly_type == none_atom: return None elif jelly_type == boolean_atom: return jelly[1] == 'True' elif jelly_type == unicode_atom: return unicode(jelly[1], jelly[2]) else: raise UnjellyingError, "Don't know how to unjelly an object of type %s" % jelly_type def resolve_ref(self, obj, index=-1): self.references[index] = obj def register_res(self, marker, reassembler): self.markers[marker] = reassembler class Jellyable: def __getstate__(self): return self.__dict__ def jelly_for(self, jellier): sxp = [jellier.jelly(self.__class__), jellier.jelly(self.__getstate__())] return sxp class Unjellyable: def __setstate__(self, state): self.__dict__ = state def unjelly_for(self, unjellier, jelly_state): state = unjellier.unjelly(jelly_state) self.__setstate__(state) def dump(obj, file): jellied = ModJellier(_disassemblers,external_modules).jelly(obj) pickle.dump(jellied, file) def load(file): jellied = pickle.load(file) return ModUnjellier(_markers,external_modules).unjelly(jellied) def jelly(obj): return ModJellier(_disassemblers,external_modules).jelly(obj) def unjelly(jellied): return ModUnjellier(_markers,external_modules).unjelly(jellied) loads, dumps = jelly, unjelly def register_dis(obj_type, disassembler): _disassemblers[obj_type] = disassembler def register_res(marker, reassembler): _markers[marker] = reassembler dis_cls = lambda cls: (cls_marker, (cls.__name__,cls.__bases__,cls.__dict__)) dis_mod = lambda mod: (mod_marker, (mod.__dict__,)) dis_func = lambda func: (func_marker,(func.func_code,func.func_globals,func.func_name,func.func_defaults,func.func_closure)) dis_inst = lambda inst: (inst_marker,(inst.__class__,inst.__dict__)) #add __getstate__,__setstate__ support later, if security isn't a problem #dis_inst = lambda inst: (inst_marker, (inst.__class__,inst.__getstate__())) def res_mod(d): mod = types.ModuleType('FakeName','FakeDocs') mod.__dict__.update(d) if not d.has_key('__name__'): del mod.__name__ if not d.has_key('__doc__'): del mod.__doc__ return mod def dis_code(co):#From the python cookbook if co.co_freevars or co.co_cellvars: raise ValueError, "Sorry, cannot pickle code objects from closures" return code_marker,(co.co_argcount, co.co_nlocals, co.co_stacksize,co.co_flags, co.co_code, co.co_consts, co.co_names,co.co_varnames, co.co_filename, co.co_name,co.co_firstlineno, co.co_lnotab) def dis_meth(method): marker = meth_marker if method.im_self is None: marker = unbd_marker return marker,(method.im_func,method.im_self,method.im_class) debug_func = lambda func: (func_marker,(func.func_code,{},func.func_name,func.func_defaults,func.func_closure)) #register all *assemblers # type disassembler register_dis(types.ClassType, dis_cls) register_dis(types.ModuleType, dis_mod) register_dis(types.CodeType, dis_code) register_dis(types.FunctionType,dis_func) register_dis(types.InstanceType,dis_inst) register_dis(types.MethodType, dis_meth) # marker reassembler register_res(cls_marker, types.ClassType) register_res(mod_marker, res_mod) register_res(code_marker, types.CodeType) register_res(func_marker, types.FunctionType) register_res(inst_marker, types.InstanceType) register_res(meth_marker, types.MethodType) register_res(unbd_marker, types.UnboundMethodType) register_res(type_marker, types.TypeType)