1 /*********************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 #include <algorithm> // for std::replace
28 #include <signal.h> // for SIGINT and SIG_DFL
29 #include <string.h> // for strrchr
31 #include <boost/python.hpp>
33 #include "client-conf.h"
36 #include "python-bindings.h" // for pythonInitBindings
37 #include "resourcer.h"
40 #include <Python-ast.h>
43 namespace bp
= boost::python
;
45 static bp::object modMain
, modBltin
;
46 static bp::object dictMain
, dictBltin
;
48 int inPythonScript
= 0;
50 //! List of known safe Python modules allowed for importing.
51 static std::string moduleWhitelist
[] = {
60 static bool inWhitelist(const std::string
& name
)
62 for (int i
= 0; moduleWhitelist
[i
].size(); i
++)
63 if (name
== moduleWhitelist
[i
])
69 static void pythonIncludeModule(const char* name
)
71 bp::object
module(bp::handle
<>(PyImport_ImportModule(name
)));
72 dictMain
[name
] = module
;
75 static void pythonSetDefaultEncoding(const char* enc
)
77 if (PyUnicode_SetDefaultEncoding(enc
) != 0) {
78 PyErr_Format(PyExc_SystemError
,
79 "encoding %s not found", enc
);
80 bp::throw_error_already_set();
86 safeImport(PyObject
*, PyObject
* args
, PyObject
* kwds
)
88 static const char* kwlist
[] = {"name", "globals", "locals", "fromlist",
92 PyObject
* globals
, *locals
, *fromList
;
95 // Validate args from Python.
96 if (!PyArg_ParseTupleAndKeywords(args
, kwds
, "s|OOOi:__import__",
97 (char**)kwlist
, &_name
, &globals
, &locals
, &fromList
,
102 // Search whitelisted Python modules.
103 if (inWhitelist(name
))
104 return PyImport_ImportModuleLevel(_name
, globals
, locals
,
107 Log::info("Python", "import " + name
);
109 // Search Python scripts inside World.
110 std::replace(name
.begin(), name
.end(), '.', '/');
112 Resourcer
* rc
= Resourcer::instance();
113 if (rc
->resourceExists(name
)) {
114 rc
->runPythonScript(name
);
115 return modMain
.ptr(); // We have to return a module...
118 // Nothing acceptable found.
119 std::string msg
= std::string("Module '") + _name
+ "' not found or "
120 "not allowed. Note that Tsunagari runs in a sandbox and does "
121 "not allow most external modules.";
122 PyErr_Format(PyExc_ImportError
, msg
.c_str());
126 static PyObject
* nullExecfile(PyObject
*, PyObject
*)
128 PyErr_SetString(PyExc_RuntimeError
,
129 "file(): Tsunagari runs scripts in a sandbox and "
130 "does not allow accessing the standard filesystem");
134 static PyObject
* nullFile(PyObject
*, PyObject
*)
136 PyErr_SetString(PyExc_RuntimeError
,
137 "file(): Tsunagari runs scripts in a sandbox and "
138 "does not allow accessing the standard filesystem");
142 static PyObject
* nullOpen(PyObject
*, PyObject
*)
144 PyErr_SetString(PyExc_RuntimeError
,
145 "open(): Tsunagari runs scripts in a sandbox and "
146 "does not allow accessing the standard filesystem");
150 static PyObject
* nullPrint(PyObject
*, PyObject
*)
152 PyErr_SetString(PyExc_RuntimeError
,
153 "print(): Tsunagari does not allow scripts to print");
157 static PyObject
* nullReload(PyObject
*, PyObject
*)
159 PyErr_SetString(PyExc_RuntimeError
,
160 "reload(): Tsunagari does not allow module reloading");
164 PyMethodDef nullMethods
[] = {
165 {"__import__", (PyCFunction
)safeImport
, METH_VARARGS
| METH_KEYWORDS
, ""},
166 {"execfile", nullExecfile
, METH_VARARGS
, ""},
167 {"file", nullFile
, METH_VARARGS
, ""},
168 {"open", nullOpen
, METH_VARARGS
, ""},
169 {"print", nullPrint
, METH_VARARGS
| METH_KEYWORDS
, ""},
170 {"reload", nullReload
, METH_O
, ""},
171 {NULL
, NULL
, 0, NULL
},
177 PyImport_AppendInittab("tsunagari", &pythonInitBindings
);
179 pythonSetDefaultEncoding("utf-8");
181 modMain
= bp::import("__main__");
182 dictMain
= modMain
.attr("__dict__");
183 modBltin
= bp::import("__builtin__");
184 dictBltin
= modBltin
.attr("__dict__");
186 pythonIncludeModule("tsunagari");
188 // Hack in some rough safety. Disable external scripts and IO.
189 // InitModule doesn't remove existing modules, so we can use it to
190 // insert new methods into a pre-existing module.
191 PyObject
* module
= Py_InitModule("__builtin__", nullMethods
);
193 bp::throw_error_already_set();
195 // Restore the default SIGINT handler.
196 // Python messes with it. >:(
197 PyOS_setsig(SIGINT
, SIG_DFL
);
198 } catch (bp::error_already_set
) {
199 Log::fatal("Python", "An error occured while populating the "
201 Log::setVerbosity(V_NORMAL
); // Assure message can be seen.
208 void pythonFinalize()
213 static std::string
extractException(PyObject
* exc
, PyObject
* val
, PyObject
* tb
)
215 using namespace boost::python
;
217 handle
<> hexc(exc
), hval(allow_null(val
)), htb(allow_null(tb
));
219 return extract
<std::string
>(str(hexc
));
222 bp::object
traceback(import("traceback"));
223 bp::object
format_exception(traceback
.attr("format_exception"));
224 bp::object
formatted_list(format_exception(hexc
, hval
, htb
));
225 bp::object
formatted(str("").join(formatted_list
));
226 return extract
<std::string
>(formatted
);
231 static std::string extractException2(PyObject* exc, PyObject* val, PyObject*)
233 char* type = PyExceptionClass_Name(exc);
234 char* dot = strrchr(type, '.');
237 char* value = PyString_AsString(val);
239 std::string msg = "";
240 msg += type ? type : "<unknown type>";
251 // Something bad happened. Error is already set in Python.
252 PyObject
* exc
, *val
, *tb
;
253 PyErr_Fetch(&exc
, &val
, &tb
);
257 "pythonErr() called, but no exception set");
261 PyErr_NormalizeException(&exc
, &val
, &tb
);
263 if (conf
.halting
== HALT_SCRIPT
) {
264 Log::fatal("Python", extractException(exc
, val
, tb
));
268 Log::err("Python", extractException(exc
, val
, tb
));
271 bp::object
pythonGlobals()
276 PyCodeObject
* pythonCompile(const char* fn
, const char* code
)
278 PyArena
*arena
= PyArena_New();
283 mod_ty mod
= PyParser_ASTFromString(code
, fn
, Py_file_input
, NULL
, arena
);
286 std::string(fn
) + ": failed to parse");
291 PyCodeObject
* co
= PyAST_Compile(mod
, fn
, NULL
, arena
);
294 std::string(fn
) + ": failed to compile");
303 bool pythonExec(PyCodeObject
* code
)
311 // FIXME: locals, globals
312 PyObject
* globals
= dictMain
.ptr();
313 PyObject
* result
= PyEval_EvalCode(code
, globals
, globals
);
319 } catch (boost::python::error_already_set
) {