Mercurial > scratch
diff pydershell/pydershell.py @ 21:1950b0b5bcd8
It's now possible to expose python class instances to JS, if they're declared the right way (for security purposes).
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Mon, 07 Sep 2009 15:33:35 -0700 |
parents | 8dd18f864351 |
children | 9413bebf2ee6 |
line wrap: on
line diff
--- a/pydershell/pydershell.py Mon Sep 07 12:59:13 2009 -0700 +++ b/pydershell/pydershell.py Mon Sep 07 15:33:35 2009 -0700 @@ -5,6 +5,7 @@ import threading import traceback import weakref +import types import pydermonkey @@ -193,7 +194,15 @@ lines.insert(0, "Traceback (most recent call last):") return '\n'.join(lines) -class JSSandbox(object): +class JsExposedObject(object): + """ + Trivial base/mixin class for any Python classes that choose to + expose themselves to JS code. + """ + + pass + +class JsSandbox(object): """ A JS runtime and associated functionality capable of securely loading and executing scripts. @@ -215,12 +224,14 @@ self.py_stack = None self.js_stack = None self.__py_to_js = {} + self.__type_protos = {} self.root = self.wrap_jsobject(root, root) def finish(self): for jsobj in self.__py_to_js.values(): self.cx.clear_object_private(jsobj) del self.__py_to_js + del self.__type_protos del self.curr_exc del self.py_stack del self.js_stack @@ -239,7 +250,7 @@ self.py_stack = traceback.extract_stack() self.js_stack = cx.get_stack() - def __wrap_pycallable(self, func): + def __wrap_pycallable(self, func, pyproto=None): if func in self.__py_to_js: return self.__py_to_js[func] @@ -248,18 +259,39 @@ else: name = "" - def wrapper(func_cx, this, args): - try: - arglist = [] - for arg in args: - arglist.append(self.wrap_jsobject(arg)) - # TODO: Fill in extra required params with pymonkey.undefined? - # or automatically throw an exception to calling js code? - return func(*arglist) - except pydermonkey.error: - raise - except Exception: - raise InternalError() + if pyproto: + def wrapper(func_cx, this, args): + try: + arglist = [] + for arg in args: + arglist.append(self.wrap_jsobject(arg)) + instance = func_cx.get_object_private(this) + if instance is None or not isinstance(instance, pyproto): + raise pydermonkey.error("Method type mismatch") + + # TODO: Fill in extra required params with + # pymonkey.undefined? or automatically throw an + # exception to calling js code? + return self.wrap_pyobject(func(instance, *arglist)) + except pydermonkey.error: + raise + except Exception: + raise InternalError() + else: + def wrapper(func_cx, this, args): + try: + arglist = [] + for arg in args: + arglist.append(self.wrap_jsobject(arg)) + + # TODO: Fill in extra required params with + # pymonkey.undefined? or automatically throw an + # exception to calling js code? + return self.wrap_pyobject(func(*arglist)) + except pydermonkey.error: + raise + except Exception: + raise InternalError() wrapper.wrapped_pyobject = func wrapper.__name__ = name @@ -268,17 +300,35 @@ return jsfunc + def __wrap_pyinstance(self, value): + pyproto = type(value) + if pyproto not in self.__type_protos: + jsproto = self.cx.new_object() + for name in dir(pyproto): + attr = getattr(pyproto, name) + if (isinstance(attr, types.UnboundMethodType) and + hasattr(attr, '__jsexposed__') and + attr.__jsexposed__): + jsmethod = self.__wrap_pycallable(attr, pyproto) + self.cx.define_property(jsproto, name, jsmethod) + self.__type_protos[pyproto] = jsproto + return self.cx.new_object(value, self.__type_protos[pyproto]) + def wrap_pyobject(self, value): + if (isinstance(value, (int, basestring, float, bool)) or + value is pydermonkey.undefined or + value is None): + return value if isinstance(value, SafeJsObjectWrapper): # It's already wrapped, just unwrap it. return value.wrapped_jsobject elif callable(value): return self.__wrap_pycallable(value) - elif isinstance(value, object): - raise NotImplementedError("TODO") + elif isinstance(value, JsExposedObject): + return self.__wrap_pyinstance(value) else: - # Here's hoping it's a primitive value. - return value + raise TypeError("Can't expose objects of type '' to JS." % + type(value).__name__) def wrap_jsobject(self, jsvalue, this=None): if this is None: @@ -289,7 +339,15 @@ return self.cx.get_object_private(jsvalue).wrapped_pyobject return SafeJsFunctionWrapper(self, jsvalue, this) elif isinstance(jsvalue, pydermonkey.Object): - return SafeJsObjectWrapper(self, jsvalue, this) + # It's a wrapped Python object instance, just unwrap it. + instance = self.cx.get_object_private(jsvalue) + if instance: + if not isinstance(instance, JsExposedObject): + raise AssertionError("Object private is not of type " + "JsExposedObject") + return instance + else: + return SafeJsObjectWrapper(self, jsvalue, this) else: # It's a primitive value. return jsvalue @@ -312,7 +370,16 @@ return retval if __name__ == '__main__': - sandbox = JSSandbox() + sandbox = JsSandbox() + + class Baz(JsExposedObject): + def woozle(self, blap): + return blap + 5 + woozle.__jsexposed__ = True + + def baz(): + return Baz() + sandbox.root.baz = baz def bar(obj): print obj.bar() @@ -322,6 +389,13 @@ return callback() sandbox.root.foo = foo + def ensureBaz(baz): + if not isinstance(baz, Baz): + print "Uhoh" + else: + print "ok" + sandbox.root.ensureBaz = ensureBaz + def jsprint(string): print string sandbox.root['print'] = jsprint