1 /*
2  * Copyright (c) 2008-2009  Christian Hammond
3  * Copyright (c) 2008-2009  David Trowbridge
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21  * THE SOFTWARE.
22  */
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27 
28 #ifdef ENABLE_PYTHON
29 #include <Python.h>
30 #include <pygobject.h>
31 #include <pygtk/pygtk.h>
32 #endif // ENABLE_PYTHON
33 
34 #include <glib.h>
35 #include <glib/gi18n.h>
36 
37 #include <dlfcn.h>
38 
39 #include <signal.h>
40 
41 #include "python-hooks.h"
42 
43 
44 static gboolean python_enabled = FALSE;
45 static void *python_dlhandle = NULL;
46 
47 #ifdef ENABLE_PYTHON
48 static GString *captured_stdout = NULL;
49 static GString *captured_stderr = NULL;
50 
51 
52 static PyObject *
capture_stdout(PyObject * self,PyObject * args)53 capture_stdout(PyObject *self, PyObject *args)
54 {
55     char *str = NULL;
56 
57     if (!PyArg_ParseTuple(args, "s", &str))
58         return NULL;
59 
60     g_string_append(captured_stdout, str);
61 
62     Py_INCREF(Py_None);
63     return Py_None;
64 }
65 
66 static PyObject *
capture_stderr(PyObject * self,PyObject * args)67 capture_stderr(PyObject *self, PyObject *args)
68 {
69     char *str = NULL;
70 
71     if (!PyArg_ParseTuple(args, "s", &str))
72         return NULL;
73 
74     g_string_append(captured_stderr, str);
75 
76     Py_INCREF(Py_None);
77     return Py_None;
78 }
79 
80 static PyObject *
capture_stdin(PyObject * self,PyObject * args)81 capture_stdin(PyObject *self, PyObject *args)
82 {
83     /* Return an empty string.
84      * This is what read() returns when hitting EOF. */
85     return PyString_FromString("");
86 }
87 
88 static PyObject *
wrap_gobj(PyObject * self,PyObject * args)89 wrap_gobj(PyObject *self, PyObject *args)
90 {
91     void *addr;
92     GObject *obj;
93 
94     if (!PyArg_ParseTuple(args, "l", &addr))
95         return NULL;
96 
97     if (!G_IS_OBJECT(addr))
98         return NULL; // XXX
99 
100     obj = G_OBJECT(addr);
101 
102     if (!obj)
103         return NULL; // XXX
104 
105     return pygobject_new(obj);
106 }
107 
108 static PyMethodDef parasite_python_methods[] = {
109     {"capture_stdout", capture_stdout, METH_VARARGS, "Captures stdout"},
110     {"capture_stderr", capture_stderr, METH_VARARGS, "Captures stderr"},
111     {"capture_stdin", capture_stdin, METH_VARARGS, "Captures stdin"},
112     {"gobj", wrap_gobj, METH_VARARGS, "Wraps a C GObject"},
113     {NULL, NULL, 0, NULL}
114 };
115 
116 
117 static gboolean
is_blacklisted(void)118 is_blacklisted(void)
119 {
120     const char *prgname = g_get_prgname();
121 
122     return (!strcmp(prgname, "gimp"));
123 }
124 #endif // ENABLE_PYTHON
125 
126 int
parasite_python_init(char ** error)127 parasite_python_init(char **error)
128 {
129 #ifdef ENABLE_PYTHON
130     struct sigaction old_sigint;
131     PyObject *pygtk;
132 
133     if (is_blacklisted()) {
134       *error = g_strdup("Application is blacklisted");
135       return 0;
136     }
137 
138     /* This prevents errors such as "undefined symbol: PyExc_ImportError" */
139     python_dlhandle = dlopen(PYTHON_SHARED_LIB, RTLD_NOW | RTLD_GLOBAL);
140     if (python_dlhandle == NULL)
141     {
142         *error = g_strdup_printf("Parasite: Error on dlopen(): %s\n", dlerror());
143         return 0;
144     }
145 
146     captured_stdout = g_string_new("");
147     captured_stderr = g_string_new("");
148 
149     /* Back up and later restore SIGINT so Python doesn't steal it from us. */
150     sigaction(SIGINT, NULL, &old_sigint);
151 
152     if (!Py_IsInitialized())
153         Py_Initialize();
154 
155     sigaction(SIGINT, &old_sigint, NULL);
156 
157     Py_InitModule("parasite", parasite_python_methods);
158     if(PyRun_SimpleString(
159         "import parasite\n"
160         "import sys\n"
161         "\n"
162         "class StdoutCatcher:\n"
163         "    def write(self, str):\n"
164         "        parasite.capture_stdout(str)\n"
165         "    def flush(self):\n"
166         "        pass\n"
167         "\n"
168         "class StderrCatcher:\n"
169         "    def write(self, str):\n"
170         "        parasite.capture_stderr(str)\n"
171         "    def flush(self):\n"
172         "        pass\n"
173         "\n"
174         "class StdinCatcher:\n"
175         "    def readline(self, size=-1):\n"
176         "        return parasite.capture_stdin(size)\n"
177         "    def read(self, size=-1):\n"
178         "        return parasite.capture_stdin(size)\n"
179         "    def flush(self):\n"
180         "        pass\n"
181         "\n"
182     ) == -1) {
183       dlclose(python_dlhandle);
184       python_dlhandle = NULL;
185       return 0;
186     }
187 
188     if (!pygobject_init(-1, -1, -1)) {
189         dlclose(python_dlhandle);
190         python_dlhandle = NULL;
191         return 0;
192     }
193 
194     pygtk = PyImport_ImportModule("gtk");
195 
196     if (pygtk != NULL)
197     {
198         PyObject *module_dict = PyModule_GetDict(pygtk);
199         PyObject *cobject = PyDict_GetItemString(module_dict, "_PyGtk_API");
200 
201         /*
202          * This seems to be NULL when we're running a PyGTK program.
203          * We really need to find out why.
204          */
205         if (cobject != NULL)
206         {
207             if (PyCObject_Check(cobject)) {
208                 _PyGtk_API = (struct _PyGtk_FunctionStruct*)
209                 PyCObject_AsVoidPtr(cobject);
210             }
211 #if PY_VERSION_HEX >= 0x02070000
212             else if (PyCapsule_IsValid(cobject, "gtk._gtk._PyGtk_API")) {
213                 _PyGtk_API = (struct _PyGtk_FunctionStruct*)PyCapsule_GetPointer(cobject, "gtk._gtk._PyGtk_API");
214             }
215 #endif
216             else {
217               *error = g_strdup("Parasite: Could not find _PyGtk_API object");
218                 return 0;
219             }
220         }
221     } else {
222         *error = g_strdup("Parasite: Could not import gtk");
223         dlclose(python_dlhandle);
224         python_dlhandle = NULL;
225         return 0;
226     }
227 
228     python_enabled = TRUE;
229 #endif // ENABLE_PYTHON
230     return !0;
231 }
232 
233 void
parasite_python_done(void)234 parasite_python_done(void)
235 {
236 #ifdef ENABLE_PYTHON
237     if(python_dlhandle != NULL) {
238 	dlclose(python_dlhandle);
239 	python_dlhandle = NULL;
240     }
241 #endif
242 }
243 
244 void
parasite_python_run(const char * command,ParasitePythonLogger stdout_logger,ParasitePythonLogger stderr_logger,gpointer user_data)245 parasite_python_run(const char *command,
246                     ParasitePythonLogger stdout_logger,
247                     ParasitePythonLogger stderr_logger,
248                     gpointer user_data)
249 {
250 #ifdef ENABLE_PYTHON
251     PyGILState_STATE gstate;
252     PyObject *module;
253     PyObject *dict;
254     PyObject *obj;
255     const char *cp;
256 
257     /* empty string as command is a noop */
258     if(!strcmp(command, ""))
259       return;
260 
261     /* if first non-whitespace character is '#', command is also a noop */
262     cp = command;
263     while(cp && (*cp != '\0') && g_ascii_isspace(*cp))
264       cp++;
265     if(cp && *cp == '#')
266       return;
267 
268     gstate = PyGILState_Ensure();
269 
270     module = PyImport_AddModule("__main__");
271     dict = PyModule_GetDict(module);
272 
273     PyRun_SimpleString("old_stdout = sys.stdout\n"
274                        "old_stderr = sys.stderr\n"
275                        "old_stdin  = sys.stdin\n"
276                        "sys.stdout = StdoutCatcher()\n"
277                        "sys.stderr = StderrCatcher()\n"
278                        "sys.stdin  = StdinCatcher()\n");
279 
280     obj = PyRun_String(command, Py_single_input, dict, dict);
281     if(PyErr_Occurred())
282       PyErr_Print();
283     PyRun_SimpleString("sys.stdout = old_stdout\n"
284                        "sys.stderr = old_stderr\n"
285                        "sys.stdin = old_stdin\n");
286 
287     if (stdout_logger != NULL)
288         stdout_logger(captured_stdout->str, user_data);
289 
290     if (stderr_logger != NULL)
291         stderr_logger(captured_stderr->str, user_data);
292 
293     // Print any returned object
294     if (obj != NULL && obj != Py_None) {
295        PyObject *repr = PyObject_Repr(obj);
296        if (repr != NULL) {
297            char *string = PyString_AsString(repr);
298 
299            if (stdout_logger != NULL) {
300                stdout_logger(string, user_data);
301                stdout_logger("\n", user_data);
302            }
303         }
304 
305         Py_XDECREF(repr);
306     }
307     Py_XDECREF(obj);
308 
309     PyGILState_Release(gstate);
310     g_string_erase(captured_stdout, 0, -1);
311     g_string_erase(captured_stderr, 0, -1);
312 #endif // ENABLE_PYTHON
313 }
314 
315 gboolean
parasite_python_is_enabled(void)316 parasite_python_is_enabled(void)
317 {
318     return python_enabled;
319 }
320 
321 // vim: set et sw=4 ts=4:
322