python: Support the Assuan engine.
[gpgme.git] / lang / python / helpers.c
index 3ecbacc..90173e4 100644 (file)
@@ -1,4 +1,5 @@
 /*
+# Copyright (C) 2016 g10 Code GmbH
 # Copyright (C) 2004 Igor Belyi <belyi@users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
 #
@@ -23,7 +24,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include "Python.h"
+
 #include "helpers.h"
+#include "private.h"
 
 static PyObject *GPGMEError = NULL;
 
@@ -41,6 +44,25 @@ void pygpgme_exception_init(void) {
   }
 }
 
+static PyObject *
+pygpgme_raise_exception(gpgme_error_t err)
+{
+  PyObject *e;
+
+  pygpgme_exception_init();
+  if (GPGMEError == NULL)
+    return PyErr_Format(PyExc_RuntimeError, "Got gpgme_error_t %d", err);
+
+  e = PyObject_CallFunction(GPGMEError, "l", (long) err);
+  if (e == NULL)
+    return NULL;
+
+  PyErr_SetObject(GPGMEError, e);
+  Py_DECREF(e);
+
+  return NULL; /* raise */
+}
+
 gpgme_error_t pygpgme_exception2code(void) {
   gpgme_error_t err_status = gpg_error(GPG_ERR_GENERAL);
   if (GPGMEError && PyErr_ExceptionMatches(GPGMEError)) {
@@ -56,10 +78,6 @@ gpgme_error_t pygpgme_exception2code(void) {
   return err_status;
 }
 
-void pygpgme_clear_generic_cb(PyObject **cb) {
-  Py_DECREF(*cb);
-}
-
 /* Exception support for callbacks.  */
 #define EXCINFO        "_callback_excinfo"
 
@@ -102,6 +120,7 @@ static void pygpgme_stash_callback_exception(PyObject *weak_self)
     }
   else
     PyObject_SetAttrString(self, EXCINFO, excinfo);
+  Py_DECREF(excinfo);
 }
 
 PyObject *pygpgme_raise_callback_exception(PyObject *self)
@@ -133,12 +152,18 @@ PyObject *pygpgme_raise_callback_exception(PyObject *self)
   else
     Py_INCREF(ptraceback);
 
+  /* We now have references for the extracted items.  */
   Py_DECREF(excinfo);
-  PyErr_Restore(ptype, pvalue, ptraceback);
 
+  /* Clear the exception information.  It is important to do this
+     before setting the error, because setting the attribute may
+     execute python code, and the runtime system raises a SystemError
+     if an exception is set but values are returned.  */
   Py_INCREF(Py_None);
   PyObject_SetAttrString(self, EXCINFO, Py_None);
 
+  /* Restore exception.  */
+  PyErr_Restore(ptype, pvalue, ptraceback);
   return NULL; /* Raise exception.  */
 
  leave:
