python: Robust exception handling in callbacks.
[gpgme.git] / lang / python / helpers.c
1 /*
2 # $Id$
3 # Copyright (C) 2004 Igor Belyi <belyi@users.sourceforge.net>
4 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
5 #
6 #    This library is free software; you can redistribute it and/or
7 #    modify it under the terms of the GNU Lesser General Public
8 #    License as published by the Free Software Foundation; either
9 #    version 2.1 of the License, or (at your option) any later version.
10 #
11 #    This library is distributed in the hope that it will be useful,
12 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 #    Lesser General Public License for more details.
15 #
16 #    You should have received a copy of the GNU Lesser General Public
17 #    License along with this library; if not, write to the Free Software
18 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
19 */
20
21 #include <assert.h>
22 #include <stdio.h>
23 #include <gpgme.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "Python.h"
27 #include "helpers.h"
28
29 static PyObject *GPGMEError = NULL;
30
31 void pygpgme_exception_init(void) {
32   if (GPGMEError == NULL) {
33     PyObject *errors;
34     PyObject *from_list = PyList_New(0);
35     errors = PyImport_ImportModuleLevel("errors", PyEval_GetGlobals(),
36                                         PyEval_GetLocals(), from_list, 1);
37     Py_XDECREF(from_list);
38     if (errors) {
39       GPGMEError=PyDict_GetItemString(PyModule_GetDict(errors), "GPGMEError");
40       Py_XINCREF(GPGMEError);
41     }
42   }
43 }
44
45 gpgme_error_t pygpgme_exception2code(void) {
46   gpgme_error_t err_status = gpg_error(GPG_ERR_GENERAL);
47   if (GPGMEError && PyErr_ExceptionMatches(GPGMEError)) {
48     PyObject *type = 0, *value = 0, *traceback = 0;
49     PyObject *error = 0;
50     PyErr_Fetch(&type, &value, &traceback);
51     PyErr_NormalizeException(&type, &value, &traceback);
52     error = PyObject_GetAttrString(value, "error");
53     err_status = PyLong_AsLong(error);
54     Py_DECREF(error);
55     PyErr_Restore(type, value, traceback);
56   }
57   return err_status;
58 }
59
60 void pygpgme_clear_generic_cb(PyObject **cb) {
61   Py_DECREF(*cb);
62 }
63
64 /* Exception support for callbacks.  */
65 #define EXCINFO "_callback_excinfo"
66
67 static void pygpgme_stash_callback_exception(PyObject *self)
68 {
69   PyObject *ptype, *pvalue, *ptraceback, *excinfo;
70
71   PyErr_Fetch(&ptype, &pvalue, &ptraceback);
72   excinfo = PyTuple_New(3);
73   PyTuple_SetItem(excinfo, 0, ptype);
74
75   if (pvalue)
76     PyTuple_SetItem(excinfo, 1, pvalue);
77   else {
78     Py_INCREF(Py_None);
79     PyTuple_SetItem(excinfo, 1, Py_None);
80   }
81
82   if (ptraceback)
83     PyTuple_SetItem(excinfo, 2, ptraceback);
84   else {
85     Py_INCREF(Py_None);
86     PyTuple_SetItem(excinfo, 2, Py_None);
87   }
88
89   PyObject_SetAttrString(self, EXCINFO, excinfo);
90 }
91
92 PyObject *pygpgme_raise_callback_exception(PyObject *self)
93 {
94   PyObject *ptype, *pvalue, *ptraceback, *excinfo;
95
96   if (! PyObject_HasAttrString(self, EXCINFO))
97     goto leave;
98
99   excinfo = PyObject_GetAttrString(self, EXCINFO);
100   if (! PyTuple_Check(excinfo))
101     {
102       Py_DECREF(excinfo);
103       goto leave;
104     }
105
106   ptype = PyTuple_GetItem(excinfo, 0);
107   Py_INCREF(excinfo);
108
109   pvalue = PyTuple_GetItem(excinfo, 1);
110   if (pvalue == Py_None)
111     pvalue = NULL;
112   else
113     Py_INCREF(pvalue);
114
115   ptraceback = PyTuple_GetItem(excinfo, 2);
116   if (ptraceback == Py_None)
117     ptraceback = NULL;
118   else
119     Py_INCREF(ptraceback);
120
121   Py_DECREF(excinfo);
122   PyErr_Restore(ptype, pvalue, ptraceback);
123
124   Py_INCREF(Py_None);
125   PyObject_SetAttrString(self, EXCINFO, Py_None);
126
127   return NULL; /* Raise exception.  */
128
129  leave:
130   Py_INCREF(Py_None);
131   return Py_None;
132 }
133 #undef EXCINFO
134
135 static gpgme_error_t pyPassphraseCb(void *hook,
136                                     const char *uid_hint,
137                                     const char *passphrase_info,
138                                     int prev_was_bad,
139                                     int fd) {
140   PyObject *pyhook = (PyObject *) hook;
141   PyObject *self = NULL;
142   PyObject *func = NULL;
143   PyObject *args = NULL;
144   PyObject *retval = NULL;
145   PyObject *dataarg = NULL;
146   gpgme_error_t err_status = 0;
147
148   pygpgme_exception_init();
149
150   assert (PyTuple_Check(pyhook));
151   self = PyTuple_GetItem(pyhook, 0);
152   func = PyTuple_GetItem(pyhook, 1);
153   if (PyTuple_Size(pyhook) == 3) {
154     dataarg = PyTuple_GetItem(pyhook, 2);
155     args = PyTuple_New(4);
156   } else {
157     args = PyTuple_New(3);
158   }
159
160   if (uid_hint == NULL)
161     {
162       Py_INCREF(Py_None);
163       PyTuple_SetItem(args, 0, Py_None);
164     }
165   else
166     PyTuple_SetItem(args, 0, PyUnicode_DecodeUTF8(uid_hint, strlen (uid_hint),
167                                                   "strict"));
168   if (PyErr_Occurred()) {
169     Py_DECREF(args);
170     err_status = gpg_error(GPG_ERR_GENERAL);
171     goto leave;
172   }
173
174   PyTuple_SetItem(args, 1, PyBytes_FromString(passphrase_info));
175   PyTuple_SetItem(args, 2, PyBool_FromLong((long)prev_was_bad));
176   if (dataarg) {
177     Py_INCREF(dataarg);         /* Because GetItem doesn't give a ref but SetItem taketh away */
178     PyTuple_SetItem(args, 3, dataarg);
179   }
180
181   retval = PyObject_CallObject(func, args);
182   Py_DECREF(args);
183   if (PyErr_Occurred()) {
184     err_status = pygpgme_exception2code();
185   } else {
186     if (!retval) {
187       write(fd, "\n", 1);
188     } else {
189       char *buf;
190       size_t len;
191       if (PyBytes_Check(retval))
192         buf = PyBytes_AsString(retval), len = PyBytes_Size(retval);
193       else if (PyUnicode_Check(retval))
194         buf = PyUnicode_AsUTF8AndSize(retval, &len);
195       else
196         {
197           PyErr_Format(PyExc_TypeError,
198                        "expected str or bytes from passphrase callback, got %s",
199                        retval->ob_type->tp_name);
200           err_status = gpg_error(GPG_ERR_GENERAL);
201           goto leave;
202         }
203
204       write(fd, buf, len);
205       write(fd, "\n", 1);
206       Py_DECREF(retval);
207     }
208   }
209
210  leave:
211   if (err_status)
212     pygpgme_stash_callback_exception(self);
213
214   return err_status;
215 }
216
217 void pygpgme_set_passphrase_cb(gpgme_ctx_t ctx, PyObject *cb,
218                                PyObject **freelater) {
219   if (cb == Py_None) {
220     gpgme_set_passphrase_cb(ctx, NULL, NULL);
221     return;
222   }
223   Py_INCREF(cb);
224   *freelater = cb;
225   gpgme_set_passphrase_cb(ctx, (gpgme_passphrase_cb_t)pyPassphraseCb, (void *) cb);
226 }
227
228 static void pyProgressCb(void *hook, const char *what, int type, int current,
229                          int total) {
230   PyObject *func = NULL, *dataarg = NULL, *args = NULL, *retval = NULL;
231   PyObject *pyhook = (PyObject *) hook;
232
233   if (PyTuple_Check(pyhook)) {
234     func = PyTuple_GetItem(pyhook, 0);
235     dataarg = PyTuple_GetItem(pyhook, 1);
236     args = PyTuple_New(5);
237   } else {
238     func = pyhook;
239     args = PyTuple_New(4);
240   }
241
242   PyTuple_SetItem(args, 0, PyBytes_FromString(what));
243   PyTuple_SetItem(args, 1, PyLong_FromLong((long) type));
244   PyTuple_SetItem(args, 2, PyLong_FromLong((long) current));
245   PyTuple_SetItem(args, 3, PyLong_FromLong((long) total));
246   if (dataarg) {
247     Py_INCREF(dataarg);         /* Because GetItem doesn't give a ref but SetItem taketh away */
248     PyTuple_SetItem(args, 4, dataarg);
249   }
250
251   retval = PyObject_CallObject(func, args);
252   Py_DECREF(args);
253   Py_XDECREF(retval);
254 }
255
256 void pygpgme_set_progress_cb(gpgme_ctx_t ctx, PyObject *cb, PyObject **freelater){
257   if (cb == Py_None) {
258     gpgme_set_progress_cb(ctx, NULL, NULL);
259     return;
260   }
261   Py_INCREF(cb);
262   *freelater = cb;
263   gpgme_set_progress_cb(ctx, (gpgme_progress_cb_t) pyProgressCb, (void *) cb);
264 }