1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 #include <algorithm> // for std::replace
8 #include <signal.h> // for SIGINT and SIG_DFL
9 #include <string.h> // for strrchr
11 #include <boost/python.hpp>
13 #include "client-conf.h"
16 #include "python-bindings.h" // for pythonInitBindings
17 #include "resourcer.h"
20 #include <Python-ast.h>
23 namespace bp
= boost::python
;
25 static bp::object modMain
, modBltin
;
26 static bp::object dictMain
, dictBltin
;
28 int inPythonScript
= 0;
30 //! List of known safe Python modules allowed for importing.
31 static std::string moduleWhitelist
[] = {
40 static bool inWhitelist(const std::string
& name
)
42 for (int i
= 0; moduleWhitelist
[i
].size(); i
++)
43 if (name
== moduleWhitelist
[i
])
49 static void pythonIncludeModule(const char* name
)
51 bp::object
module(bp::handle
<>(PyImport_ImportModule(name
)));
52 dictMain
[name
] = module
;
55 static void pythonSetDefaultEncoding(const char* enc
)
57 if (PyUnicode_SetDefaultEncoding(enc
) != 0) {
58 PyErr_Format(PyExc_SystemError
,
59 "encoding %s not found", enc
);
60 bp::throw_error_already_set();
66 safeImport(PyObject
*, PyObject
* args
, PyObject
* kwds
)
68 static const char* kwlist
[] = {"name", "globals", "locals", "fromlist",
72 PyObject
* globals
, *locals
, *fromList
;
75 // Validate args from Python.
76 if (!PyArg_ParseTupleAndKeywords(args
, kwds
, "s|OOOi:__import__",
77 (char**)kwlist
, &_name
, &globals
, &locals
, &fromList
,
82 // Search whitelisted Python modules.
83 if (inWhitelist(name
))
84 return PyImport_ImportModuleLevel(_name
, globals
, locals
,
87 Log::info("Python", "import " + name
);
89 // Search Python scripts inside World.
90 std::replace(name
.begin(), name
.end(), '.', '/');
92 Resourcer
* rc
= Resourcer::instance();
93 if (rc
->resourceExists(name
)) {
94 rc
->runPythonScript(name
);
95 return modMain
.ptr(); // We have to return a module...
98 // Nothing acceptable found.
99 std::string msg
= std::string("Module '") + _name
+ "' not found or "
100 "not allowed. Note that Tsunagari runs in a sandbox and does "
101 "not allow most external modules.";
102 PyErr_Format(PyExc_ImportError
, msg
.c_str());
106 static PyObject
* nullExecfile(PyObject
*, PyObject
*)
108 PyErr_SetString(PyExc_RuntimeError
,
109 "file(): Tsunagari runs scripts in a sandbox and "
110 "does not allow accessing the standard filesystem");
114 static PyObject
* nullFile(PyObject
*, PyObject
*)
116 PyErr_SetString(PyExc_RuntimeError
,
117 "file(): Tsunagari runs scripts in a sandbox and "
118 "does not allow accessing the standard filesystem");
122 static PyObject
* nullOpen(PyObject
*, PyObject
*)
124 PyErr_SetString(PyExc_RuntimeError
,
125 "open(): Tsunagari runs scripts in a sandbox and "
126 "does not allow accessing the standard filesystem");
130 static PyObject
* nullPrint(PyObject
*, PyObject
*)
132 PyErr_SetString(PyExc_RuntimeError
,
133 "print(): Tsunagari does not allow scripts to print");
137 static PyObject
* nullReload(PyObject
*, PyObject
*)
139 PyErr_SetString(PyExc_RuntimeError
,
140 "reload(): Tsunagari does not allow module reloading");
144 PyMethodDef nullMethods
[] = {
145 {"__import__", (PyCFunction
)safeImport
, METH_VARARGS
| METH_KEYWORDS
, ""},
146 {"execfile", nullExecfile
, METH_VARARGS
, ""},
147 {"file", nullFile
, METH_VARARGS
, ""},
148 {"open", nullOpen
, METH_VARARGS
, ""},
149 {"print", nullPrint
, METH_VARARGS
| METH_KEYWORDS
, ""},
150 {"reload", nullReload
, METH_O
, ""},
151 {NULL
, NULL
, 0, NULL
},
157 PyImport_AppendInittab("tsunagari", &pythonInitBindings
);
159 pythonSetDefaultEncoding("utf-8");
161 modMain
= bp::import("__main__");
162 dictMain
= modMain
.attr("__dict__");
163 modBltin
= bp::import("__builtin__");
164 dictBltin
= modBltin
.attr("__dict__");
166 pythonIncludeModule("tsunagari");
168 // Hack in some rough safety. Disable external scripts and IO.
169 // InitModule doesn't remove existing modules, so we can use it to
170 // insert new methods into a pre-existing module.
171 PyObject
* module
= Py_InitModule("__builtin__", nullMethods
);
173 bp::throw_error_already_set();
175 // Restore the default SIGINT handler.
176 // Python messes with it. >:(
177 PyOS_setsig(SIGINT
, SIG_DFL
);
178 } catch (bp::error_already_set
) {
179 Log::fatal("Python", "An error occured while populating the "
181 Log::setVerbosity(V_NORMAL
); // Assure message can be seen.
188 void pythonFinalize()
193 static std::string
extractException(PyObject
* exc
, PyObject
* val
, PyObject
* tb
)
195 using namespace boost::python
;
197 handle
<> hexc(exc
), hval(allow_null(val
)), htb(allow_null(tb
));
199 return extract
<std::string
>(str(hexc
));
202 bp::object
traceback(import("traceback"));
203 bp::object
format_exception(traceback
.attr("format_exception"));
204 bp::object
formatted_list(format_exception(hexc
, hval
, htb
));
205 bp::object
formatted(str("").join(formatted_list
));
206 return extract
<std::string
>(formatted
);
211 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
213 char* type = PyExceptionClass_Name(exc);
214 char* dot = strrchr(type, '.');
217 char* value = PyString_AsString(val);
219 std::string msg = "";
220 msg += type ? type : "<unknown type>";
231 // Something bad happened. Error is already set in Python.
232 PyObject
* exc
, *val
, *tb
;
233 PyErr_Fetch(&exc
, &val
, &tb
);
237 "pythonErr() called, but no exception set");
241 PyErr_NormalizeException(&exc
, &val
, &tb
);
243 if (conf
.scriptHalt
) {
244 Log::fatal("Python", extractException(exc
, val
, tb
));
248 Log::err("Python", extractException(exc
, val
, tb
));
251 bp::object
pythonGlobals()
256 PyCodeObject
* pythonCompile(const char* fn
, const char* code
)
258 PyArena
*arena
= PyArena_New();
263 mod_ty mod
= PyParser_ASTFromString(code
, fn
, Py_file_input
, NULL
, arena
);
266 std::string(fn
) + ": failed to parse");
271 PyCodeObject
* co
= PyAST_Compile(mod
, fn
, NULL
, arena
);
274 std::string(fn
) + ": failed to compile");
283 bool pythonExec(PyCodeObject
* code
)
291 // FIXME: locals, globals
292 PyObject
* globals
= dictMain
.ptr();
293 PyObject
* result
= PyEval_EvalCode(code
, globals
, globals
);
299 } catch (boost::python::error_already_set
) {