@@ -154,7 +179,7 @@ PyObject *
 object_to_gpgme_t(PyObject *input, const char *objtype, int argnum)
 {
   PyObject *pyname = NULL, *pypointer = NULL;
-  pyname = PyObject_CallMethod(input, "_getctype", NULL);
+  pyname = PyObject_GetAttrString(input, "_ctype");
   if (pyname && PyUnicode_Check(pyname))
     {
       if (strcmp(PyUnicode_AsUTF8(pyname), objtype) != 0)
@@ -180,6 +205,112 @@ object_to_gpgme_t(PyObject *input, const char *objtype, int argnum)
   return pypointer;
 }
 
+/* Convert object to a pointer to gpgme type, version for data
+   objects.  Constructs a wrapper Python on the fly e.g. for file-like
+   objects with a fileno method, returning it in WRAPPER.  This object
+   must be de-referenced when no longer needed.  */
+PyObject *
+object_to_gpgme_data_t(PyObject *input, int argnum, gpgme_data_t *wrapper,
+                       PyObject **bytesio, Py_buffer *view)
+{
+  gpgme_error_t err;
+  PyObject *data;
+  PyObject *fd;
+
+  /* See if it is a file-like object with file number.  */
+  fd = PyObject_CallMethod(input, "fileno", NULL);
+  if (fd) {
+    err = gpgme_data_new_from_fd(wrapper, (int) PyLong_AsLong(fd));
+    Py_DECREF(fd);
+    if (err)
+      return pygpgme_raise_exception (err);
+
+    return pygpgme_wrap_gpgme_data_t(*wrapper);
+  }
+  else
+    PyErr_Clear();
+
+  /* No?  Maybe it implements the buffer protocol.  */
+  data = PyObject_CallMethod(input, "getbuffer", NULL);
+  if (data)
+    {
+      /* Save a reference to input, which seems to be a BytesIO
+         object.  */
+      Py_INCREF(input);
+      *bytesio = input;
+    }
+  else
+    {
+      PyErr_Clear();
+
+      /* No, but maybe the user supplied a buffer object?  */
+      data = input;
+    }
+
+  /* Do we have a buffer object?  */
+  if (PyObject_CheckBuffer(data))
+    {
+      if (PyObject_GetBuffer(data, view, PyBUF_SIMPLE) < 0)
+        return NULL;
+
+      if (data != input)
+        Py_DECREF(data);
+
+      assert (view->obj);
+      assert (view->ndim == 1);
+      assert (view->shape == NULL);
+      assert (view->strides == NULL);
+      assert (view->suboffsets == NULL);
+
+      err = gpgme_data_new_from_mem(wrapper, view->buf, (size_t) view->len, 0);
+      if (err)
+        return pygpgme_raise_exception (err);
+
+      return pygpgme_wrap_gpgme_data_t(*wrapper);
+    }
+
+  /* As last resort we assume it is a wrapped data object.  */
+  if (PyObject_HasAttrString(data, "_ctype"))
+    return object_to_gpgme_t(data, "gpgme_data_t", argnum);
+
+  return PyErr_Format(PyExc_TypeError,
+                      "arg %d: expected pyme.Data, file, or an object "
+                      "implementing the buffer protocol, got %s",
+                      argnum, data->ob_type->tp_name);
+}
+
+\f
+
+PyObject *
+pygpgme_wrap_fragile_result(PyObject *fragile, const char *classname)
+{
+  static PyObject *results;
+  PyObject *class;
+  PyObject *replacement;
+
+  if (results == NULL)
+    {
+      PyObject *from_list = PyList_New(0);
+      if (from_list == NULL)
+        return NULL;
+
+      results = PyImport_ImportModuleLevel("results", PyEval_GetGlobals(),
+                                           PyEval_GetLocals(), from_list, 1);
+      Py_DECREF(from_list);
+
+      if (results == NULL)
+        return NULL;
+    }
+
+  class = PyMapping_GetItemString(PyModule_GetDict(results), classname);
+  if (class == NULL)
+    return NULL;
+
+  replacement = PyObject_CallFunctionObjArgs(class, fragile, NULL);
+  Py_DECREF(class);
+  return replacement;
+}
+
 \f
 
 /* Callback support.  */
@@ -199,6 +330,7 @@ static gpgme_error_t pyPassphraseCb(void *hook,
   pygpgme_exception_init();
 
   assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2 || PyTuple_Size(pyhook) == 3);
   self = PyTuple_GetItem(pyhook, 0);
   func = PyTuple_GetItem(pyhook, 1);
   if (PyTuple_Size(pyhook) == 3) {
@@ -235,7 +367,10 @@ static gpgme_error_t pyPassphraseCb(void *hook,
     err_status = pygpgme_exception2code();
   } else {
     if (!retval) {
-      write(fd, "\n", 1);
+      if (write(fd, "\n", 1) < 0) {
+        err_status = gpgme_error_from_syserror ();
+        pygpgme_raise_exception (err_status);
+      }
     } else {
       char *buf;
       size_t len;
@@ -257,8 +392,15 @@ static gpgme_error_t pyPassphraseCb(void *hook,
           goto leave;
         }
 
-      write(fd, buf, len);
-      write(fd, "\n", 1);
+      if (write(fd, buf, len) < 0) {
+        err_status = gpgme_error_from_syserror ();
+        pygpgme_raise_exception (err_status);
+      }
+      if (! err_status && write(fd, "\n", 1) < 0) {
+        err_status = gpgme_error_from_syserror ();
+        pygpgme_raise_exception (err_status);
+      }
+
       Py_DECREF(retval);
     }
   }
@@ -270,15 +412,47 @@ static gpgme_error_t pyPassphraseCb(void *hook,
   return err_status;
 }
 
-void pygpgme_set_passphrase_cb(gpgme_ctx_t ctx, PyObject *cb,
-                              PyObject **freelater) {
+PyObject *
+pygpgme_set_passphrase_cb(PyObject *self, PyObject *cb) {
+  PyObject *wrapped;
+  gpgme_ctx_t ctx;
+
+  wrapped = PyObject_GetAttrString(self, "wrapped");
+  if (wrapped == NULL)
+    {
+      assert (PyErr_Occurred ());
+      return NULL;
+    }
+
+  ctx = pygpgme_unwrap_gpgme_ctx_t(wrapped);
+  Py_DECREF(wrapped);
+  if (ctx == NULL)
+    {
+      if (cb == Py_None)
+        goto out;
+      else
+        return PyErr_Format(PyExc_RuntimeError, "wrapped is NULL");
+    }
+
   if (cb == Py_None) {
     gpgme_set_passphrase_cb(ctx, NULL, NULL);
-    return;
+    PyObject_SetAttrString(self, "_passphrase_cb", Py_None);
+    goto out;
   }
-  Py_INCREF(cb);
-  *freelater = cb;
-  gpgme_set_passphrase_cb(ctx, (gpgme_passphrase_cb_t)pyPassphraseCb, (void *) cb);
+
+  if (! PyTuple_Check(cb))
+    return PyErr_Format(PyExc_TypeError, "cb must be a tuple");
+  if (PyTuple_Size(cb) != 2 && PyTuple_Size(cb) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "cb must be a tuple of size 2 or 3");
+
+  gpgme_set_passphrase_cb(ctx, (gpgme_passphrase_cb_t) pyPassphraseCb,
+                          (void *) cb);
+  PyObject_SetAttrString(self, "_passphrase_cb", cb);
+
+ out:
+  Py_INCREF(Py_None);
+  return Py_None;
 }
 
 static void pyProgressCb(void *hook, const char *what, int type, int current,
@@ -288,6 +462,7 @@ static void pyProgressCb(void *hook, const char *what, int type, int current,
   PyObject *self = NULL;
 
   assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2 || PyTuple_Size(pyhook) == 3);
   self = PyTuple_GetItem(pyhook, 0);
   func = PyTuple_GetItem(pyhook, 1);
   if (PyTuple_Size(pyhook) == 3) {
@@ -319,14 +494,46 @@ static void pyProgressCb(void *hook, const char *what, int type, int current,
   Py_XDECREF(retval);
 }
 
-void pygpgme_set_progress_cb(gpgme_ctx_t ctx, PyObject *cb, PyObject **freelater){
+PyObject *
+pygpgme_set_progress_cb(PyObject *self, PyObject *cb) {
+  PyObject *wrapped;
+  gpgme_ctx_t ctx;
+
+  wrapped = PyObject_GetAttrString(self, "wrapped");
+  if (wrapped == NULL)
+    {
+      assert (PyErr_Occurred ());
+      return NULL;
+    }
+
+  ctx = pygpgme_unwrap_gpgme_ctx_t(wrapped);
+  Py_DECREF(wrapped);
+  if (ctx == NULL)
+    {
+      if (cb == Py_None)
+        goto out;
+      else
+        return PyErr_Format(PyExc_RuntimeError, "wrapped is NULL");
+    }
+
   if (cb == Py_None) {
     gpgme_set_progress_cb(ctx, NULL, NULL);
-    return;
+    PyObject_SetAttrString(self, "_progress_cb", Py_None);
+    goto out;
   }
-  Py_INCREF(cb);
-  *freelater = cb;
+
+  if (! PyTuple_Check(cb))
+    return PyErr_Format(PyExc_TypeError, "cb must be a tuple");
+  if (PyTuple_Size(cb) != 2 && PyTuple_Size(cb) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "cb must be a tuple of size 2 or 3");
+
   gpgme_set_progress_cb(ctx, (gpgme_progress_cb_t) pyProgressCb, (void *) cb);
+  PyObject_SetAttrString(self, "_progress_cb", cb);
+
+ out:
+  Py_INCREF(Py_None);
+  return Py_None;
 }
 \f
 /* Status callbacks.  */
@@ -384,15 +591,46 @@ static gpgme_error_t pyStatusCb(void *hook, const char *keyword,
   return err;
 }
 
-void pygpgme_set_status_cb(gpgme_ctx_t ctx, PyObject *cb,
-                           PyObject **freelater) {
+PyObject *
+pygpgme_set_status_cb(PyObject *self, PyObject *cb) {
+  PyObject *wrapped;
+  gpgme_ctx_t ctx;
+
+  wrapped = PyObject_GetAttrString(self, "wrapped");
+  if (wrapped == NULL)
+    {
+      assert (PyErr_Occurred ());
+      return NULL;
+    }
+
+  ctx = pygpgme_unwrap_gpgme_ctx_t(wrapped);
+  Py_DECREF(wrapped);
+  if (ctx == NULL)
+    {
+      if (cb == Py_None)
+        goto out;
+      else
+        return PyErr_Format(PyExc_RuntimeError, "wrapped is NULL");
+    }
+
   if (cb == Py_None) {
     gpgme_set_status_cb(ctx, NULL, NULL);
-    return;
+    PyObject_SetAttrString(self, "_status_cb", Py_None);
+    goto out;
   }
-  Py_INCREF(cb);
-  *freelater = cb;
+
+  if (! PyTuple_Check(cb))
+    return PyErr_Format(PyExc_TypeError, "cb must be a tuple");
+  if (PyTuple_Size(cb) != 2 && PyTuple_Size(cb) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "cb must be a tuple of size 2 or 3");
+
   gpgme_set_status_cb(ctx, (gpgme_status_cb_t) pyStatusCb, (void *) cb);
+  PyObject_SetAttrString(self, "_status_cb", cb);
+
+ out:
+  Py_INCREF(Py_None);
+  return Py_None;
 }
 \f
 /* Edit callbacks.  */
@@ -427,17 +665,24 @@ gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
   Py_DECREF(pyargs);
   if (PyErr_Occurred()) {
     err_status = pygpgme_exception2code();
-    pygpgme_stash_callback_exception(self);
   } else {
     if (fd>=0 && retval && PyUnicode_Check(retval)) {
       const char *buffer;
       Py_ssize_t size;
 
       buffer = PyUnicode_AsUTF8AndSize(retval, &size);
-      write(fd, buffer, size);
-      write(fd, "\n", 1);
+      if (write(fd, buffer, size) < 0) {
+        err_status = gpgme_error_from_syserror ();
+        pygpgme_raise_exception (err_status);
+      }
+      if (! err_status && write(fd, "\n", 1) < 0) {
+        err_status = gpgme_error_from_syserror ();
+        pygpgme_raise_exception (err_status);
+      }
     }
   }
+  if (err_status)
+    pygpgme_stash_callback_exception(self);
 
   Py_XDECREF(retval);
   return err_status;
@@ -664,9 +909,10 @@ static void pyDataReleaseCb(void *hook)
     pygpgme_stash_callback_exception(self);
 }
 
-gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
-                                        PyObject *pycbs,
-                                        PyObject **freelater)
+PyObject *
+pygpgme_data_new_from_cbs(PyObject *self,
+                          PyObject *pycbs,
+                          gpgme_data_t *r_data)
 {
   static struct gpgme_data_cbs cbs = {
     pyDataReadCb,
@@ -674,12 +920,136 @@ gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
     pyDataSeekCb,
     pyDataReleaseCb,
   };
+  gpgme_error_t err;
+
+  if (! PyTuple_Check(pycbs))
+    return PyErr_Format(PyExc_TypeError, "pycbs must be a tuple");
+  if (PyTuple_Size(pycbs) != 5 && PyTuple_Size(pycbs) != 6)
+    return PyErr_Format(PyExc_TypeError,
+                        "pycbs must be a tuple of size 5 or 6");
 
-  assert (PyTuple_Check(pycbs));
-  assert (PyTuple_Size(pycbs) == 5 || PyTuple_Size(pycbs) == 6);
+  err = gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
+  if (err)
+    return pygpgme_raise_exception(err);
 
-  Py_INCREF(pycbs);
-  *freelater = pycbs;
+  PyObject_SetAttrString(self, "_data_cbs", pycbs);
 
-  return gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+\f
+
+/* The assuan callbacks.  */
+
+gpgme_error_t
+_pyme_assuan_data_cb (void *hook, const void *data, size_t datalen)
+{
+  gpgme_error_t err = 0;
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *py_data = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2);
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 1);
+  assert (PyCallable_Check(func));
+
+  py_data = PyBytes_FromStringAndSize(data, datalen);
+  if (py_data == NULL)
+    return NULL;       /* raise */
+
+  retval = PyObject_CallFunctionObjArgs(func, py_data, NULL);
+  if (PyErr_Occurred())
+    err = pygpgme_exception2code();
+  Py_DECREF(py_data);
+  Py_XDECREF(retval);
+
+ leave:
+  if (err)
+    pygpgme_stash_callback_exception(self);
+  return err;
+}
+
+gpgme_error_t
+_pyme_assuan_inquire_cb (void *hook, const char *name, const char *args,
+                         gpgme_data_t *r_data)
+{
+  gpgme_error_t err = 0;
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *py_name = NULL;
+  PyObject *py_args = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2);
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 1);
+  assert (PyCallable_Check(func));
+
+  py_name = PyUnicode_FromString(name);
+  if (py_name == NULL)
+    return NULL;       /* raise */
+
+  py_args = PyUnicode_FromString(args);
+  if (py_args == NULL)
+    return NULL;       /* raise */
+
+  retval = PyObject_CallFunctionObjArgs(func, py_name, py_args, NULL);
+  if (PyErr_Occurred())
+    err = pygpgme_exception2code();
+  Py_DECREF(py_name);
+  Py_DECREF(py_args);
+  Py_XDECREF(retval);
+
+  /* FIXME: Returning data is not yet implemented.  */
+  r_data = NULL;
+
+ leave:
+  if (err)
+    pygpgme_stash_callback_exception(self);
+  return err;
+}
+
+gpgme_error_t
+_pyme_assuan_status_cb (void *hook, const char *status, const char *args)
+{
+  gpgme_error_t err = 0;
+  PyObject *pyhook = (PyObject *) hook;
+  PyObject *self = NULL;
+  PyObject *func = NULL;
+  PyObject *py_status = NULL;
+  PyObject *py_args = NULL;
+  PyObject *retval = NULL;
+
+  assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2);
+  self = PyTuple_GetItem(pyhook, 0);
+  func = PyTuple_GetItem(pyhook, 1);
+  assert (PyCallable_Check(func));
+
+  py_status = PyUnicode_FromString(status);
+  if (py_status == NULL)
+    return NULL;       /* raise */
+
+  py_args = PyUnicode_FromString(args);
+  if (py_args == NULL)
+    return NULL;       /* raise */
+
+  retval = PyObject_CallFunctionObjArgs(func, py_status, py_args, NULL);
+  if (PyErr_Occurred())
+    err = pygpgme_exception2code();
+  Py_DECREF(py_status);
+  Py_DECREF(py_args);
+  Py_XDECREF(retval);
+
+ leave:
+  if (err)
+    pygpgme_stash_callback_exception(self);
+  return err;
 }