diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i index 610b3d9d..492326b8 100644 --- a/lang/python/gpgme.i +++ b/lang/python/gpgme.i @@ -1,693 +1,698 @@ /* # Copyright (C) 2016 g10 Code GmbH # Copyright (C) 2004,2008 Igor Belyi # Copyright (C) 2002 John Goerzen # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ %module gpgme %include "cpointer.i" %include "cstring.i" /* Generate doc strings for all methods. This will generate docstrings of the form gpgme_op_encrypt(ctx, recp, flags, plain, cipher) -> gpgme_error_t which we transform into ctx.op_encrypt(recp, flags, plain, cipher) -> gpgme_error_t for automagically wrapped functions. */ %feature("autodoc", "0"); /* Allow use of Unicode objects, bytes, and None for strings. */ %typemap(in) const char *(PyObject *encodedInput = NULL) { if ($input == Py_None) $1 = NULL; else if (PyUnicode_Check($input)) { encodedInput = PyUnicode_AsUTF8String($input); if (encodedInput == NULL) return NULL; $1 = PyBytes_AsString(encodedInput); } else if (PyBytes_Check($input)) $1 = PyBytes_AsString($input); else { PyErr_Format(PyExc_TypeError, "arg %d: expected str, bytes, or None, got %s", $argnum, $input->ob_type->tp_name); return NULL; } } %typemap(freearg) const char * { Py_XDECREF(encodedInput$argnum); } /* Likewise for a list of strings. */ %typemap(in) const char *[] (void *vector = NULL, size_t size, PyObject **pyVector = NULL) { /* Check if is a list */ if (PyList_Check($input)) { size_t i, j; size = PyList_Size($input); $1 = (char **) (vector = malloc((size+1) * sizeof(char *))); pyVector = calloc(sizeof *pyVector, size); for (i = 0; i < size; i++) { PyObject *o = PyList_GetItem($input,i); if (PyUnicode_Check(o)) { pyVector[i] = PyUnicode_AsUTF8String(o); if (pyVector[i] == NULL) { free(vector); for (j = 0; j < i; j++) Py_XDECREF(pyVector[j]); return NULL; } $1[i] = PyBytes_AsString(pyVector[i]); } else if (PyString_Check(o)) $1[i] = PyString_AsString(o); else { PyErr_Format(PyExc_TypeError, "arg %d: list must contain only str or bytes, got %s " "at position %d", $argnum, o->ob_type->tp_name, i); free($1); return NULL; } } $1[i] = NULL; } else { PyErr_Format(PyExc_TypeError, "arg %d: expected a list of str or bytes, got %s", $argnum, $input->ob_type->tp_name); return NULL; } } %typemap(freearg) const char *[] { size_t i; free(vector$argnum); for (i = 0; i < size$argnum; i++) Py_XDECREF(pyVector$argnum[i]); } /* Release returned buffers as necessary. */ %typemap(newfree) char * "gpgme_free($1);"; %newobject gpgme_data_release_and_get_mem; %newobject gpgme_pubkey_algo_string; %newobject gpgme_addrspec_from_uid; %typemap(arginit) gpgme_key_t [] { $1 = NULL; } %typemap(in) gpgme_key_t [] { int i, numb = 0; if (!PySequence_Check($input)) { PyErr_Format(PyExc_ValueError, "arg %d: Expected a list of gpgme_key_t", $argnum); return NULL; } if((numb = PySequence_Length($input)) != 0) { $1 = (gpgme_key_t*)malloc((numb+1)*sizeof(gpgme_key_t)); for(i=0; iob_type->tp_name, i); free($1); return NULL; } Py_DECREF(pypointer); } $1[numb] = NULL; } } %typemap(freearg) gpgme_key_t [] { if ($1) free($1); } /* Special handling for references to our objects. */ %typemap(in) gpgme_data_t DATAIN (gpgme_data_t wrapper = NULL, PyObject *bytesio = NULL, Py_buffer view, int have_view = 0) { /* If we create a temporary wrapper object, we will store it in wrapperN, where N is $argnum. Here in this fragment, SWIG will automatically append $argnum. */ memset(&view, 0, sizeof view); if ($input == Py_None) $1 = NULL; else { PyObject *pypointer; pypointer = _gpg_obj2gpgme_data_t($input, $argnum, &wrapper, &bytesio, &view); if (pypointer == NULL) return NULL; have_view = !! view.obj; /* input = $input, 1 = $1, 1_descriptor = $1_descriptor */ /* Following code is from swig's python.swg. */ if ((SWIG_ConvertPtr(pypointer,(void **) &$1, $1_descriptor, SWIG_POINTER_EXCEPTION | $disown )) == -1) { Py_DECREF(pypointer); return NULL; } Py_DECREF(pypointer); } } #if HAVE_DATA_H /* If we are doing an in-tree build, we can use the internal representation of struct gpgme_data for an very efficient check if the buffer has been modified. */ %{ #include "data.h" /* For struct gpgme_data. */ %} #endif %typemap(freearg) gpgme_data_t DATAIN { /* See whether we need to update the Python buffer. */ if (resultobj && wrapper$argnum && view$argnum.buf) { int dirty; char *new_data = NULL; size_t new_size; #if HAVE_DATA_H new_data = wrapper$argnum->data.mem.buffer; new_size = wrapper$argnum->data.mem.length; dirty = new_data != NULL; #else new_data = gpgme_data_release_and_get_mem (wrapper$argnum, &new_size); wrapper$argnum = NULL; dirty = new_size != view$argnum.len || memcmp (new_data, view$argnum.buf, view$argnum.len); #endif if (dirty) { /* The buffer is dirty. */ if (view$argnum.readonly) { Py_XDECREF(resultobj); resultobj = NULL; PyErr_SetString(PyExc_ValueError, "cannot update read-only buffer"); } /* See if we need to truncate the buffer. */ if (resultobj && view$argnum.len != new_size) { if (bytesio$argnum == NULL) { Py_XDECREF(resultobj); resultobj = NULL; PyErr_SetString(PyExc_ValueError, "cannot resize buffer"); } else { PyObject *retval; PyBuffer_Release(&view$argnum); assert(view$argnum.obj == NULL); retval = PyObject_CallMethod(bytesio$argnum, "truncate", "l", (long) new_size); if (retval == NULL) { Py_XDECREF(resultobj); resultobj = NULL; } else { Py_DECREF(retval); retval = PyObject_CallMethod(bytesio$argnum, "getbuffer", NULL); if (retval == NULL || PyObject_GetBuffer(retval, &view$argnum, PyBUF_SIMPLE|PyBUF_WRITABLE) < 0) { Py_XDECREF(resultobj); resultobj = NULL; } Py_XDECREF(retval); if (resultobj && view$argnum.len != new_size) { Py_XDECREF(resultobj); resultobj = NULL; PyErr_Format(PyExc_ValueError, "Expected buffer of length %zu, got %zi", new_size, view$argnum.len); } } } } if (resultobj) memcpy(view$argnum.buf, new_data, new_size); } #if ! HAVE_DATA_H free (new_data); #endif } /* Free the temporary wrapper, if any. */ if (wrapper$argnum) gpgme_data_release(wrapper$argnum); Py_XDECREF (bytesio$argnum); if (have_view$argnum && view$argnum.buf) PyBuffer_Release(&view$argnum); } %apply gpgme_data_t DATAIN {gpgme_data_t plain, gpgme_data_t cipher, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_data_t keydata, gpgme_data_t pubkey, gpgme_data_t seckey, gpgme_data_t out, gpgme_data_t data}; /* SWIG has problems interpreting ssize_t, off_t or gpgme_error_t in gpgme.h. */ %typemap(out) ssize_t, gpgme_error_t, gpgme_err_code_t, gpgme_err_source_t, gpg_error_t { $result = PyLong_FromLong($1); } %typemap(in) ssize_t, gpgme_error_t, gpgme_err_code_t, gpgme_err_source_t, gpg_error_t { if (PyLong_Check($input)) $1 = PyLong_AsLong($input); #if PY_MAJOR_VERSION < 3 else if (PyInt_Check($input)) $1 = PyInt_AsLong($input); #endif else PyErr_SetString(PyExc_TypeError, "Numeric argument expected"); } %typemap(out) off_t { #if _FILE_OFFSET_BITS == 64 $result = PyLong_FromLongLong($1); #else $result = PyLong_FromLong($1); #endif } %typemap(in) off_t { if (PyLong_Check($input)) #if _FILE_OFFSET_BITS == 64 $1 = PyLong_AsLongLong($input); #else $1 = PyLong_AsLong($input); #endif #if PY_MAJOR_VERSION < 3 else if (PyInt_Check($input)) $1 = PyInt_AsLong($input); #endif else PyErr_SetString(PyExc_TypeError, "Numeric argument expected"); } /* Those are for gpgme_data_read() and gpgme_strerror_r(). */ %typemap(in) (void *buffer, size_t size), (char *buf, size_t buflen) { { long tmp$argnum; if (PyLong_Check($input)) tmp$argnum = PyLong_AsLong($input); #if PY_MAJOR_VERSION < 3 else if (PyInt_Check($input)) tmp$argnum = PyInt_AsLong($input); #endif else { PyErr_SetString(PyExc_TypeError, "Numeric argument expected"); return NULL; } if (tmp$argnum < 0) { PyErr_SetString(PyExc_ValueError, "Positive integer expected"); return NULL; } $2 = (size_t) tmp$argnum; $1 = ($1_ltype) malloc($2+1); } } %typemap(argout) (void *buffer, size_t size), (char *buf, size_t buflen) { Py_XDECREF($result); /* Blow away any previous result */ if (result < 0) { /* Check for I/O error */ free($1); return PyErr_SetFromErrno(PyExc_RuntimeError); } $result = PyBytes_FromStringAndSize($1,result); free($1); } /* For gpgme_data_write, but should be universal. */ %typemap(in) (const void *buffer, size_t size)(PyObject *encodedInput = NULL) { Py_ssize_t ssize; if ($input == Py_None) $1 = NULL, $2 = 0; else if (PyUnicode_Check($input)) { encodedInput = PyUnicode_AsUTF8String($input); if (encodedInput == NULL) return NULL; if (PyBytes_AsStringAndSize(encodedInput, (char **) &$1, &ssize) == -1) { Py_DECREF(encodedInput); return NULL; } } else if (PyBytes_Check($input)) PyBytes_AsStringAndSize($input, (char **) &$1, &ssize); else { PyErr_Format(PyExc_TypeError, "arg %d: expected str, bytes, or None, got %s", $argnum, $input->ob_type->tp_name); return NULL; } if (! $1) $2 = 0; else { assert (ssize >= 0); $2 = (size_t) ssize; } } %typemap(freearg) (const void *buffer, size_t size) { Py_XDECREF(encodedInput$argnum); } /* Make types containing 'next' field to be lists. */ %ignore next; %typemap(out) gpgme_sig_notation_t, gpgme_subkey_t, gpgme_key_sig_t, gpgme_user_id_t, gpgme_invalid_key_t, gpgme_recipient_t, gpgme_new_signature_t, gpgme_signature_t, gpgme_import_status_t, gpgme_conf_arg_t, gpgme_conf_opt_t, gpgme_conf_comp_t, gpgme_tofu_info_t { int i; int size = 0; $1_ltype curr; for (curr = $1; curr != NULL; curr = curr->next) { size++; } $result = PyList_New(size); for (i=0,curr=$1; inext) { PyObject *o = SWIG_NewPointerObj(SWIG_as_voidptr(curr), $1_descriptor, %newpointer_flags); PyList_SetItem($result, i, o); } } /* Wrap the fragile result objects into robust Python ones. */ %define wrapresult(cls, name) %typemap(out) cls { PyObject *fragile; fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor, %newpointer_flags); $result = _gpg_wrap_result(fragile, name); Py_DECREF(fragile); } %enddef wrapresult(gpgme_encrypt_result_t, "EncryptResult") wrapresult(gpgme_decrypt_result_t, "DecryptResult") wrapresult(gpgme_sign_result_t, "SignResult") wrapresult(gpgme_verify_result_t, "VerifyResult") wrapresult(gpgme_import_result_t, "ImportResult") wrapresult(gpgme_genkey_result_t, "GenkeyResult") wrapresult(gpgme_keylist_result_t, "KeylistResult") wrapresult(gpgme_vfs_mount_result_t, "VFSMountResult") %typemap(out) gpgme_engine_info_t { int i; int size = 0; $1_ltype curr; for (curr = $1; curr != NULL; curr = curr->next) { size++; } $result = PyList_New(size); if ($result == NULL) return NULL; /* raise */ for (i=0,curr=$1; inext) { PyObject *fragile, *o; fragile = SWIG_NewPointerObj(SWIG_as_voidptr(curr), $1_descriptor, %newpointer_flags); if (fragile == NULL) { Py_DECREF($result); return NULL; /* raise */ } o = _gpg_wrap_result(fragile, "EngineInfo"); Py_DECREF(fragile); if (o == NULL) { Py_DECREF($result); return NULL; /* raise */ } PyList_SetItem($result, i, o); } } /* Include mapper for interact callbacks. */ %typemap(in) (gpgme_interact_cb_t fnc, void *fnc_value) { if (! PyTuple_Check($input)) 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, "interact callback must be a tuple of size 2 or 3"); $1 = (gpgme_interact_cb_t) _gpg_interact_cb; $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 = _gpg_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 = _gpg_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 = _gpg_assuan_status_cb; $2 = $input; } } /* With SWIG, you can define default arguments for parameters. * While it's legal in C++ it is not in C, so we cannot change the * already existing gpgme.h. We need, however, to declare the function * *before* SWIG loads it from gpgme.h. Hence, we define it here. */ gpgme_error_t gpgme_op_keylist_start (gpgme_ctx_t ctx, const char *pattern="", int secret_only=0); +/* The whence argument is surprising in Python-land, + because BytesIO or StringIO objects do not require it. + It defaults to SEEK_SET. Let's do that for Data objects, too */ +off_t gpgme_data_seek (gpgme_data_t dh, off_t offset, int whence=SEEK_SET); + /* Include the unmodified 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 SWIG. */ %{ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include %} /* This is for notations, where we want to hide the length fields, and the unused bit field block. */ struct _gpgme_sig_notation { struct _gpgme_sig_notation *next; /* If NAME is a null pointer, then VALUE contains a policy URL rather than a notation. */ char *name; /* The value of the notation data. */ char *value; /* The accumulated flags. */ gpgme_sig_notation_flags_t flags; /* Notation data is human-readable. */ unsigned int human_readable : 1; /* Notation data is critical. */ unsigned int critical : 1; }; /* Now include our local modified version. Any structs defined above are ignored. */ #ifdef HAVE_CONFIG_H %include "config.h" #endif %include "gpgme.h" %include "errors.i" /* Generating and handling pointers-to-pointers. */ %pointer_functions(gpgme_ctx_t, gpgme_ctx_t_p); %pointer_functions(gpgme_data_t, gpgme_data_t_p); %pointer_functions(gpgme_key_t, gpgme_key_t_p); %pointer_functions(gpgme_error_t, gpgme_error_t_p); %pointer_functions(gpgme_trust_item_t, gpgme_trust_item_t_p); %pointer_functions(gpgme_engine_info_t, gpgme_engine_info_t_p); /* Helper functions. */ %{ #include %} FILE *fdopen(int fildes, const char *mode); /* We include both headers in the generated c code... */ %{ #include "helpers.h" #include "private.h" /* SWIG runtime support for helpers.c */ PyObject * _gpg_wrap_gpgme_data_t(gpgme_data_t data) { /* * If SWIG is invoked without -builtin, the macro SWIG_NewPointerObj * expects a variable named "self". * * XXX: It is not quite clear why passing NULL as self is okay, but * it works with -builtin, and it seems to work just fine without * it too. */ PyObject* self = NULL; (void) self; return SWIG_NewPointerObj(data, SWIGTYPE_p_gpgme_data, 0); } gpgme_ctx_t _gpg_unwrap_gpgme_ctx_t(PyObject *wrapped) { gpgme_ctx_t result; if (SWIG_ConvertPtr(wrapped, (void **) &result, SWIGTYPE_p_gpgme_context, SWIG_POINTER_EXCEPTION) == -1) return NULL; return result; } %} /* ... but only the public definitions here. They will be exposed to the Python world, so let's be careful. */ %include "helpers.h" %define genericrepr(cls) %pythoncode %{ def __repr__(self): names = [name for name in dir(self) if not name.startswith("_") and name != "this"] props = ", ".join(("{}={!r}".format(name, getattr(self, name)) for name in names) ) return "cls({})".format(props) %} %enddef %extend _gpgme_key { genericrepr(Key) }; %extend _gpgme_subkey { genericrepr(SubKey) }; %extend _gpgme_key_sig { genericrepr(KeySig) }; %extend _gpgme_user_id { genericrepr(UID) }; %extend _gpgme_tofu_info { genericrepr(TofuInfo) }; diff --git a/lang/python/tests/t-data.py b/lang/python/tests/t-data.py index 33013193..5cf074c5 100755 --- a/lang/python/tests/t-data.py +++ b/lang/python/tests/t-data.py @@ -1,131 +1,137 @@ #!/usr/bin/env python # 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 . from __future__ import absolute_import, print_function, unicode_literals del absolute_import, print_function, unicode_literals import io import os import tempfile import gpg import support _ = support # to appease pyflakes. data = gpg.Data('Hello world!') assert data.read() == b'Hello world!' assert data.read() == b'' data.seek(0, os.SEEK_SET) assert data.read() == b'Hello world!' assert data.read() == b'' data = gpg.Data(b'Hello world!') assert data.read() == b'Hello world!' data = gpg.Data(b'Hello world!', copy=False) assert data.read() == b'Hello world!' data = gpg.Data() data.write('Hello world!') data.seek(0, os.SEEK_SET) assert data.read() == b'Hello world!' data = gpg.Data() data.write(b'Hello world!') data.seek(0, os.SEEK_SET) assert data.read() == b'Hello world!' +data = gpg.Data() +data.write(b'Hello world!') +# We expect the second argument to default to SEEK_SET +data.seek(0) +assert data.read() == b'Hello world!' + binjunk = bytes(range(256)) data = gpg.Data() data.write(binjunk) data.seek(0, os.SEEK_SET) assert data.read() == binjunk data = gpg.Data() data.set_file_name("foobar") assert data.get_file_name() == "foobar" # Test reading from an existing file. with tempfile.NamedTemporaryFile() as tmp: tmp.write(binjunk) tmp.flush() tmp.seek(0) # Open using name. data = gpg.Data(file=tmp.name) assert data.read() == binjunk # Open using name, without copying. if False: # delayed reads are not yet supported data = gpg.Data(file=tmp.name, copy=False) assert data.read() == binjunk # Open using stream. tmp.seek(0) data = gpg.Data(file=tmp) assert data.read() == binjunk # Open using stream, offset, and length. data = gpg.Data(file=tmp, offset=0, length=42) assert data.read() == binjunk[:42] # Open using name, offset, and length. data = gpg.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 = gpg.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 = gpg.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