diff options
| author | Justus Winter <[email protected]> | 2016-07-28 10:40:54 +0000 | 
|---|---|---|
| committer | Justus Winter <[email protected]> | 2016-07-28 12:23:07 +0000 | 
| commit | de69fa496c09386d5e99747670d6887cf52dd09e (patch) | |
| tree | 4debb7586a9b18b62eba374a67020e1f1ae69c1e | |
| parent | python: Improve engine information handling. (diff) | |
| download | gpgme-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.i | 54 | ||||
| -rw-r--r-- | lang/python/helpers.c | 116 | ||||
| -rw-r--r-- | lang/python/private.h | 8 | ||||
| -rw-r--r-- | lang/python/pyme/core.py | 50 | ||||
| -rw-r--r-- | lang/python/pyme/util.py | 6 | ||||
| -rw-r--r-- | lang/python/tests/Makefile.am | 3 | ||||
| -rwxr-xr-x | lang/python/tests/t-protocol-assuan.py | 66 | 
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) | 
