python: Implement data 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 *weak_self)
68 {
69   PyObject *self, *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   self = PyWeakref_GetObject(weak_self);
90   /* self only has a borrowed reference.  */
91   if (self == Py_None) {
92     /* This should not happen, as even if we're called from the data
93        release callback triggered from the wrappers destructor, the
94        object is still alive and hence the weak reference still refers
95        to the object.  However, in case this ever changes, not seeing
96        any exceptions is worse than having a little extra code, so
97        here we go.  */
98       fprintf(stderr,
99               "Error occurred in callback, but the wrapper object "
100               "has been deallocated.\n");
101       PyErr_Restore(ptype, pvalue, ptraceback);
102       PyErr_Print();
103     }
104   else
105     PyObject_SetAttrString(self, EXCINFO, excinfo);
106 }
107
108 PyObject *pygpgme_raise_callback_exception(PyObject *self)
109 {
110   PyObject *ptype, *pvalue, *ptraceback, *excinfo;
111
112   if (! PyObject_HasAttrString(self, EXCINFO))
113     goto leave;
114
115   excinfo = PyObject_GetAttrString(self, EXCINFO);
116   if (! PyTuple_Check(excinfo))
117     {
118       Py_DECREF(excinfo);
119       goto leave;
120     }
121
122   ptype = PyTuple_GetItem(excinfo, 0);
123   Py_INCREF(excinfo);
124
125   pvalue = PyTuple_GetItem(excinfo, 1);
126   if (pvalue == Py_None)
127     pvalue = NULL;
128   else
129     Py_INCREF(pvalue);
130
131   ptraceback = PyTuple_GetItem(excinfo, 2);
132   if (ptraceback == Py_None)
133     ptraceback = NULL;
134   else
135     Py_INCREF(ptraceback);
136
137   Py_DECREF(excinfo);
138   PyErr_Restore(ptype, pvalue, ptraceback);
139
140   Py_INCREF(Py_None);
141   PyObject_SetAttrString(self, EXCINFO, Py_None);
142
143   return NULL; /* Raise exception.  */
144
145  leave:
146   Py_INCREF(Py_None);
147   return Py_None;
148 }
149 #undef EXCINFO
150
151 static gpgme_error_t pyPassphraseCb(void *hook,
152                                     const char *uid_hint,
153                                     const char *passphrase_info,
154                                     int prev_was_bad,
155                                     int fd) {
156   PyObject *pyhook = (PyObject *) hook;
157   PyObject *self = NULL;
158   PyObject *func = NULL;
159   PyObject *args = NULL;
160   PyObject *retval = NULL;
161   PyObject *dataarg = NULL;
162   gpgme_error_t err_status = 0;
163
164   pygpgme_exception_init();
165
166   assert (PyTuple_Check(pyhook));
167   self = PyTuple_GetItem(pyhook, 0);
168   func = PyTuple_GetItem(pyhook, 1);
169   if (PyTuple_Size(pyhook) == 3) {
170     dataarg = PyTuple_GetItem(pyhook, 2);
171     args = PyTuple_New(4);
172   } else {
173     args = PyTuple_New(3);
174   }
175
176   if (uid_hint == NULL)
177     {
178       Py_INCREF(Py_None);
179       PyTuple_SetItem(args, 0, Py_None);
180     }
181   else
182     PyTuple_SetItem(args, 0, PyUnicode_DecodeUTF8(uid_hint, strlen (uid_hint),
183                                                   "strict"));
184   if (PyErr_Occurred()) {
185     Py_DECREF(args);
186     err_status = gpg_error(GPG_ERR_GENERAL);
187     goto leave;
188   }
189
190   PyTuple_SetItem(args, 1, PyBytes_FromString(passphrase_info));
191   PyTuple_SetItem(args, 2, PyBool_FromLong((long)prev_was_bad));
192   if (dataarg) {
193     Py_INCREF(dataarg);         /* Because GetItem doesn't give a ref but SetItem taketh away */
194     PyTuple_SetItem(args, 3, dataarg);
195   }
196
197   retval = PyObject_CallObject(func, args);
198   Py_DECREF(args);
199   if (PyErr_Occurred()) {
200     err_status = pygpgme_exception2code();
201   } else {
202     if (!retval) {
203       write(fd, "\n", 1);
204     } else {
205       char *buf;
206       size_t len;
207       if (PyBytes_Check(retval))
208         buf = PyBytes_AsString(retval), len = PyBytes_Size(retval);
209       else if (PyUnicode_Check(retval))
210         buf = PyUnicode_AsUTF8AndSize(retval, &len);
211       else
212         {
213           PyErr_Format(PyExc_TypeError,
214                        "expected str or bytes from passphrase callback, got %s",
215                        retval->ob_type->tp_name);
216           err_status = gpg_error(GPG_ERR_GENERAL);
217           goto leave;
218         }
219
220       write(fd, buf, len);
221       write(fd, "\n", 1);
222       Py_DECREF(retval);
223     }
224   }
225
226  leave:
227   if (err_status)
228     pygpgme_stash_callback_exception(self);
229
230   return err_status;
231 }
232
233 void pygpgme_set_passphrase_cb(gpgme_ctx_t ctx, PyObject *cb,
234                                PyObject **freelater) {
235   if (cb == Py_None) {
236     gpgme_set_passphrase_cb(ctx, NULL, NULL);
237     return;
238   }
239   Py_INCREF(cb);
240   *freelater = cb;
241   gpgme_set_passphrase_cb(ctx, (gpgme_passphrase_cb_t)pyPassphraseCb, (void *) cb);
242 }
243
244 static void pyProgressCb(void *hook, const char *what, int type, int current,
245                          int total) {
246   PyObject *func = NULL, *dataarg = NULL, *args = NULL, *retval = NULL;
247   PyObject *pyhook = (PyObject *) hook;
248   PyObject *self = NULL;
249
250   assert (PyTuple_Check(pyhook));
251   self = PyTuple_GetItem(pyhook, 0);
252   func = PyTuple_GetItem(pyhook, 1);
253   if (PyTuple_Size(pyhook) == 3) {
254     dataarg = PyTuple_GetItem(pyhook, 2);
255     args = PyTuple_New(5);
256   } else {
257     args = PyTuple_New(4);
258   }
259
260   PyTuple_SetItem(args, 0, PyUnicode_DecodeUTF8(what, strlen (what),
261                                                 "strict"));
262   if (PyErr_Occurred()) {
263     pygpgme_stash_callback_exception(self);
264     Py_DECREF(args);
265     return;
266   }
267   PyTuple_SetItem(args, 1, PyLong_FromLong((long) type));
268   PyTuple_SetItem(args, 2, PyLong_FromLong((long) current));
269   PyTuple_SetItem(args, 3, PyLong_FromLong((long) total));
270   if (dataarg) {
271     Py_INCREF(dataarg);         /* Because GetItem doesn't give a ref but SetItem taketh away */
272     PyTuple_SetItem(args, 4, dataarg);
273   }
274
275   retval = PyObject_CallObject(func, args);
276   if (PyErr_Occurred())
277     pygpgme_stash_callback_exception(self);
278   Py_DECREF(args);
279   Py_XDECREF(retval);
280 }
281
282 void pygpgme_set_progress_cb(gpgme_ctx_t ctx, PyObject *cb, PyObject **freelater){
283   if (cb == Py_None) {
284     gpgme_set_progress_cb(ctx, NULL, NULL);
285     return;
286   }
287   Py_INCREF(cb);
288   *freelater = cb;
289   gpgme_set_progress_cb(ctx, (gpgme_progress_cb_t) pyProgressCb, (void *) cb);
290 }
291 \f
292 /* Status callbacks.  */
293 static gpgme_error_t pyStatusCb(void *hook, const char *keyword,
294                                 const char *args) {
295   gpgme_error_t err = 0;
296   PyObject *pyhook = (PyObject *) hook;
297   PyObject *self = NULL;
298   PyObject *func = NULL;
299   PyObject *dataarg = NULL;
300   PyObject *pyargs = NULL;
301   PyObject *retval = NULL;
302
303   assert (PyTuple_Check(pyhook));
304   assert (PyTuple_Size(pyhook) == 2 || PyTuple_Size(pyhook) == 3);
305   self = PyTuple_GetItem(pyhook, 0);
306   func = PyTuple_GetItem(pyhook, 1);
307   if (PyTuple_Size(pyhook) == 3) {
308     dataarg = PyTuple_GetItem(pyhook, 2);
309     pyargs = PyTuple_New(3);
310   } else {
311     pyargs = PyTuple_New(2);
312   }
313
314   if (keyword)
315     PyTuple_SetItem(pyargs, 0, PyUnicode_DecodeUTF8(keyword, strlen (keyword),
316                                                     "strict"));
317   else
318     {
319       Py_INCREF(Py_None);
320       PyTuple_SetItem(pyargs, 0, Py_None);
321     }
322   PyTuple_SetItem(pyargs, 1, PyUnicode_DecodeUTF8(args, strlen (args),
323                                                 "strict"));
324   if (PyErr_Occurred()) {
325     err = gpg_error(GPG_ERR_GENERAL);
326     Py_DECREF(pyargs);
327     goto leave;
328   }
329
330   if (dataarg) {
331     Py_INCREF(dataarg);
332     PyTuple_SetItem(pyargs, 2, dataarg);
333   }
334
335   retval = PyObject_CallObject(func, pyargs);
336   if (PyErr_Occurred())
337     err = pygpgme_exception2code();
338   Py_DECREF(pyargs);
339   Py_XDECREF(retval);
340
341  leave:
342   if (err)
343     pygpgme_stash_callback_exception(self);
344   return err;
345 }
346
347 void pygpgme_set_status_cb(gpgme_ctx_t ctx, PyObject *cb,
348                            PyObject **freelater) {
349   if (cb == Py_None) {
350     gpgme_set_status_cb(ctx, NULL, NULL);
351     return;
352   }
353   Py_INCREF(cb);
354   *freelater = cb;
355   gpgme_set_status_cb(ctx, (gpgme_status_cb_t) pyStatusCb, (void *) cb);
356 }
357 \f
358 /* Edit callbacks.  */
359 gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
360                        const char *args, int fd) {
361   PyObject *func = NULL, *dataarg = NULL, *pyargs = NULL, *retval = NULL;
362   PyObject *pyopaque = (PyObject *) opaque;
363   gpgme_error_t err_status = 0;
364   PyObject *self = NULL;
365
366   pygpgme_exception_init();
367
368   assert (PyTuple_Check(pyopaque));
369   assert (PyTuple_Size(pyopaque) == 2 || PyTuple_Size(pyopaque) == 3);
370   self = PyTuple_GetItem(pyopaque, 0);
371   func = PyTuple_GetItem(pyopaque, 1);
372   if (PyTuple_Size(pyopaque) == 3) {
373     dataarg = PyTuple_GetItem(pyopaque, 2);
374     pyargs = PyTuple_New(3);
375   } else {
376     pyargs = PyTuple_New(2);
377   }
378
379   PyTuple_SetItem(pyargs, 0, PyLong_FromLong((long) status));
380   PyTuple_SetItem(pyargs, 1, PyUnicode_FromString(args));
381   if (dataarg) {
382     Py_INCREF(dataarg);         /* Because GetItem doesn't give a ref but SetItem taketh away */
383     PyTuple_SetItem(pyargs, 2, dataarg);
384   }
385
386   retval = PyObject_CallObject(func, pyargs);
387   Py_DECREF(pyargs);
388   if (PyErr_Occurred()) {
389     err_status = pygpgme_exception2code();
390     pygpgme_stash_callback_exception(self);
391   } else {
392     if (fd>=0 && retval && PyUnicode_Check(retval)) {
393       const char *buffer;
394       Py_ssize_t size;
395
396       buffer = PyUnicode_AsUTF8AndSize(retval, &size);
397       write(fd, buffer, size);
398       write(fd, "\n", 1);
399     }
400   }
401
402   Py_XDECREF(retval);
403   return err_status;
404 }
405 \f
406 /* Data callbacks.  */
407
408 /* Read up to SIZE bytes into buffer BUFFER from the data object with
409    the handle HOOK.  Return the number of characters read, 0 on EOF
410    and -1 on error.  If an error occurs, errno is set.  */
411 static ssize_t pyDataReadCb(void *hook, void *buffer, size_t size)
412 {
413   ssize_t result;
414   PyObject *pyhook = (PyObject *) hook;
415   PyObject *self = NULL;
416   PyObject *func = NULL;
417   PyObject *dataarg = NULL;
418   PyObject *pyargs = NULL;
419   PyObject *retval = NULL;
420
421   assert (PyTuple_Check(pyhook));
422   assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
423
424   self = PyTuple_GetItem(pyhook, 0);
425   func = PyTuple_GetItem(pyhook, 1);
426   if (PyTuple_Size(pyhook) == 6) {
427     dataarg = PyTuple_GetItem(pyhook, 5);
428     pyargs = PyTuple_New(2);
429   } else {
430     pyargs = PyTuple_New(1);
431   }
432
433   PyTuple_SetItem(pyargs, 0, PyLong_FromSize_t(size));
434   if (dataarg) {
435     Py_INCREF(dataarg);
436     PyTuple_SetItem(pyargs, 1, dataarg);
437   }
438
439   retval = PyObject_CallObject(func, pyargs);
440   Py_DECREF(pyargs);
441   if (PyErr_Occurred()) {
442     pygpgme_stash_callback_exception(self);
443     result = -1;
444     goto leave;
445   }
446
447   if (! PyBytes_Check(retval)) {
448     PyErr_Format(PyExc_TypeError,
449                  "expected bytes from read callback, got %s",
450                  retval->ob_type->tp_name);
451     pygpgme_stash_callback_exception(self);
452     result = -1;
453     goto leave;
454   }
455
456   if (PyBytes_Size(retval) > size) {
457     PyErr_Format(PyExc_TypeError,
458                  "expected %zu bytes from read callback, got %zu",
459                  size, PyBytes_Size(retval));
460     pygpgme_stash_callback_exception(self);
461     result = -1;
462     goto leave;
463   }
464
465   memcpy(buffer, PyBytes_AsString(retval), PyBytes_Size(retval));
466   result = PyBytes_Size(retval);
467
468  leave:
469   Py_XDECREF(retval);
470   return result;
471 }
472
473 /* Write up to SIZE bytes from buffer BUFFER to the data object with
474    the handle HOOK.  Return the number of characters written, or -1
475    on error.  If an error occurs, errno is set.  */
476 static ssize_t pyDataWriteCb(void *hook, const void *buffer, size_t size)
477 {
478   ssize_t result;
479   PyObject *pyhook = (PyObject *) hook;
480   PyObject *self = NULL;
481   PyObject *func = NULL;
482   PyObject *dataarg = NULL;
483   PyObject *pyargs = NULL;
484   PyObject *retval = NULL;
485
486   assert (PyTuple_Check(pyhook));
487   assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
488
489   self = PyTuple_GetItem(pyhook, 0);
490   func = PyTuple_GetItem(pyhook, 2);
491   if (PyTuple_Size(pyhook) == 6) {
492     dataarg = PyTuple_GetItem(pyhook, 5);
493     pyargs = PyTuple_New(2);
494   } else {
495     pyargs = PyTuple_New(1);
496   }
497
498   PyTuple_SetItem(pyargs, 0, PyBytes_FromStringAndSize(buffer, size));
499   if (dataarg) {
500     Py_INCREF(dataarg);
501     PyTuple_SetItem(pyargs, 1, dataarg);
502   }
503
504   retval = PyObject_CallObject(func, pyargs);
505   Py_DECREF(pyargs);
506   if (PyErr_Occurred()) {
507     pygpgme_stash_callback_exception(self);
508     result = -1;
509     goto leave;
510   }
511
512   if (! PyLong_Check(retval)) {
513     PyErr_Format(PyExc_TypeError,
514                  "expected int from read callback, got %s",
515                  retval->ob_type->tp_name);
516     pygpgme_stash_callback_exception(self);
517     result = -1;
518     goto leave;
519   }
520
521   result = PyLong_AsSsize_t(retval);
522
523  leave:
524   Py_XDECREF(retval);
525   return result;
526 }
527
528 /* Set the current position from where the next read or write starts
529    in the data object with the handle HOOK to OFFSET, relativ to
530    WHENCE.  Returns the new offset in bytes from the beginning of the
531    data object.  */
532 static off_t pyDataSeekCb(void *hook, off_t offset, int whence)
533 {
534   off_t result;
535   PyObject *pyhook = (PyObject *) hook;
536   PyObject *self = NULL;
537   PyObject *func = NULL;
538   PyObject *dataarg = NULL;
539   PyObject *pyargs = NULL;
540   PyObject *retval = NULL;
541
542   assert (PyTuple_Check(pyhook));
543   assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
544
545   self = PyTuple_GetItem(pyhook, 0);
546   func = PyTuple_GetItem(pyhook, 3);
547   if (PyTuple_Size(pyhook) == 6) {
548     dataarg = PyTuple_GetItem(pyhook, 5);
549     pyargs = PyTuple_New(3);
550   } else {
551     pyargs = PyTuple_New(2);
552   }
553
554 #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
555   PyTuple_SetItem(pyargs, 0, PyLong_FromLongLong((long long) offset));
556 #else
557   PyTuple_SetItem(pyargs, 0, PyLong_FromLong((long) offset));
558 #endif
559   PyTuple_SetItem(pyargs, 1, PyLong_FromLong((long) whence));
560   if (dataarg) {
561     Py_INCREF(dataarg);
562     PyTuple_SetItem(pyargs, 2, dataarg);
563   }
564
565   retval = PyObject_CallObject(func, pyargs);
566   Py_DECREF(pyargs);
567   if (PyErr_Occurred()) {
568     pygpgme_stash_callback_exception(self);
569     result = -1;
570     goto leave;
571   }
572
573   if (! PyLong_Check(retval)) {
574     PyErr_Format(PyExc_TypeError,
575                  "expected int from read callback, got %s",
576                  retval->ob_type->tp_name);
577     pygpgme_stash_callback_exception(self);
578     result = -1;
579     goto leave;
580   }
581
582 #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
583   result = PyLong_AsLongLong(retval);
584 #else
585   result = PyLong_AsLong(retval);
586 #endif
587
588  leave:
589   Py_XDECREF(retval);
590   return result;
591 }
592
593 /* Close the data object with the handle HOOK.  */
594 static void pyDataReleaseCb(void *hook)
595 {
596   PyObject *pyhook = (PyObject *) hook;
597   PyObject *self = NULL;
598   PyObject *func = NULL;
599   PyObject *dataarg = NULL;
600   PyObject *pyargs = NULL;
601   PyObject *retval = NULL;
602
603   assert (PyTuple_Check(pyhook));
604   assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
605
606   self = PyTuple_GetItem(pyhook, 0);
607   func = PyTuple_GetItem(pyhook, 4);
608   if (PyTuple_Size(pyhook) == 6) {
609     dataarg = PyTuple_GetItem(pyhook, 5);
610     pyargs = PyTuple_New(1);
611   } else {
612     pyargs = PyTuple_New(0);
613   }
614
615   if (dataarg) {
616     Py_INCREF(dataarg);
617     PyTuple_SetItem(pyargs, 0, dataarg);
618   }
619
620   retval = PyObject_CallObject(func, pyargs);
621   Py_XDECREF(retval);
622   Py_DECREF(pyargs);
623   if (PyErr_Occurred())
624     pygpgme_stash_callback_exception(self);
625 }
626
627 gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
628                                         PyObject *pycbs,
629                                         PyObject **freelater)
630 {
631   static struct gpgme_data_cbs cbs = {
632     pyDataReadCb,
633     pyDataWriteCb,
634     pyDataSeekCb,
635     pyDataReleaseCb,
636   };
637   PyObject *dataarg = NULL;
638
639   assert (PyTuple_Check(pycbs));
640   assert (PyTuple_Size(pycbs) == 5 || PyTuple_Size(pycbs) == 6);
641
642   Py_INCREF(pycbs);
643   *freelater = pycbs;
644
645   return gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
646 }