Permalink
Browse files

Filter Python access with a function.

JavaScript requests for data from the Python VM can now be filtered by
specifying an access callback function on the Context instance. See the
README or test-access.py tests for API usage.

Thanks to Richard Boulton for the initial patch.
  • Loading branch information...
1 parent 44e4423 commit e8e69e9f5644907885c0b4bf4b0d5217e10443cd @davisp davisp committed Jun 14, 2009
Showing with 313 additions and 20 deletions.
  1. +28 −4 README.md
  2. +3 −0 THANKS
  3. +113 −4 spidermonkey/context.c
  4. +3 −0 spidermonkey/context.h
  5. +21 −0 spidermonkey/pyobject.c
  6. +14 −12 spidermonkey/runtime.c
  7. +125 −0 tests/test-access.py
  8. +6 −0 tests/test-global.py
View
@@ -137,7 +137,8 @@ Basics
>>> fruit = Orange()
>>> cx.add_global("apple", fruit)
>>> cx.execute('"Show me the " + apple.is_ripe("raisin");')
- Show me the ripe raisin
+ u'Show me the ripe raisin'
+
Playing with Classes
--------------------
@@ -151,13 +152,14 @@ Playing with Classes
...
>>> rt = spidermonkey.Runtime()
>>> cx = rt.new_context()
- >>> cx.add_global(Monkey)
+ >>> cx.add_global("Monkey", Monkey)
>>> monkey = cx.execute('var x = new Monkey(); x.baz = "schmammo"; x;')
>>> monkey.baz
- 'schmammo'
+ u'schmammo'
>>> monkey.__class__.__name__
'Monkey'
+
JavaScript Functions
--------------------
@@ -166,7 +168,29 @@ JavaScript Functions
>>> cx = rt.new_context()
>>> func = cx.execute('function(val) {return "whoosh: " + val;}')
>>> func("zipper!");
- 'whoosh: zipper!'
+ u'whoosh: zipper!'
+
+
+Filtering access to Python
+--------------------------
+
+ >>> import spidermonkey
+ >>> rt = spidermonkey.Runtime()
+ >>> def checker(obj, name):
+ ... return not name.startswith("_")
+ ...
+ >>> cx = rt.new_context(access=checker)
+ >>> # Alternatively:
+ >>> cx.set_access() #doctest: +ELLIPSIS
+ <function checker at ...>
+ >>> cx.set_access(checker) #doctest: +ELLIPSIS
+ <function checker at ...>
+ >>> cx.add_global("fish", {"gold": "gone", "_old_lady": "huzza"})
+ >>> cx.execute('fish["_old_lady"];')
+ Traceback (most recent call last):
+ ...
+ JSError: Error executing JavaScript.
+
Previous Authors
================
View
3 THANKS
@@ -27,3 +27,6 @@ Riccardo Pelizzi
Keiji Costantini
* Bug report on the memory limit test.
+
+Richard Boulton
+ * Initial patch for filtering Python access.
View
@@ -13,6 +13,9 @@
#include <jsobj.h>
#include <jscntxt.h>
+// Forward decl for add_prop
+JSBool set_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval);
+
JSBool
add_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
{
@@ -46,6 +49,9 @@ del_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
ret = JS_TRUE;
goto done;
}
+
+ // Check access to python land.
+ if(Context_has_access(pycx, jscx, pycx->global, pykey) <= 0) goto done;
// Bail if the global doesn't have a __delitem__
if(!PyObject_HasAttrString(pycx->global, "__delitem__"))
@@ -92,6 +98,8 @@ get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
pykey = js2py(pycx, key);
if(pykey == NULL) goto done;
+ if(Context_has_access(pycx, jscx, pycx->global, pykey) <= 0) goto done;
+
pyval = PyObject_GetItem(pycx->global, pykey);
if(pyval == NULL)
{
@@ -138,6 +146,8 @@ set_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
pykey = js2py(pycx, key);
if(pykey == NULL) goto done;
+ if(Context_has_access(pycx, jscx, pycx->global, pykey) <= 0) goto done;
+
pyval = js2py(pycx, *rval);
if(pyval == NULL) goto done;
@@ -175,6 +185,8 @@ resolve(JSContext* jscx, JSObject* jsobj, jsval key)
pykey = js2py(pycx, key);
if(pykey == NULL) goto done;
+
+ if(Context_has_access(pycx, jscx, pycx->global, pykey) <= 0) goto done;
if(!PyMapping_HasKey(pycx->global, pykey))
{
@@ -275,21 +287,36 @@ Context_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
Context* self = NULL;
Runtime* runtime = NULL;
PyObject* global = NULL;
+ PyObject* access = NULL;
+
+ char* keywords[] = {"runtime", "glbl", "access", NULL};
- if(!PyArg_ParseTuple(
- args,
- "O!|O",
+ if(!PyArg_ParseTupleAndKeywords(
+ args, kwargs,
+ "O!|OO",
+ keywords,
RuntimeType, &runtime,
- &global
+ &global,
+ &access
)) goto error;
+ if(global == Py_None) global = NULL;
+ if(access == Py_None) access = NULL;
+
if(global != NULL && !PyMapping_Check(global))
{
PyErr_SetString(PyExc_TypeError,
"Global handler must provide item access.");
goto error;
}
+ if(access != NULL && !PyCallable_Check(access))
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "Access handler must be callable.");
+ goto error;
+ }
+
self = (Context*) type->tp_alloc(type, 0);
if(self == NULL) goto error;
@@ -344,6 +371,9 @@ Context_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
if(global != NULL) Py_INCREF(global);
self->global = global;
+ if(access != NULL) Py_INCREF(access);
+ self->access = access;
+
// Setup counters for resource limits
self->branch_count = 0;
self->max_time = 0;
@@ -383,6 +413,7 @@ Context_dealloc(Context* self)
}
Py_XDECREF(self->global);
+ Py_XDECREF(self->access);
Py_XDECREF(self->objects);
Py_XDECREF(self->classes);
Py_XDECREF(self->rt);
@@ -474,6 +505,42 @@ Context_rem_global(Context* self, PyObject* args, PyObject* kwargs)
}
PyObject*
+Context_set_access(Context* self, PyObject* args, PyObject* kwargs)
+{
+ PyObject* ret = NULL;
+ PyObject* newval = NULL;
+
+ if(!PyArg_ParseTuple(args, "|O", &newval)) goto done;
+ if(newval != NULL && newval != Py_None)
+ {
+ if(!PyCallable_Check(newval))
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "Access handler must be callable.");
+ ret = NULL;
+ goto done;
+ }
+ }
+
+ ret = self->access;
+
+ if(newval != NULL && newval != Py_None)
+ {
+ Py_INCREF(newval);
+ self->access = newval;
+ }
+
+ if(ret == NULL)
+ {
+ ret = Py_None;
+ Py_INCREF(ret);
+ }
+
+done:
+ return ret;
+}
+
+PyObject*
Context_execute(Context* self, PyObject* args, PyObject* kwargs)
{
PyObject* obj = NULL;
@@ -595,6 +662,12 @@ static PyMethodDef Context_methods[] = {
"Remove a global object in the JS VM."
},
{
+ "set_access",
+ (PyCFunction)Context_set_access,
+ METH_VARARGS,
+ "Set the access handler for wrapped python objects."
+ },
+ {
"execute",
(PyCFunction)Context_execute,
METH_VARARGS,
@@ -663,6 +736,42 @@ PyTypeObject _ContextType = {
Context_new, /*tp_new*/
};
+int
+Context_has_access(Context* pycx, JSContext* jscx, PyObject* obj, PyObject* key)
+{
+ PyObject* tpl = NULL;
+ PyObject* tmp = NULL;
+ int res = -1;
+
+ if(pycx->access == NULL)
+ {
+ res = 1;
+ goto done;
+ }
+
+ tpl = Py_BuildValue("(OO)", obj, key);
+ if(tpl == NULL) goto done;
+
+ tmp = PyObject_Call(pycx->access, tpl, NULL);
+ res = PyObject_IsTrue(tmp);
+
+done:
+ Py_XDECREF(tpl);
+ Py_XDECREF(tmp);
+
+ if(res < 0)
+ {
+ PyErr_Clear();
+ JS_ReportError(jscx, "Failed to check python access.");
+ }
+ else if(res == 0)
+ {
+ JS_ReportError(jscx, "Python access prohibited.");
+ }
+
+ return res;
+}
+
PyObject*
Context_get_class(Context* cx, const char* key)
{
View
@@ -18,6 +18,7 @@ typedef struct {
PyObject_HEAD
Runtime* rt;
PyObject* global;
+ PyObject* access;
JSContext* cx;
JSObject* root;
PyDictObject* classes;
@@ -31,6 +32,8 @@ typedef struct {
PyObject* Context_get_class(Context* cx, const char* key);
int Context_add_class(Context* cx, const char* key, PyObject* val);
+int Context_has_access(Context*, JSContext*, PyObject*, PyObject*);
+
int Context_has_object(Context* cx, PyObject* val);
int Context_add_object(Context* cx, PyObject* val);
int Context_rem_object(Context* cx, PyObject* val);
View
@@ -58,6 +58,8 @@ js_del_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* val)
pykey = js2py(pycx, key);
if(pykey == NULL) goto error;
+ if(Context_has_access(pycx, jscx, pyobj, pykey) <= 0) goto error;
+
if(PyObject_DelItem(pyobj, pykey) < 0)
{
PyErr_Clear();
@@ -101,6 +103,8 @@ js_get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* val)
pykey = js2py(pycx, key);
if(pykey == NULL) goto done;
+ if(Context_has_access(pycx, jscx, pyobj, pykey) <= 0) goto done;
+
// Yeah. It's ugly as sin.
if(PyString_Check(pykey) || PyUnicode_Check(pykey))
{
@@ -177,6 +181,8 @@ js_set_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* val)
goto error;
}
+ if(Context_has_access(pycx, jscx, pyobj, pykey) <= 0) goto error;
+
pyval = js2py(pycx, *val);
if(pyval == NULL)
{
@@ -262,6 +268,7 @@ js_call(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
PyObject* pyobj = NULL;
PyObject* tpl = NULL;
PyObject* ret = NULL;
+ PyObject* attrcheck = NULL;
JSBool jsret = JS_FALSE;
pycx = (Context*) JS_GetContextPrivate(jscx);
@@ -279,6 +286,12 @@ js_call(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
goto error;
}
+ // Use '__call__' as a notice that we want to execute a function.
+ attrcheck = PyString_FromString("__call__");
+ if(attrcheck == NULL) goto error;
+
+ if(Context_has_access(pycx, jscx, pyobj, attrcheck) <= 0) goto error;
+
tpl = mk_args_tuple(pycx, jscx, argc, argv);
if(tpl == NULL) goto error;
@@ -307,6 +320,7 @@ js_call(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
success:
Py_XDECREF(tpl);
Py_XDECREF(ret);
+ Py_XDECREF(attrcheck);
return jsret;
}
@@ -317,6 +331,7 @@ js_ctor(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
PyObject* pyobj = NULL;
PyObject* tpl = NULL;
PyObject* ret = NULL;
+ PyObject* attrcheck = NULL;
JSBool jsret = JS_FALSE;
pycx = (Context*) JS_GetContextPrivate(jscx);
@@ -340,6 +355,12 @@ js_ctor(JSContext* jscx, JSObject* jsobj, uintN argc, jsval* argv, jsval* rval)
goto error;
}
+ // Use '__init__' to signal use as a constructor.
+ attrcheck = PyString_FromString("__init__");
+ if(attrcheck == NULL) goto error;
+
+ if(Context_has_access(pycx, jscx, pyobj, attrcheck) <= 0) goto error;
+
tpl = mk_args_tuple(pycx, jscx, argc, argv);
if(tpl == NULL) goto error;
View
@@ -51,20 +51,22 @@ Runtime_new_context(Runtime* self, PyObject* args, PyObject* kwargs)
{
PyObject* cx = NULL;
PyObject* tpl = NULL;
- PyObject* global = NULL;
+ PyObject* global = Py_None;
+ PyObject* access = Py_None;
- if(!PyArg_ParseTuple(args, "|O", &global)) goto error;
-
- if(global == NULL)
- {
- tpl = Py_BuildValue("(O)", self);
- }
- else
- {
- tpl = Py_BuildValue("(OO)", self, global);
- }
+ char* keywords[] = {"glbl", "access", NULL};
+
+ if(!PyArg_ParseTupleAndKeywords(
+ args, kwargs,
+ "|OO",
+ keywords,
+ &global,
+ &access
+ )) goto error;
+
+ tpl = Py_BuildValue("OOO", self, global, access);
if(tpl == NULL) goto error;
-
+
cx = PyObject_CallObject((PyObject*) ContextType, tpl);
goto success;
Oops, something went wrong.

0 comments on commit e8e69e9

Please sign in to comment.