Mercurial > scratch
comparison 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 |
comparison
equal
deleted
inserted
replaced
20:8dd18f864351 | 21:1950b0b5bcd8 |
---|---|
3 import sys | 3 import sys |
4 import time | 4 import time |
5 import threading | 5 import threading |
6 import traceback | 6 import traceback |
7 import weakref | 7 import weakref |
8 import types | |
8 | 9 |
9 import pydermonkey | 10 import pydermonkey |
10 | 11 |
11 class ContextWatchdogThread(threading.Thread): | 12 class ContextWatchdogThread(threading.Thread): |
12 """ | 13 """ |
191 pass | 192 pass |
192 js_stack = js_stack['caller'] | 193 js_stack = js_stack['caller'] |
193 lines.insert(0, "Traceback (most recent call last):") | 194 lines.insert(0, "Traceback (most recent call last):") |
194 return '\n'.join(lines) | 195 return '\n'.join(lines) |
195 | 196 |
196 class JSSandbox(object): | 197 class JsExposedObject(object): |
198 """ | |
199 Trivial base/mixin class for any Python classes that choose to | |
200 expose themselves to JS code. | |
201 """ | |
202 | |
203 pass | |
204 | |
205 class JsSandbox(object): | |
197 """ | 206 """ |
198 A JS runtime and associated functionality capable of securely | 207 A JS runtime and associated functionality capable of securely |
199 loading and executing scripts. | 208 loading and executing scripts. |
200 """ | 209 """ |
201 | 210 |
213 self.cx = cx | 222 self.cx = cx |
214 self.curr_exc = None | 223 self.curr_exc = None |
215 self.py_stack = None | 224 self.py_stack = None |
216 self.js_stack = None | 225 self.js_stack = None |
217 self.__py_to_js = {} | 226 self.__py_to_js = {} |
227 self.__type_protos = {} | |
218 self.root = self.wrap_jsobject(root, root) | 228 self.root = self.wrap_jsobject(root, root) |
219 | 229 |
220 def finish(self): | 230 def finish(self): |
221 for jsobj in self.__py_to_js.values(): | 231 for jsobj in self.__py_to_js.values(): |
222 self.cx.clear_object_private(jsobj) | 232 self.cx.clear_object_private(jsobj) |
223 del self.__py_to_js | 233 del self.__py_to_js |
234 del self.__type_protos | |
224 del self.curr_exc | 235 del self.curr_exc |
225 del self.py_stack | 236 del self.py_stack |
226 del self.js_stack | 237 del self.js_stack |
227 del self.cx | 238 del self.cx |
228 del self.rt | 239 del self.rt |
237 if self.curr_exc != curr_exc: | 248 if self.curr_exc != curr_exc: |
238 self.curr_exc = curr_exc | 249 self.curr_exc = curr_exc |
239 self.py_stack = traceback.extract_stack() | 250 self.py_stack = traceback.extract_stack() |
240 self.js_stack = cx.get_stack() | 251 self.js_stack = cx.get_stack() |
241 | 252 |
242 def __wrap_pycallable(self, func): | 253 def __wrap_pycallable(self, func, pyproto=None): |
243 if func in self.__py_to_js: | 254 if func in self.__py_to_js: |
244 return self.__py_to_js[func] | 255 return self.__py_to_js[func] |
245 | 256 |
246 if hasattr(func, '__name__'): | 257 if hasattr(func, '__name__'): |
247 name = func.__name__ | 258 name = func.__name__ |
248 else: | 259 else: |
249 name = "" | 260 name = "" |
250 | 261 |
251 def wrapper(func_cx, this, args): | 262 if pyproto: |
252 try: | 263 def wrapper(func_cx, this, args): |
253 arglist = [] | 264 try: |
254 for arg in args: | 265 arglist = [] |
255 arglist.append(self.wrap_jsobject(arg)) | 266 for arg in args: |
256 # TODO: Fill in extra required params with pymonkey.undefined? | 267 arglist.append(self.wrap_jsobject(arg)) |
257 # or automatically throw an exception to calling js code? | 268 instance = func_cx.get_object_private(this) |
258 return func(*arglist) | 269 if instance is None or not isinstance(instance, pyproto): |
259 except pydermonkey.error: | 270 raise pydermonkey.error("Method type mismatch") |
260 raise | 271 |
261 except Exception: | 272 # TODO: Fill in extra required params with |
262 raise InternalError() | 273 # pymonkey.undefined? or automatically throw an |
274 # exception to calling js code? | |
275 return self.wrap_pyobject(func(instance, *arglist)) | |
276 except pydermonkey.error: | |
277 raise | |
278 except Exception: | |
279 raise InternalError() | |
280 else: | |
281 def wrapper(func_cx, this, args): | |
282 try: | |
283 arglist = [] | |
284 for arg in args: | |
285 arglist.append(self.wrap_jsobject(arg)) | |
286 | |
287 # TODO: Fill in extra required params with | |
288 # pymonkey.undefined? or automatically throw an | |
289 # exception to calling js code? | |
290 return self.wrap_pyobject(func(*arglist)) | |
291 except pydermonkey.error: | |
292 raise | |
293 except Exception: | |
294 raise InternalError() | |
263 wrapper.wrapped_pyobject = func | 295 wrapper.wrapped_pyobject = func |
264 wrapper.__name__ = name | 296 wrapper.__name__ = name |
265 | 297 |
266 jsfunc = self.cx.new_function(wrapper, name) | 298 jsfunc = self.cx.new_function(wrapper, name) |
267 self.__py_to_js[func] = jsfunc | 299 self.__py_to_js[func] = jsfunc |
268 | 300 |
269 return jsfunc | 301 return jsfunc |
270 | 302 |
303 def __wrap_pyinstance(self, value): | |
304 pyproto = type(value) | |
305 if pyproto not in self.__type_protos: | |
306 jsproto = self.cx.new_object() | |
307 for name in dir(pyproto): | |
308 attr = getattr(pyproto, name) | |
309 if (isinstance(attr, types.UnboundMethodType) and | |
310 hasattr(attr, '__jsexposed__') and | |
311 attr.__jsexposed__): | |
312 jsmethod = self.__wrap_pycallable(attr, pyproto) | |
313 self.cx.define_property(jsproto, name, jsmethod) | |
314 self.__type_protos[pyproto] = jsproto | |
315 return self.cx.new_object(value, self.__type_protos[pyproto]) | |
316 | |
271 def wrap_pyobject(self, value): | 317 def wrap_pyobject(self, value): |
318 if (isinstance(value, (int, basestring, float, bool)) or | |
319 value is pydermonkey.undefined or | |
320 value is None): | |
321 return value | |
272 if isinstance(value, SafeJsObjectWrapper): | 322 if isinstance(value, SafeJsObjectWrapper): |
273 # It's already wrapped, just unwrap it. | 323 # It's already wrapped, just unwrap it. |
274 return value.wrapped_jsobject | 324 return value.wrapped_jsobject |
275 elif callable(value): | 325 elif callable(value): |
276 return self.__wrap_pycallable(value) | 326 return self.__wrap_pycallable(value) |
277 elif isinstance(value, object): | 327 elif isinstance(value, JsExposedObject): |
278 raise NotImplementedError("TODO") | 328 return self.__wrap_pyinstance(value) |
279 else: | 329 else: |
280 # Here's hoping it's a primitive value. | 330 raise TypeError("Can't expose objects of type '' to JS." % |
281 return value | 331 type(value).__name__) |
282 | 332 |
283 def wrap_jsobject(self, jsvalue, this=None): | 333 def wrap_jsobject(self, jsvalue, this=None): |
284 if this is None: | 334 if this is None: |
285 this = self.root.wrapped_jsobject | 335 this = self.root.wrapped_jsobject |
286 if isinstance(jsvalue, pydermonkey.Function): | 336 if isinstance(jsvalue, pydermonkey.Function): |
287 if jsvalue.is_python: | 337 if jsvalue.is_python: |
288 # It's a Python function, just unwrap it. | 338 # It's a Python function, just unwrap it. |
289 return self.cx.get_object_private(jsvalue).wrapped_pyobject | 339 return self.cx.get_object_private(jsvalue).wrapped_pyobject |
290 return SafeJsFunctionWrapper(self, jsvalue, this) | 340 return SafeJsFunctionWrapper(self, jsvalue, this) |
291 elif isinstance(jsvalue, pydermonkey.Object): | 341 elif isinstance(jsvalue, pydermonkey.Object): |
292 return SafeJsObjectWrapper(self, jsvalue, this) | 342 # It's a wrapped Python object instance, just unwrap it. |
343 instance = self.cx.get_object_private(jsvalue) | |
344 if instance: | |
345 if not isinstance(instance, JsExposedObject): | |
346 raise AssertionError("Object private is not of type " | |
347 "JsExposedObject") | |
348 return instance | |
349 else: | |
350 return SafeJsObjectWrapper(self, jsvalue, this) | |
293 else: | 351 else: |
294 # It's a primitive value. | 352 # It's a primitive value. |
295 return jsvalue | 353 return jsvalue |
296 | 354 |
297 def run_script(self, filename): | 355 def run_script(self, filename): |
310 traceback.print_tb(e.exc_info[2]) | 368 traceback.print_tb(e.exc_info[2]) |
311 print e.exc_info[1] | 369 print e.exc_info[1] |
312 return retval | 370 return retval |
313 | 371 |
314 if __name__ == '__main__': | 372 if __name__ == '__main__': |
315 sandbox = JSSandbox() | 373 sandbox = JsSandbox() |
374 | |
375 class Baz(JsExposedObject): | |
376 def woozle(self, blap): | |
377 return blap + 5 | |
378 woozle.__jsexposed__ = True | |
379 | |
380 def baz(): | |
381 return Baz() | |
382 sandbox.root.baz = baz | |
316 | 383 |
317 def bar(obj): | 384 def bar(obj): |
318 print obj.bar() | 385 print obj.bar() |
319 sandbox.root.bar = bar | 386 sandbox.root.bar = bar |
320 | 387 |
321 def foo(callback): | 388 def foo(callback): |
322 return callback() | 389 return callback() |
323 sandbox.root.foo = foo | 390 sandbox.root.foo = foo |
391 | |
392 def ensureBaz(baz): | |
393 if not isinstance(baz, Baz): | |
394 print "Uhoh" | |
395 else: | |
396 print "ok" | |
397 sandbox.root.ensureBaz = ensureBaz | |
324 | 398 |
325 def jsprint(string): | 399 def jsprint(string): |
326 print string | 400 print string |
327 sandbox.root['print'] = jsprint | 401 sandbox.root['print'] = jsprint |
328 | 402 |