diff --git a/lang/python/examples/inter-edit.py b/lang/python/examples/inter-edit.py index 459df114..39d6f176 100644 --- a/lang/python/examples/inter-edit.py +++ b/lang/python/examples/inter-edit.py @@ -23,13 +23,6 @@ del absolute_import, print_function, unicode_literals import sys import pyme -import pyme.constants.status - -# Get names for the status codes -status2str = {} -for name in dir(pyme.constants.status): - if not name.startswith('__') and name != "util": - status2str[getattr(pyme.constants.status, name)] = name if len(sys.argv) != 2: sys.exit("Usage: %s \n" % sys.argv[0]) @@ -46,11 +39,11 @@ with pyme.Context() as c: key = keys[0] print("Editing key {} ({}):".format(key.uids[0].uid, key.subkeys[0].fpr)) - def edit_fnc(status, args): + def edit_fnc(keyword, args): print("Status: {} ({}), args: {} > ".format( - status2str[status], status, args), end='', flush=True) + keyword, status, args), end='', flush=True) - if not 'GET' in status2str[status]: + if not 'GET' in keyword: # no prompt print() return None @@ -60,4 +53,4 @@ with pyme.Context() as c: except EOFError: return "quit" - c.op_edit(key, edit_fnc, None, sys.stdout) + c.interact(key, edit_fnc, sink=sys.stdout) diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i index 458ae7f1..84addae2 100644 --- a/lang/python/gpgme.i +++ b/lang/python/gpgme.i @@ -476,15 +476,15 @@ -// Include mapper for edit callbacks -%typemap(in) (gpgme_edit_cb_t fnc, void *fnc_value) { +/* Include mapper for interact callbacks. */ +%typemap(in) (gpgme_interact_cb_t fnc, void *fnc_value) { if (! PyTuple_Check($input)) - return PyErr_Format(PyExc_TypeError, "edit callback must be a tuple"); + return PyErr_Format(PyExc_TypeError, "interact callback must be a tuple"); if (PyTuple_Size($input) != 2 && PyTuple_Size($input) != 3) return PyErr_Format(PyExc_TypeError, - "edit callback must be a tuple of size 2 or 3"); + "interact callback must be a tuple of size 2 or 3"); - $1 = (gpgme_edit_cb_t) _pyme_edit_cb; + $1 = (gpgme_interact_cb_t) _pyme_interact_cb; $2 = $input; } diff --git a/lang/python/helpers.c b/lang/python/helpers.c index bc8aed40..bb2128c9 100644 --- a/lang/python/helpers.c +++ b/lang/python/helpers.c @@ -656,11 +656,16 @@ pyme_set_status_cb(PyObject *self, PyObject *cb) { Py_INCREF(Py_None); return Py_None; } + -/* Edit callbacks. */ -gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status, - const char *args, int fd) { + +/* Interact callbacks. */ +gpgme_error_t +_pyme_interact_cb(void *opaque, const char *keyword, + const char *args, int fd) +{ PyObject *func = NULL, *dataarg = NULL, *pyargs = NULL, *retval = NULL; + PyObject *py_keyword; PyObject *pyopaque = (PyObject *) opaque; gpgme_error_t err_status = 0; PyObject *self = NULL; @@ -678,7 +683,15 @@ gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status, pyargs = PyTuple_New(2); } - PyTuple_SetItem(pyargs, 0, PyLong_FromLong((long) status)); + if (keyword) + py_keyword = PyUnicode_FromString(keyword); + else + { + Py_INCREF(Py_None); + py_keyword = Py_None; + } + + PyTuple_SetItem(pyargs, 0, py_keyword); PyTuple_SetItem(pyargs, 1, PyUnicode_FromString(args)); if (dataarg) { Py_INCREF(dataarg); /* Because GetItem doesn't give a ref but SetItem taketh away */ @@ -726,7 +739,9 @@ gpgme_error_t _pyme_edit_cb(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 diff --git a/lang/python/private.h b/lang/python/private.h index cb4d2f80..3a903c18 100644 --- a/lang/python/private.h +++ b/lang/python/private.h @@ -34,9 +34,8 @@ PyObject *_pyme_obj2gpgme_data_t(PyObject *input, int argnum, PyObject *_pyme_wrap_result(PyObject *fragile, const char *classname); -gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status, - const char *args, int fd); - +gpgme_error_t _pyme_interact_cb(void *opaque, const char *keyword, + 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, diff --git a/lang/python/pyme/constants/__init__.py b/lang/python/pyme/constants/__init__.py index 96465de5..96d89e47 100644 --- a/lang/python/pyme/constants/__init__.py +++ b/lang/python/pyme/constants/__init__.py @@ -7,3 +7,108 @@ util.process_constants('GPGME_', globals()) __all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk', 'protocol', 'sig', 'sigsum', 'status', 'validity'] + +# GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact. We +# implement pyme.Context.op_edit using gpgme_op_interact, so the +# callbacks will be called with string keywords instead of numeric +# status messages. Code that is using these constants will continue +# to work. + +STATUS_ABORT = "ABORT" +STATUS_ALREADY_SIGNED = "ALREADY_SIGNED" +STATUS_ATTRIBUTE = "ATTRIBUTE" +STATUS_BACKUP_KEY_CREATED = "BACKUP_KEY_CREATED" +STATUS_BAD_PASSPHRASE = "BAD_PASSPHRASE" +STATUS_BADARMOR = "BADARMOR" +STATUS_BADMDC = "BADMDC" +STATUS_BADSIG = "BADSIG" +STATUS_BEGIN_DECRYPTION = "BEGIN_DECRYPTION" +STATUS_BEGIN_ENCRYPTION = "BEGIN_ENCRYPTION" +STATUS_BEGIN_SIGNING = "BEGIN_SIGNING" +STATUS_BEGIN_STREAM = "BEGIN_STREAM" +STATUS_CARDCTRL = "CARDCTRL" +STATUS_DECRYPTION_FAILED = "DECRYPTION_FAILED" +STATUS_DECRYPTION_INFO = "DECRYPTION_INFO" +STATUS_DECRYPTION_OKAY = "DECRYPTION_OKAY" +STATUS_DELETE_PROBLEM = "DELETE_PROBLEM" +STATUS_ENC_TO = "ENC_TO" +STATUS_END_DECRYPTION = "END_DECRYPTION" +STATUS_END_ENCRYPTION = "END_ENCRYPTION" +STATUS_END_STREAM = "END_STREAM" +STATUS_ENTER = "ENTER" +STATUS_ERRMDC = "ERRMDC" +STATUS_ERROR = "ERROR" +STATUS_ERRSIG = "ERRSIG" +STATUS_EXPKEYSIG = "EXPKEYSIG" +STATUS_EXPSIG = "EXPSIG" +STATUS_FAILURE = "FAILURE" +STATUS_FILE_DONE = "FILE_DONE" +STATUS_FILE_ERROR = "FILE_ERROR" +STATUS_FILE_START = "FILE_START" +STATUS_GET_BOOL = "GET_BOOL" +STATUS_GET_HIDDEN = "GET_HIDDEN" +STATUS_GET_LINE = "GET_LINE" +STATUS_GOOD_PASSPHRASE = "GOOD_PASSPHRASE" +STATUS_GOODMDC = "GOODMDC" +STATUS_GOODSIG = "GOODSIG" +STATUS_GOT_IT = "GOT_IT" +STATUS_IMPORT_OK = "IMPORT_OK" +STATUS_IMPORT_PROBLEM = "IMPORT_PROBLEM" +STATUS_IMPORT_RES = "IMPORT_RES" +STATUS_IMPORTED = "IMPORTED" +STATUS_INQUIRE_MAXLEN = "INQUIRE_MAXLEN" +STATUS_INV_RECP = "INV_RECP" +STATUS_INV_SGNR = "INV_SGNR" +STATUS_KEY_CONSIDERED = "KEY_CONSIDERED" +STATUS_KEY_CREATED = "KEY_CREATED" +STATUS_KEY_NOT_CREATED = "KEY_NOT_CREATED" +STATUS_KEYEXPIRED = "KEYEXPIRED" +STATUS_KEYREVOKED = "KEYREVOKED" +STATUS_LEAVE = "LEAVE" +STATUS_MISSING_PASSPHRASE = "MISSING_PASSPHRASE" +STATUS_MOUNTPOINT = "MOUNTPOINT" +STATUS_NEED_PASSPHRASE = "NEED_PASSPHRASE" +STATUS_NEED_PASSPHRASE_PIN = "NEED_PASSPHRASE_PIN" +STATUS_NEED_PASSPHRASE_SYM = "NEED_PASSPHRASE_SYM" +STATUS_NEWSIG = "NEWSIG" +STATUS_NO_PUBKEY = "NO_PUBKEY" +STATUS_NO_RECP = "NO_RECP" +STATUS_NO_SECKEY = "NO_SECKEY" +STATUS_NO_SGNR = "NO_SGNR" +STATUS_NODATA = "NODATA" +STATUS_NOTATION_DATA = "NOTATION_DATA" +STATUS_NOTATION_FLAGS = "NOTATION_FLAGS" +STATUS_NOTATION_NAME = "NOTATION_NAME" +STATUS_PINENTRY_LAUNCHED = "PINENTRY_LAUNCHED" +STATUS_PKA_TRUST_BAD = "PKA_TRUST_BAD" +STATUS_PKA_TRUST_GOOD = "PKA_TRUST_GOOD" +STATUS_PLAINTEXT = "PLAINTEXT" +STATUS_PLAINTEXT_LENGTH = "PLAINTEXT_LENGTH" +STATUS_POLICY_URL = "POLICY_URL" +STATUS_PROGRESS = "PROGRESS" +STATUS_REVKEYSIG = "REVKEYSIG" +STATUS_RSA_OR_IDEA = "RSA_OR_IDEA" +STATUS_SC_OP_FAILURE = "SC_OP_FAILURE" +STATUS_SC_OP_SUCCESS = "SC_OP_SUCCESS" +STATUS_SESSION_KEY = "SESSION_KEY" +STATUS_SHM_GET = "SHM_GET" +STATUS_SHM_GET_BOOL = "SHM_GET_BOOL" +STATUS_SHM_GET_HIDDEN = "SHM_GET_HIDDEN" +STATUS_SHM_INFO = "SHM_INFO" +STATUS_SIG_CREATED = "SIG_CREATED" +STATUS_SIG_ID = "SIG_ID" +STATUS_SIG_SUBPACKET = "SIG_SUBPACKET" +STATUS_SIGEXPIRED = "SIGEXPIRED" +STATUS_SUCCESS = "SUCCESS" +STATUS_TOFU_STATS = "TOFU_STATS" +STATUS_TOFU_STATS_LONG = "TOFU_STATS_LONG" +STATUS_TOFU_USER = "TOFU_USER" +STATUS_TRUNCATED = "TRUNCATED" +STATUS_TRUST_FULLY = "TRUST_FULLY" +STATUS_TRUST_MARGINAL = "TRUST_MARGINAL" +STATUS_TRUST_NEVER = "TRUST_NEVER" +STATUS_TRUST_ULTIMATE = "TRUST_ULTIMATE" +STATUS_TRUST_UNDEFINED = "TRUST_UNDEFINED" +STATUS_UNEXPECTED = "UNEXPECTED" +STATUS_USERID_HINT = "USERID_HINT" +STATUS_VALIDSIG = "VALIDSIG" diff --git a/lang/python/pyme/constants/status.py b/lang/python/pyme/constants/status.py index ee522591..a04d9aae 100644 --- a/lang/python/pyme/constants/status.py +++ b/lang/python/pyme/constants/status.py @@ -18,5 +18,107 @@ from __future__ import absolute_import, print_function, unicode_literals del absolute_import, print_function, unicode_literals -from pyme import util -util.process_constants('GPGME_STATUS_', globals()) +# GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact. We +# implement pyme.Context.op_edit using gpgme_op_interact, so the +# callbacks will be called with string keywords instead of numeric +# status messages. Code that is using these constants will continue +# to work. + +ABORT = "ABORT" +ALREADY_SIGNED = "ALREADY_SIGNED" +ATTRIBUTE = "ATTRIBUTE" +BACKUP_KEY_CREATED = "BACKUP_KEY_CREATED" +BAD_PASSPHRASE = "BAD_PASSPHRASE" +BADARMOR = "BADARMOR" +BADMDC = "BADMDC" +BADSIG = "BADSIG" +BEGIN_DECRYPTION = "BEGIN_DECRYPTION" +BEGIN_ENCRYPTION = "BEGIN_ENCRYPTION" +BEGIN_SIGNING = "BEGIN_SIGNING" +BEGIN_STREAM = "BEGIN_STREAM" +CARDCTRL = "CARDCTRL" +DECRYPTION_FAILED = "DECRYPTION_FAILED" +DECRYPTION_INFO = "DECRYPTION_INFO" +DECRYPTION_OKAY = "DECRYPTION_OKAY" +DELETE_PROBLEM = "DELETE_PROBLEM" +ENC_TO = "ENC_TO" +END_DECRYPTION = "END_DECRYPTION" +END_ENCRYPTION = "END_ENCRYPTION" +END_STREAM = "END_STREAM" +ENTER = "ENTER" +ERRMDC = "ERRMDC" +ERROR = "ERROR" +ERRSIG = "ERRSIG" +EXPKEYSIG = "EXPKEYSIG" +EXPSIG = "EXPSIG" +FAILURE = "FAILURE" +FILE_DONE = "FILE_DONE" +FILE_ERROR = "FILE_ERROR" +FILE_START = "FILE_START" +GET_BOOL = "GET_BOOL" +GET_HIDDEN = "GET_HIDDEN" +GET_LINE = "GET_LINE" +GOOD_PASSPHRASE = "GOOD_PASSPHRASE" +GOODMDC = "GOODMDC" +GOODSIG = "GOODSIG" +GOT_IT = "GOT_IT" +IMPORT_OK = "IMPORT_OK" +IMPORT_PROBLEM = "IMPORT_PROBLEM" +IMPORT_RES = "IMPORT_RES" +IMPORTED = "IMPORTED" +INQUIRE_MAXLEN = "INQUIRE_MAXLEN" +INV_RECP = "INV_RECP" +INV_SGNR = "INV_SGNR" +KEY_CONSIDERED = "KEY_CONSIDERED" +KEY_CREATED = "KEY_CREATED" +KEY_NOT_CREATED = "KEY_NOT_CREATED" +KEYEXPIRED = "KEYEXPIRED" +KEYREVOKED = "KEYREVOKED" +LEAVE = "LEAVE" +MISSING_PASSPHRASE = "MISSING_PASSPHRASE" +MOUNTPOINT = "MOUNTPOINT" +NEED_PASSPHRASE = "NEED_PASSPHRASE" +NEED_PASSPHRASE_PIN = "NEED_PASSPHRASE_PIN" +NEED_PASSPHRASE_SYM = "NEED_PASSPHRASE_SYM" +NEWSIG = "NEWSIG" +NO_PUBKEY = "NO_PUBKEY" +NO_RECP = "NO_RECP" +NO_SECKEY = "NO_SECKEY" +NO_SGNR = "NO_SGNR" +NODATA = "NODATA" +NOTATION_DATA = "NOTATION_DATA" +NOTATION_FLAGS = "NOTATION_FLAGS" +NOTATION_NAME = "NOTATION_NAME" +PINENTRY_LAUNCHED = "PINENTRY_LAUNCHED" +PKA_TRUST_BAD = "PKA_TRUST_BAD" +PKA_TRUST_GOOD = "PKA_TRUST_GOOD" +PLAINTEXT = "PLAINTEXT" +PLAINTEXT_LENGTH = "PLAINTEXT_LENGTH" +POLICY_URL = "POLICY_URL" +PROGRESS = "PROGRESS" +REVKEYSIG = "REVKEYSIG" +RSA_OR_IDEA = "RSA_OR_IDEA" +SC_OP_FAILURE = "SC_OP_FAILURE" +SC_OP_SUCCESS = "SC_OP_SUCCESS" +SESSION_KEY = "SESSION_KEY" +SHM_GET = "SHM_GET" +SHM_GET_BOOL = "SHM_GET_BOOL" +SHM_GET_HIDDEN = "SHM_GET_HIDDEN" +SHM_INFO = "SHM_INFO" +SIG_CREATED = "SIG_CREATED" +SIG_ID = "SIG_ID" +SIG_SUBPACKET = "SIG_SUBPACKET" +SIGEXPIRED = "SIGEXPIRED" +SUCCESS = "SUCCESS" +TOFU_STATS = "TOFU_STATS" +TOFU_STATS_LONG = "TOFU_STATS_LONG" +TOFU_USER = "TOFU_USER" +TRUNCATED = "TRUNCATED" +TRUST_FULLY = "TRUST_FULLY" +TRUST_MARGINAL = "TRUST_MARGINAL" +TRUST_NEVER = "TRUST_NEVER" +TRUST_ULTIMATE = "TRUST_ULTIMATE" +TRUST_UNDEFINED = "TRUST_UNDEFINED" +UNEXPECTED = "UNEXPECTED" +USERID_HINT = "USERID_HINT" +VALIDSIG = "VALIDSIG" diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py index 55e86872..88a086b1 100644 --- a/lang/python/pyme/core.py +++ b/lang/python/pyme/core.py @@ -29,6 +29,7 @@ del absolute_import, print_function, unicode_literals import re import os +import warnings import weakref from . import gpgme from .errors import errorcheck, GPGMEError @@ -536,6 +537,39 @@ class Context(GpgmeWrapper): return GPGMEError(status) if status != 0 else None + def interact(self, key, func, sink=None, flags=0, fnc_value=None): + """Interact with the engine + + This method can be used to edit keys and cards interactively. + KEY is the key to edit, FUNC is called repeatedly with two + unicode arguments, 'keyword' and 'args'. See the GPGME manual + for details. + + Keyword arguments: + sink -- if given, additional output is written here + flags -- use constants.INTERACT_CARD to edit a card + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + if key == None: + raise ValueError("First argument cannot be None") + + if sink == None: + sink = Data() + + if fnc_value: + opaquedata = (weakref.ref(self), func, fnc_value) + else: + opaquedata = (weakref.ref(self), func) + + result = gpgme.gpgme_op_interact(self.wrapped, key, flags, + opaquedata, sink) + if self._callback_excinfo: + gpgme.pyme_raise_callback_exception(self) + errorcheck(result) + @property def signers(self): """Keys used for signing""" @@ -793,18 +827,21 @@ class Context(GpgmeWrapper): errorcheck(status) def op_edit(self, key, func, fnc_value, out): - """Start key editing using supplied callback function""" - if key == None: - raise ValueError("op_edit: First argument cannot be None") - if fnc_value: - opaquedata = (weakref.ref(self), func, fnc_value) - else: - opaquedata = (weakref.ref(self), func) + """Start key editing using supplied callback function + + Note: This interface is deprecated and will be removed with + GPGME 1.8. Please use .interact instead. Furthermore, we + implement this using gpgme_op_interact, so callbacks will get + called with string keywords instead of numeric status + messages. Code that is using constants.STATUS_X or + constants.status.X will continue to work, whereas code using + magic numbers will break as a result. + + """ + warnings.warn("Call to deprecated method op_edit.", + category=DeprecationWarning) + return self.interact(key, func, sink=out, fnc_value=fnc_value) - result = gpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out) - if self._callback_excinfo: - gpgme.pyme_raise_callback_exception(self) - errorcheck(result) class Data(GpgmeWrapper): """Data buffer diff --git a/lang/python/tests/t-edit.py b/lang/python/tests/t-edit.py index 9ba187d5..18bcb94e 100755 --- a/lang/python/tests/t-edit.py +++ b/lang/python/tests/t-edit.py @@ -33,7 +33,7 @@ class KeyEditor(object): self.done = False self.verbose = int(os.environ.get('verbose', 0)) > 1 - def edit_fnc(self, status, args, out): + def edit_fnc(self, status, args, out=None): if args == "keyedit.prompt": result = self.steps[self.step] self.step += 1 @@ -57,8 +57,15 @@ c = core.Context() c.set_pinentry_mode(constants.PINENTRY_MODE_LOOPBACK) c.set_passphrase_cb(lambda *args: "abc") c.set_armor(True) -sink = core.Data() +# The deprecated interface. +editor = KeyEditor() +c.interact(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), + editor.edit_fnc) +assert editor.done + +# The deprecated interface. +sink = core.Data() editor = KeyEditor() c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), editor.edit_fnc, sink, sink)