aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <[email protected]>2016-07-28 10:40:54 +0000
committerJustus Winter <[email protected]>2016-07-28 12:23:07 +0000
commitde69fa496c09386d5e99747670d6887cf52dd09e (patch)
tree4debb7586a9b18b62eba374a67020e1f1ae69c1e
parentpython: Improve engine information handling. (diff)
downloadgpgme-de69fa496c09386d5e99747670d6887cf52dd09e.tar.gz
gpgme-de69fa496c09386d5e99747670d6887cf52dd09e.zip
python: Support the Assuan engine.
* lang/python/gpgme.i: Add typemaps for the Assuan protocol callbacks. * lang/python/helpers.c (_pyme_assuan_{data,inquire,status}_cb): New functions. * lang/python/private.h (_pyme_assuan_{data,inquire,status}_cb): New prototypes. * lang/python/pyme/core.py (Context.assuan_transact): New method. * lang/python/pyme/util.py (percent_escape): New function. * lang/python/tests/Makefile.am (py_tests): Add new test. * lang/python/tests/t-protocol-assuan.py: New file. Signed-off-by: Justus Winter <[email protected]>
-rw-r--r--lang/python/gpgme.i54
-rw-r--r--lang/python/helpers.c116
-rw-r--r--lang/python/private.h8
-rw-r--r--lang/python/pyme/core.py50
-rw-r--r--lang/python/pyme/util.py6
-rw-r--r--lang/python/tests/Makefile.am3
-rwxr-xr-xlang/python/tests/t-protocol-assuan.py66
7 files changed, 302 insertions, 1 deletions
diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index a372edd4..24adf74d 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -443,6 +443,60 @@
$2 = $input;
}
+
+
+/* The assuan protocol callbacks. */
+%typemap(in) (gpgme_assuan_data_cb_t data_cb, void *data_cb_value) {
+ if ($input == Py_None)
+ $1 = $2 = NULL;
+ else
+ {
+ if (! PyTuple_Check($input))
+ return PyErr_Format(PyExc_TypeError, "callback must be a tuple");
+ if (PyTuple_Size($input) != 2)
+ return PyErr_Format(PyExc_TypeError,
+ "callback must be a tuple of size 2");
+ if (! PyCallable_Check(PyTuple_GetItem($input, 1)))
+ return PyErr_Format(PyExc_TypeError, "second item must be callable");
+ $1 = _pyme_assuan_data_cb;
+ $2 = $input;
+ }
+}
+
+%typemap(in) (gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value) {
+ if ($input == Py_None)
+ $1 = $2 = NULL;
+ else
+ {
+ if (! PyTuple_Check($input))
+ return PyErr_Format(PyExc_TypeError, "callback must be a tuple");
+ if (PyTuple_Size($input) != 2)
+ return PyErr_Format(PyExc_TypeError,
+ "callback must be a tuple of size 2");
+ if (! PyCallable_Check(PyTuple_GetItem($input, 1)))
+ return PyErr_Format(PyExc_TypeError, "second item must be callable");
+ $1 = _pyme_assuan_inquire_cb;
+ $2 = $input;
+ }
+}
+
+%typemap(in) (gpgme_assuan_status_cb_t stat_cb, void *stat_cb_value) {
+ if ($input == Py_None)
+ $1 = $2 = NULL;
+ else
+ {
+ if (! PyTuple_Check($input))
+ return PyErr_Format(PyExc_TypeError, "callback must be a tuple");
+ if (PyTuple_Size($input) != 2)
+ return PyErr_Format(PyExc_TypeError,
+ "callback must be a tuple of size 2");
+ if (! PyCallable_Check(PyTuple_GetItem($input, 1)))
+ return PyErr_Format(PyExc_TypeError, "second item must be callable");
+ $1 = _pyme_assuan_status_cb;
+ $2 = $input;
+ }
+}
+
/* Include the unmodified <gpgme.h> for cc, and the cleaned-up local
version for SWIG. We do, however, want to hide certain fields on
some structs, which we provide prior to including the version for
diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 2b381729..90173e4e 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -937,3 +937,119 @@ pygpgme_data_new_from_cbs(PyObject *self,
Py_INCREF(Py_None);
return Py_None;
}
+
+
+
+/* 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;
+}
diff --git a/lang/python/private.h b/lang/python/private.h
index cb21f060..88b96538 100644
--- a/lang/python/private.h
+++ b/lang/python/private.h
@@ -35,4 +35,12 @@ PyObject *pygpgme_wrap_fragile_result(PyObject *fragile, const char *classname);
gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
const char *args, int fd);
+gpgme_error_t _pyme_assuan_data_cb (void *hook,
+ const void *data, size_t datalen);
+gpgme_error_t _pyme_assuan_inquire_cb (void *hook,
+ const char *name, const char *args,
+ gpgme_data_t *r_data);
+gpgme_error_t _pyme_assuan_status_cb (void *hook,
+ const char *status, const char *args);
+
#endif /* _PYME_PRIVATE_H_ */
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 216e26fd..b25808dc 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -31,6 +31,7 @@ from . import pygpgme
from .errors import errorcheck, GPGMEError
from . import constants
from . import errors
+from . import util
class GpgmeWrapper(object):
"""Base wrapper class
@@ -467,6 +468,55 @@ class Context(GpgmeWrapper):
plainbytes = data.read()
return plainbytes, result
+ def assuan_transact(self, command,
+ data_cb=None, inquire_cb=None, status_cb=None):
+ """Issue a raw assuan command
+
+ This function can be used to issue a raw assuan command to the
+ engine.
+
+ If command is a string or bytes, it will be used as-is. If it
+ is an iterable of strings, it will be properly escaped and
+ joined into an well-formed assuan command.
+
+ Keyword arguments:
+ data_cb -- a callback receiving data lines
+ inquire_cb -- a callback providing more information
+ status_cb -- a callback receiving status lines
+
+ Returns:
+ result -- the result of command as GPGMEError
+
+ Raises:
+ GPGMEError -- as signaled by the underlying library
+
+ """
+
+ if isinstance(command, (str, bytes)):
+ cmd = command
+ else:
+ cmd = " ".join(util.percent_escape(f) for f in command)
+
+ errptr = pygpgme.new_gpgme_error_t_p()
+
+ err = pygpgme.gpgme_op_assuan_transact_ext(
+ self.wrapped,
+ cmd,
+ (weakref.ref(self), data_cb) if data_cb else None,
+ (weakref.ref(self), inquire_cb) if inquire_cb else None,
+ (weakref.ref(self), status_cb) if status_cb else None,
+ errptr)
+
+ if self._callback_excinfo:
+ pygpgme.pygpgme_raise_callback_exception(self)
+
+ errorcheck(err)
+
+ status = pygpgme.gpgme_error_t_p_value(errptr)
+ pygpgme.delete_gpgme_error_t_p(errptr)
+
+ return GPGMEError(status) if status != 0 else None
+
@property
def signers(self):
"""Keys used for signing"""
diff --git a/lang/python/pyme/util.py b/lang/python/pyme/util.py
index bbd28fe7..7eb6353f 100644
--- a/lang/python/pyme/util.py
+++ b/lang/python/pyme/util.py
@@ -31,3 +31,9 @@ def process_constants(prefix, scope):
if identifier.startswith(prefix)}
scope.update(constants)
return list(constants.keys())
+
+def percent_escape(s):
+ return ''.join(
+ '%{0:2x}'.format(ord(c))
+ if c == '+' or c == '"' or c == '%' or ord(c) <= 0x20 else c
+ for c in s)
diff --git a/lang/python/tests/Makefile.am b/lang/python/tests/Makefile.am
index b2e725fa..bc571fe4 100644
--- a/lang/python/tests/Makefile.am
+++ b/lang/python/tests/Makefile.am
@@ -49,7 +49,8 @@ py_tests = t-wrapper.py \
t-wait.py \
t-encrypt-large.py \
t-file-name.py \
- t-idiomatic.py
+ t-idiomatic.py \
+ t-protocol-assuan.py
TESTS = initial.py $(py_tests) final.py
EXTRA_DIST = support.py $(TESTS) encrypt-only.asc sign-only.asc
diff --git a/lang/python/tests/t-protocol-assuan.py b/lang/python/tests/t-protocol-assuan.py
new file mode 100755
index 00000000..30907a1b
--- /dev/null
+++ b/lang/python/tests/t-protocol-assuan.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2016 g10 Code GmbH
+#
+# This file is part of GPGME.
+#
+# GPGME is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GPGME is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import pyme
+
+with pyme.Context(protocol=pyme.constants.PROTOCOL_ASSUAN) as c:
+ # Do nothing.
+ c.assuan_transact('nop')
+ c.assuan_transact('NOP')
+ c.assuan_transact(['NOP'])
+
+ err = c.assuan_transact('idontexist')
+ assert err.getsource() == pyme.errors.SOURCE_GPGAGENT
+ assert err.getcode() == pyme.errors.ASS_UNKNOWN_CMD
+
+ # Invoke the pinentry to get a confirmation.
+ c.assuan_transact(['GET_CONFIRMATION', 'Hello there'])
+
+ data = []
+ def data_cb(line):
+ data.append(line)
+
+ err = c.assuan_transact(['GETINFO', 'version'], data_cb=data_cb)
+ assert not err
+ assert len(data) == 1
+
+ data = []
+ err = c.assuan_transact(['GETINFO', 's2k_count'], data_cb=data_cb)
+ if not err:
+ assert len(data) == 1
+ assert int(data[0]) > 0
+
+ # XXX HELP sends status lines if we could use ASSUAN_CONVEY_COMMENTS.
+
+ status = []
+ def status_cb(line, args):
+ status.append((line, args))
+
+ alphas_grip = '76F7E2B35832976B50A27A282D9B87E44577EB66'
+ err = c.assuan_transact(['KEYINFO', alphas_grip], status_cb=status_cb)
+ if not err:
+ assert len(status) == 1
+ line, args = status[0]
+ assert line.startswith('KEYINFO')
+ assert args.startswith(alphas_grip)
+
+ # XXX: test these callbacks, e.g. using PRESET_PASSPHRASE
+ # XXX: once issue2428 is resolved
+ def inq_cb(name, args):
+ print("inq_cb", name, args)