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