diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index 84bc6e98..5b3c193b 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -180,10 +180,13 @@ PyObject* object_to_gpgme_t(PyObject* input, const char* objtype, int argnum) {
gpgme_data_t pubkey, gpgme_data_t seckey,
gpgme_data_t out};
-// SWIG has problem interpreting ssize_t, off_t or gpgme_error_t in gpgme.h
+/* SWIG has problems interpreting ssize_t, off_t or gpgme_error_t in
+ gpgme.h. */
+/* XXX: This is wrong at least for off_t if compiled with LFS. */
%typemap(out) ssize_t, off_t, gpgme_error_t, gpgme_err_code_t, gpgme_err_source_t, gpg_error_t {
$result = PyLong_FromLong($1);
}
+/* XXX: This is wrong at least for off_t if compiled with LFS. */
%typemap(in) ssize_t, off_t, gpgme_error_t, gpgme_err_code_t, gpgme_err_source_t, gpg_error_t {
$1 = PyLong_AsLong($input);
}
@@ -201,7 +204,7 @@ PyObject* object_to_gpgme_t(PyObject* input, const char* objtype, int argnum) {
Py_XDECREF($result); /* Blow away any previous result */
if (result < 0) { /* Check for I/O error */
free($1);
- return NULL;
+ return PyErr_SetFromErrno(PyExc_RuntimeError);
}
$result = PyBytes_FromStringAndSize($1,result);
free($1);
diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 9fe81c9f..4792c87d 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -402,3 +402,245 @@ gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
Py_XDECREF(retval);
return err_status;
}
+
+/* Data callbacks. */
+
+/* Read up to SIZE bytes into buffer BUFFER from the data object with
+ the handle HOOK. Return the number of characters read, 0 on EOF
+ and -1 on error. If an error occurs, errno is set. */
+static ssize_t pyDataReadCb(void *hook, void *buffer, size_t size)
+{
+ ssize_t result;
+ PyObject *pyhook = (PyObject *) hook;
+ PyObject *self = NULL;
+ PyObject *func = NULL;
+ PyObject *dataarg = NULL;
+ PyObject *pyargs = NULL;
+ PyObject *retval = NULL;
+
+ assert (PyTuple_Check(pyhook));
+ assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+ self = PyTuple_GetItem(pyhook, 0);
+ func = PyTuple_GetItem(pyhook, 1);
+ if (PyTuple_Size(pyhook) == 6) {
+ dataarg = PyTuple_GetItem(pyhook, 5);
+ pyargs = PyTuple_New(2);
+ } else {
+ pyargs = PyTuple_New(1);
+ }
+
+ PyTuple_SetItem(pyargs, 0, PyLong_FromSize_t(size));
+ if (dataarg) {
+ Py_INCREF(dataarg);
+ PyTuple_SetItem(pyargs, 1, dataarg);
+ }
+
+ retval = PyObject_CallObject(func, pyargs);
+ Py_DECREF(pyargs);
+ if (PyErr_Occurred()) {
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+ if (! PyBytes_Check(retval)) {
+ PyErr_Format(PyExc_TypeError,
+ "expected bytes from read callback, got %s",
+ retval->ob_type->tp_name);
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+ if (PyBytes_Size(retval) > size) {
+ PyErr_Format(PyExc_TypeError,
+ "expected %zu bytes from read callback, got %zu",
+ size, PyBytes_Size(retval));
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+ memcpy(buffer, PyBytes_AsString(retval), PyBytes_Size(retval));
+ result = PyBytes_Size(retval);
+
+ leave:
+ Py_XDECREF(retval);
+ return result;
+}
+
+/* Write up to SIZE bytes from buffer BUFFER to the data object with
+ the handle HOOK. Return the number of characters written, or -1
+ on error. If an error occurs, errno is set. */
+static ssize_t pyDataWriteCb(void *hook, const void *buffer, size_t size)
+{
+ ssize_t result;
+ PyObject *pyhook = (PyObject *) hook;
+ PyObject *self = NULL;
+ PyObject *func = NULL;
+ PyObject *dataarg = NULL;
+ PyObject *pyargs = NULL;
+ PyObject *retval = NULL;
+
+ assert (PyTuple_Check(pyhook));
+ assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+ self = PyTuple_GetItem(pyhook, 0);
+ func = PyTuple_GetItem(pyhook, 2);
+ if (PyTuple_Size(pyhook) == 6) {
+ dataarg = PyTuple_GetItem(pyhook, 5);
+ pyargs = PyTuple_New(2);
+ } else {
+ pyargs = PyTuple_New(1);
+ }
+
+ PyTuple_SetItem(pyargs, 0, PyBytes_FromStringAndSize(buffer, size));
+ if (dataarg) {
+ Py_INCREF(dataarg);
+ PyTuple_SetItem(pyargs, 1, dataarg);
+ }
+
+ retval = PyObject_CallObject(func, pyargs);
+ Py_DECREF(pyargs);
+ if (PyErr_Occurred()) {
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+ if (! PyLong_Check(retval)) {
+ PyErr_Format(PyExc_TypeError,
+ "expected int from read callback, got %s",
+ retval->ob_type->tp_name);
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+ result = PyLong_AsSsize_t(retval);
+
+ leave:
+ Py_XDECREF(retval);
+ return result;
+}
+
+/* Set the current position from where the next read or write starts
+ in the data object with the handle HOOK to OFFSET, relativ to
+ WHENCE. Returns the new offset in bytes from the beginning of the
+ data object. */
+static off_t pyDataSeekCb(void *hook, off_t offset, int whence)
+{
+ off_t result;
+ PyObject *pyhook = (PyObject *) hook;
+ PyObject *self = NULL;
+ PyObject *func = NULL;
+ PyObject *dataarg = NULL;
+ PyObject *pyargs = NULL;
+ PyObject *retval = NULL;
+
+ assert (PyTuple_Check(pyhook));
+ assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+ self = PyTuple_GetItem(pyhook, 0);
+ func = PyTuple_GetItem(pyhook, 3);
+ if (PyTuple_Size(pyhook) == 6) {
+ dataarg = PyTuple_GetItem(pyhook, 5);
+ pyargs = PyTuple_New(3);
+ } else {
+ pyargs = PyTuple_New(2);
+ }
+
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
+ PyTuple_SetItem(pyargs, 0, PyLong_FromLongLong((long long) offset));
+#else
+ PyTuple_SetItem(pyargs, 0, PyLong_FromLong((long) offset));
+#endif
+ PyTuple_SetItem(pyargs, 1, PyLong_FromLong((long) whence));
+ if (dataarg) {
+ Py_INCREF(dataarg);
+ PyTuple_SetItem(pyargs, 2, dataarg);
+ }
+
+ retval = PyObject_CallObject(func, pyargs);
+ Py_DECREF(pyargs);
+ if (PyErr_Occurred()) {
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+ if (! PyLong_Check(retval)) {
+ PyErr_Format(PyExc_TypeError,
+ "expected int from read callback, got %s",
+ retval->ob_type->tp_name);
+ pygpgme_stash_callback_exception(self);
+ result = -1;
+ goto leave;
+ }
+
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
+ result = PyLong_AsLongLong(retval);
+#else
+ result = PyLong_AsLong(retval);
+#endif
+
+ leave:
+ Py_XDECREF(retval);
+ return result;
+}
+
+/* Close the data object with the handle HOOK. */
+static void pyDataReleaseCb(void *hook)
+{
+ PyObject *pyhook = (PyObject *) hook;
+ PyObject *self = NULL;
+ PyObject *func = NULL;
+ PyObject *dataarg = NULL;
+ PyObject *pyargs = NULL;
+ PyObject *retval = NULL;
+
+ assert (PyTuple_Check(pyhook));
+ assert (PyTuple_Size(pyhook) == 5 || PyTuple_Size(pyhook) == 6);
+
+ self = PyTuple_GetItem(pyhook, 0);
+ func = PyTuple_GetItem(pyhook, 4);
+ if (PyTuple_Size(pyhook) == 6) {
+ dataarg = PyTuple_GetItem(pyhook, 5);
+ pyargs = PyTuple_New(1);
+ } else {
+ pyargs = PyTuple_New(0);
+ }
+
+ if (dataarg) {
+ Py_INCREF(dataarg);
+ PyTuple_SetItem(pyargs, 0, dataarg);
+ }
+
+ retval = PyObject_CallObject(func, pyargs);
+ Py_XDECREF(retval);
+ Py_DECREF(pyargs);
+ if (PyErr_Occurred())
+ pygpgme_stash_callback_exception(self);
+}
+
+gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
+ PyObject *pycbs,
+ PyObject **freelater)
+{
+ static struct gpgme_data_cbs cbs = {
+ pyDataReadCb,
+ pyDataWriteCb,
+ pyDataSeekCb,
+ pyDataReleaseCb,
+ };
+ PyObject *dataarg = NULL;
+
+ assert (PyTuple_Check(pycbs));
+ assert (PyTuple_Size(pycbs) == 5 || PyTuple_Size(pycbs) == 6);
+
+ Py_INCREF(pycbs);
+ *freelater = pycbs;
+
+ return gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
+}
diff --git a/lang/python/helpers.h b/lang/python/helpers.h
index 5dd88a06..8b900085 100644
--- a/lang/python/helpers.h
+++ b/lang/python/helpers.h
@@ -40,3 +40,7 @@ void pygpgme_set_status_cb(gpgme_ctx_t ctx, PyObject *cb,
gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
const char *args, int fd);
+
+gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
+ PyObject *pycbs,
+ PyObject **freelater);
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 6ef2dabe..e89c1812 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -384,7 +384,7 @@ class Data(GpgmeWrapper):
If cbs is specified, it MUST be a tuple of the form:
- ((read_cb, write_cb, seek_cb, release_cb), hook)
+ (read_cb, write_cb, seek_cb, release_cb[, hook])
where func is a callback function taking two arguments (count,
hook) and returning a string of read data, or None on EOF.
@@ -396,7 +396,7 @@ class Data(GpgmeWrapper):
Any other use will result in undefined or erroneous behavior."""
super().__init__(None)
- self.last_readcb = None
+ self.data_cbs = None
if cbs != None:
self.new_from_cbs(*cbs)
@@ -419,15 +419,18 @@ class Data(GpgmeWrapper):
if self.wrapped != None and pygpgme.gpgme_data_release:
pygpgme.gpgme_data_release(self.wrapped)
- self._free_readcb()
+ if self._callback_excinfo:
+ print(self._callback_excinfo)
+ pygpgme.pygpgme_raise_callback_exception(self)
+ self._free_datacbs()
- def _free_readcb(self):
- if self.last_readcb != None:
+ def _free_datacbs(self):
+ if self.data_cbs != None:
if pygpgme.pygpgme_clear_generic_cb:
- pygpgme.pygpgme_clear_generic_cb(self.last_readcb)
+ pygpgme.pygpgme_clear_generic_cb(self.data_cbs)
if pygpgme.delete_PyObject_p_p:
- pygpgme.delete_PyObject_p_p(self.last_readcb)
- self.last_readcb = None
+ pygpgme.delete_PyObject_p_p(self.data_cbs)
+ self.data_cbs = None
def new(self):
tmp = pygpgme.new_gpgme_data_t_p()
@@ -453,14 +456,18 @@ class Data(GpgmeWrapper):
self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
pygpgme.delete_gpgme_data_t_p(tmp)
- def new_from_cbs(self, funcs, hook):
- """Argument funcs must be a 4 element tuple with callbacks:
- (read_cb, write_cb, seek_cb, release_cb)"""
+ def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
+ assert self.data_cbs == None
+ self.data_cbs = pygpgme.new_PyObject_p_p()
tmp = pygpgme.new_gpgme_data_t_p()
- self._free_readcb()
- self.last_readcb = pygpgme.new_PyObject_p_p()
- hookdata = (funcs, hook)
- pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.last_readcb)
+ if hook != None:
+ hookdata = (weakref.ref(self),
+ read_cb, write_cb, seek_cb, release_cb, hook)
+ else:
+ hookdata = (weakref.ref(self),
+ read_cb, write_cb, seek_cb, release_cb)
+ errorcheck(
+ pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.data_cbs))
self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
pygpgme.delete_gpgme_data_t_p(tmp)
@@ -512,7 +519,10 @@ class Data(GpgmeWrapper):
If a string is given, it is implicitly encoded using UTF-8."""
written = pygpgme.gpgme_data_write(self.wrapped, buffer)
if written < 0:
- raise GPGMEError.fromSyserror()
+ if self._callback_excinfo:
+ pygpgme.pygpgme_raise_callback_exception(self)
+ else:
+ raise GPGMEError.fromSyserror()
return written
def read(self, size = -1):
@@ -527,11 +537,24 @@ class Data(GpgmeWrapper):
return ''
if size > 0:
- return pygpgme.gpgme_data_read(self.wrapped, size)
+ try:
+ result = pygpgme.gpgme_data_read(self.wrapped, size)
+ except:
+ if self._callback_excinfo:
+ pygpgme.pygpgme_raise_callback_exception(self)
+ else:
+ raise
+ return result
else:
chunks = []
- while 1:
- result = pygpgme.gpgme_data_read(self.wrapped, 4096)
+ while True:
+ try:
+ result = pygpgme.gpgme_data_read(self.wrapped, 4096)
+ except:
+ if self._callback_excinfo:
+ pygpgme.pygpgme_raise_callback_exception(self)
+ else:
+ raise
if len(result) == 0:
break
chunks.append(result)
diff --git a/lang/python/tests/t-callbacks.py b/lang/python/tests/t-callbacks.py
index 57975264..3219463c 100755
--- a/lang/python/tests/t-callbacks.py
+++ b/lang/python/tests/t-callbacks.py
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see .
+import os
from pyme import core, constants
import support
@@ -181,3 +182,73 @@ except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
+
+
+
+# Test the data callbacks.
+def read_cb(amount, hook=None):
+ assert hook == cookie
+ return 0
+def release_cb(hook=None):
+ assert hook == cookie
+data = core.Data(cbs=(read_cb, None, None, release_cb, cookie))
+try:
+ data.read()
+except Exception as e:
+ assert type(e) == TypeError
+else:
+ assert False, "Expected an error, got none"
+
+def read_cb(amount):
+ raise myException
+data = core.Data(cbs=(read_cb, None, None, lambda: None))
+try:
+ data.read()
+except Exception as e:
+ assert e == myException
+else:
+ assert False, "Expected an error, got none"
+
+
+def write_cb(what, hook=None):
+ assert hook == cookie
+ return "wrong type"
+data = core.Data(cbs=(None, write_cb, None, release_cb, cookie))
+try:
+ data.write(b'stuff')
+except Exception as e:
+ assert type(e) == TypeError
+else:
+ assert False, "Expected an error, got none"
+
+def write_cb(what):
+ raise myException
+data = core.Data(cbs=(None, write_cb, None, lambda: None))
+try:
+ data.write(b'stuff')
+except Exception as e:
+ assert e == myException
+else:
+ assert False, "Expected an error, got none"
+
+
+def seek_cb(offset, whence, hook=None):
+ assert hook == cookie
+ return "wrong type"
+data = core.Data(cbs=(None, None, seek_cb, release_cb, cookie))
+try:
+ data.seek(0, os.SEEK_SET)
+except Exception as e:
+ assert type(e) == TypeError
+else:
+ assert False, "Expected an error, got none"
+
+def seek_cb(offset, whence):
+ raise myException
+data = core.Data(cbs=(None, None, seek_cb, lambda: None))
+try:
+ data.seek(0, os.SEEK_SET)
+except Exception as e:
+ assert e == myException
+else:
+ assert False, "Expected an error, got none"
diff --git a/lang/python/tests/t-data.py b/lang/python/tests/t-data.py
index 6cf10fab..de60c473 100755
--- a/lang/python/tests/t-data.py
+++ b/lang/python/tests/t-data.py
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see .
+import io
import os
import tempfile
from pyme import core
@@ -79,3 +80,43 @@ with tempfile.NamedTemporaryFile() as tmp:
# Open using name, offset, and length.
data = core.Data(file=tmp.name, offset=23, length=42)
assert data.read() == binjunk[23:23+42]
+
+# Test callbacks.
+class DataObject(object):
+ def __init__(self):
+ self.buffer = io.BytesIO()
+ self.released = False
+
+ def read(self, amount, hook=None):
+ assert not self.released
+ return self.buffer.read(amount)
+
+ def write(self, data, hook=None):
+ assert not self.released
+ return self.buffer.write(data)
+
+ def seek(self, offset, whence, hook=None):
+ assert not self.released
+ return self.buffer.seek(offset, whence)
+
+ def release(self, hook=None):
+ assert not self.released
+ self.released = True
+
+do = DataObject()
+cookie = object()
+data = core.Data(cbs=(do.read, do.write, do.seek, do.release, cookie))
+data.write('Hello world!')
+data.seek(0, os.SEEK_SET)
+assert data.read() == b'Hello world!'
+del data
+assert do.released
+
+# Again, without the cookie.
+do = DataObject()
+data = core.Data(cbs=(do.read, do.write, do.seek, do.release))
+data.write('Hello world!')
+data.seek(0, os.SEEK_SET)
+assert data.read() == b'Hello world!'
+del data
+assert do.released