python: Simplify wrapping glue.
[gpgme.git] / lang / python / helpers.c
index 7e1c1c3..6de2b8d 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>
 #
@@ -41,6 +42,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 +76,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 +118,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)
@@ -154,7 +171,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)
@@ -185,64 +202,72 @@ object_to_gpgme_t(PyObject *input, const char *objtype, int argnum)
    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, PyObject **wrapper)
+object_to_gpgme_data_t(PyObject *input, int argnum, gpgme_data_t *wrapper,
+                       PyObject **bytesio, Py_buffer *view)
 {
-  static PyObject *Data = NULL;
-  PyObject *data = input;
+  gpgme_error_t err;
+  PyObject *data;
   PyObject *fd;
-  PyObject *result;
-  *wrapper = NULL;
-
-  if (Data == NULL) {
-    PyObject *core;
-    PyObject *from_list = PyList_New(0);
-    core = PyImport_ImportModuleLevel("core", PyEval_GetGlobals(),
-                                      PyEval_GetLocals(), from_list, 1);
-    Py_XDECREF(from_list);
-    if (core) {
-      Data = PyDict_GetItemString(PyModule_GetDict(core), "Data");
-      Py_XINCREF(Data);
-    }
-    else
-      return NULL;
-  }
 
+  /* See if it is a file-like object with file number.  */
   fd = PyObject_CallMethod(input, "fileno", NULL);
   if (fd) {
-    /* File-like object with file number.  */
-    PyObject *args = NULL;
-    PyObject *kw = NULL;
-
-    /* We don't need the fd, as we have no constructor accepting file
-       descriptors directly.  */
+    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;
+    }
 
-    args = PyTuple_New(0);
-    kw = PyDict_New();
-    if (args == NULL || kw == NULL)
-      {
-      fail:
-        Py_XDECREF(args);
-        Py_XDECREF(kw);
+  /* Do we have a buffer object?  */
+  if (PyObject_CheckBuffer(data))
+    {
+      if (PyObject_GetBuffer(data, view, PyBUF_SIMPLE) < 0)
         return NULL;
-      }
 
-    if (PyDict_SetItemString(kw, "file", input) < 0)
-      goto fail;
+      if (data != input)
+        Py_DECREF(data);
 
-    *wrapper = PyObject_Call(Data, args, kw);
-    if (*wrapper == NULL)
-      goto fail;
+      assert (view->ndim == 1);
+      assert (view->shape == NULL);
+      assert (view->strides == NULL);
+      assert (view->suboffsets == NULL);
 
-    Py_DECREF(args);
-    Py_DECREF(kw);
-    data = *wrapper;
-  }
-  else
-    PyErr_Clear();
+      err = gpgme_data_new_from_mem(wrapper, view->buf, (size_t) view->len, 0);
+      if (err)
+        return pygpgme_raise_exception (err);
 
-  result = object_to_gpgme_t(data, "gpgme_data_t", argnum);
-  return result;
+      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
@@ -264,6 +289,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) {
@@ -300,7 +326,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;
@@ -322,8 +351,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);
     }
   }
@@ -335,15 +371,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,
@@ -353,6 +421,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) {
@@ -384,14 +453,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.  */
@@ -449,15 +550,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.  */
@@ -492,17 +624,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;
@@ -729,9 +868,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,
@@ -739,12 +879,20 @@ 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;
 }