Skip to content

Commit

Permalink
Filter Python access with a function.
Browse files Browse the repository at this point in the history
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
davisp committed Jun 14, 2009
1 parent 44e4423 commit e8e69e9
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 20 deletions.
32 changes: 28 additions & 4 deletions README.md
Expand Up @@ -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
--------------------
Expand All @@ -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
--------------------

Expand All @@ -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
================
Expand Down
3 changes: 3 additions & 0 deletions THANKS
Expand Up @@ -27,3 +27,6 @@ Riccardo Pelizzi

Keiji Costantini
* Bug report on the memory limit test.

Richard Boulton
* Initial patch for filtering Python access.
117 changes: 113 additions & 4 deletions spidermonkey/context.c
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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__"))
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -473,6 +504,42 @@ Context_rem_global(Context* self, PyObject* args, PyObject* kwargs)
return ret;
}

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)
{
Expand Down Expand Up @@ -594,6 +661,12 @@ static PyMethodDef Context_methods[] = {
METH_VARARGS,
"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,
Expand Down Expand Up @@ -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)
{
Expand Down
3 changes: 3 additions & 0 deletions spidermonkey/context.h
Expand Up @@ -18,6 +18,7 @@ typedef struct {
PyObject_HEAD
Runtime* rt;
PyObject* global;
PyObject* access;
JSContext* cx;
JSObject* root;
PyDictObject* classes;
Expand All @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions spidermonkey/pyobject.c
Expand Up @@ -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();
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
Expand All @@ -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;

Expand Down
26 changes: 14 additions & 12 deletions spidermonkey/runtime.c
Expand Up @@ -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;

Expand Down

0 comments on commit e8e69e9

Please sign in to comment.