Imported Upstream version 1.12.0 upstream/1.12.0
authorJinWang An <jinwang.an@samsung.com>
Wed, 1 Dec 2021 07:54:37 +0000 (16:54 +0900)
committerJinWang An <jinwang.an@samsung.com>
Wed, 1 Dec 2021 07:54:37 +0000 (16:54 +0900)
273 files changed:
AUTHORS
ChangeLog
Makefile.am
Makefile.in
NEWS
VERSION
conf/config.h.in [moved from config.h.in with 99% similarity]
conf/whatisthis [new file with mode: 0644]
configure
configure.ac
doc/Makefile.am
doc/Makefile.in
doc/defsincdate
doc/examples/gpgme-chrome.json [new file with mode: 0644]
doc/examples/gpgme-mozilla.json [new file with mode: 0644]
doc/gpgme.info
doc/gpgme.info-1
doc/gpgme.info-2
doc/gpgme.texi
gpgme.spec
gpgme.spec.in
lang/Makefile.am
lang/Makefile.in
lang/README
lang/cl/Makefile.in
lang/cl/gpgme.asd
lang/cpp/Makefile.in
lang/cpp/src/Makefile.in
lang/cpp/src/context.cpp
lang/cpp/src/context.h
lang/cpp/src/data.cpp
lang/cpp/src/data.h
lang/cpp/src/decryptionresult.cpp
lang/cpp/src/decryptionresult.h
lang/cpp/src/gpggencardkeyinteractor.cpp
lang/cpp/src/key.cpp
lang/cpp/src/verificationresult.cpp
lang/js/.eslintrc.json [new file with mode: 0644]
lang/js/BrowserTestExtension/Makefile.am [new file with mode: 0644]
lang/js/BrowserTestExtension/Makefile.in [new file with mode: 0644]
lang/js/BrowserTestExtension/browsertest.html [new file with mode: 0644]
lang/js/BrowserTestExtension/index.html [new file with mode: 0644]
lang/js/BrowserTestExtension/longTests.html [new file with mode: 0644]
lang/js/BrowserTestExtension/manifest.json [new file with mode: 0644]
lang/js/BrowserTestExtension/popup.html [new file with mode: 0644]
lang/js/BrowserTestExtension/popup.js [new file with mode: 0644]
lang/js/BrowserTestExtension/runbrowsertest.js [new file with mode: 0644]
lang/js/BrowserTestExtension/rununittests.js [new file with mode: 0644]
lang/js/BrowserTestExtension/setup_testing.js [new file with mode: 0644]
lang/js/BrowserTestExtension/testicon.png [new file with mode: 0644]
lang/js/BrowserTestExtension/testkey.pub [new file with mode: 0644]
lang/js/BrowserTestExtension/testkey.sec [new file with mode: 0644]
lang/js/BrowserTestExtension/testkey2.pub [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/KeyImportExport.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/KeyInfos.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/decryptTest.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/encryptDecryptTest.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/encryptTest.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/inputvalues.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/longRunningTests.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/signTest.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/startup.js [new file with mode: 0644]
lang/js/BrowserTestExtension/tests/verifyTest.js [new file with mode: 0644]
lang/js/BrowserTestExtension/unittests.html [new file with mode: 0644]
lang/js/DemoExtension/Makefile.am [new file with mode: 0644]
lang/js/DemoExtension/Makefile.in [new file with mode: 0644]
lang/js/DemoExtension/entry.js [new file with mode: 0644]
lang/js/DemoExtension/maindemo.js [new file with mode: 0644]
lang/js/DemoExtension/mainui.html [new file with mode: 0644]
lang/js/DemoExtension/manifest.json [new file with mode: 0644]
lang/js/DemoExtension/popup.html [new file with mode: 0644]
lang/js/DemoExtension/testicon.png [new file with mode: 0644]
lang/js/DemoExtension/ui.css [new file with mode: 0644]
lang/js/Makefile.am [new file with mode: 0644]
lang/js/Makefile.in [new file with mode: 0644]
lang/js/README [new file with mode: 0644]
lang/js/build_extensions.sh [new file with mode: 0755]
lang/js/jsdoc.conf [new file with mode: 0644]
lang/js/jsdoc_index.md [new file with mode: 0644]
lang/js/package.json [new file with mode: 0644]
lang/js/src/Connection.js [new file with mode: 0644]
lang/js/src/Errors.js [new file with mode: 0644]
lang/js/src/Helpers.js [new file with mode: 0644]
lang/js/src/Key.js [new file with mode: 0644]
lang/js/src/Keyring.js [new file with mode: 0644]
lang/js/src/Makefile.am [new file with mode: 0644]
lang/js/src/Makefile.in [new file with mode: 0644]
lang/js/src/Message.js [new file with mode: 0644]
lang/js/src/Signature.js [new file with mode: 0644]
lang/js/src/gpgmejs.js [new file with mode: 0644]
lang/js/src/index.js [new file with mode: 0644]
lang/js/src/permittedOperations.js [new file with mode: 0644]
lang/js/unittest_inputvalues.js [new file with mode: 0644]
lang/js/unittests.js [new file with mode: 0644]
lang/js/webpack.conf.js [new file with mode: 0644]
lang/js/webpack.conf_unittests.js [new file with mode: 0644]
lang/python/MANIFEST.in
lang/python/Makefile.am
lang/python/Makefile.in
lang/python/README
lang/python/doc/README [new file with mode: 0644]
lang/python/doc/meta/TODO.org [new file with mode: 0644]
lang/python/doc/meta/old-commits.log [new file with mode: 0644]
lang/python/doc/rst/gpgme-python-howto.rst [new file with mode: 0644]
lang/python/doc/rst/index.rst [new file with mode: 0644]
lang/python/doc/rst/short-history.rst [new file with mode: 0644]
lang/python/doc/src/gpgme-python-howto.org [new file with mode: 0644]
lang/python/doc/src/index.org [new file with mode: 0644]
lang/python/doc/src/short-history.org [new file with mode: 0644]
lang/python/doc/texinfo/gpgme-python-howto.texi [new file with mode: 0644]
lang/python/doc/texinfo/index.texi [new file with mode: 0644]
lang/python/doc/texinfo/short-history.texi [new file with mode: 0644]
lang/python/doc/texinfo/texinfo.tex [new file with mode: 0644]
lang/python/examples/assuan.py
lang/python/examples/decryption-filter.py
lang/python/examples/delkey.py
lang/python/examples/exportimport.py
lang/python/examples/genkey.py
lang/python/examples/howto/add-userid.py
lang/python/examples/howto/advanced/cython/keycount.pyx [new file with mode: 0755]
lang/python/examples/howto/advanced/cython/setup.py [new file with mode: 0644]
lang/python/examples/howto/clear-sign-file.py
lang/python/examples/howto/create-key.py
lang/python/examples/howto/decrypt-file.py
lang/python/examples/howto/detach-sign-file.py
lang/python/examples/howto/encrypt-file.py
lang/python/examples/howto/encrypt-sign-file.py
lang/python/examples/howto/encrypt-to-group-gullible.py [new file with mode: 0755]
lang/python/examples/howto/encrypt-to-group-trustno1.py [new file with mode: 0755]
lang/python/examples/howto/encrypt-to-group.py [new file with mode: 0755]
lang/python/examples/howto/export-key.py [new file with mode: 0755]
lang/python/examples/howto/export-minimised-key.py [new file with mode: 0755]
lang/python/examples/howto/export-secret-key.py [new file with mode: 0755]
lang/python/examples/howto/export-secret-keys.py [new file with mode: 0755]
lang/python/examples/howto/groups.py
lang/python/examples/howto/import-key.py [new file with mode: 0755]
lang/python/examples/howto/import-keys-hkp.py [new file with mode: 0755]
lang/python/examples/howto/import-keys.py [new file with mode: 0755]
lang/python/examples/howto/keycount.py
lang/python/examples/howto/local-sign-group.py [new file with mode: 0755]
lang/python/examples/howto/mutt-groups.py [new file with mode: 0755]
lang/python/examples/howto/pmkey-import-alt.py [new file with mode: 0755]
lang/python/examples/howto/pmkey-import-hkp-alt.py [new file with mode: 0755]
lang/python/examples/howto/pmkey-import-hkp.py [new file with mode: 0755]
lang/python/examples/howto/pmkey-import.py [new file with mode: 0755]
lang/python/examples/howto/revoke-userid.py
lang/python/examples/howto/send-key-to-keyserver.py [new file with mode: 0755]
lang/python/examples/howto/sign-file.py
lang/python/examples/howto/sign-key.py
lang/python/examples/howto/symcrypt-file.py [new file with mode: 0755]
lang/python/examples/howto/temp-homedir-config.py
lang/python/examples/howto/verify-signatures.py
lang/python/examples/howto/verify-signed-file.py
lang/python/examples/inter-edit.py
lang/python/examples/low_level-encrypt_to_all.py
lang/python/examples/sign.py
lang/python/examples/signverify.py
lang/python/examples/simple.py
lang/python/examples/testCMSgetkey.py
lang/python/examples/verifydetails.py
lang/python/gpgme.i
lang/python/setup.py.in
lang/python/src/__init__.py
lang/python/src/callbacks.py
lang/python/src/constants/__init__.py
lang/python/src/constants/create.py
lang/python/src/constants/data/__init__.py
lang/python/src/constants/data/encoding.py
lang/python/src/constants/event.py
lang/python/src/constants/import.py
lang/python/src/constants/keylist/__init__.py
lang/python/src/constants/keylist/mode.py
lang/python/src/constants/keysign.py
lang/python/src/constants/md.py
lang/python/src/constants/pk.py
lang/python/src/constants/protocol.py
lang/python/src/constants/sig/__init__.py
lang/python/src/constants/sig/mode.py
lang/python/src/constants/sig/notation.py
lang/python/src/constants/sigsum.py
lang/python/src/constants/tofu/__init__.py
lang/python/src/constants/tofu/policy.py
lang/python/src/constants/validity.py
lang/python/src/core.py
lang/python/src/errors.py
lang/python/src/results.py
lang/python/src/util.py
lang/python/tests/Makefile.am
lang/python/tests/Makefile.in
lang/python/tests/final.py
lang/python/tests/initial.py
lang/python/tests/run-tests.py
lang/python/tests/support.py
lang/python/tests/t-callbacks.py
lang/python/tests/t-data.py
lang/python/tests/t-decrypt-verify.py
lang/python/tests/t-decrypt.py
lang/python/tests/t-edit.py
lang/python/tests/t-encrypt-large.py
lang/python/tests/t-encrypt-sign.py
lang/python/tests/t-encrypt-sym.py
lang/python/tests/t-encrypt.py
lang/python/tests/t-export.py
lang/python/tests/t-file-name.py
lang/python/tests/t-idiomatic.py
lang/python/tests/t-import.py
lang/python/tests/t-keylist-from-data.py
lang/python/tests/t-keylist.py
lang/python/tests/t-protocol-assuan.py
lang/python/tests/t-quick-key-creation.py
lang/python/tests/t-quick-key-manipulation.py
lang/python/tests/t-quick-key-signing.py
lang/python/tests/t-quick-subkey-creation.py
lang/python/tests/t-sig-notation.py
lang/python/tests/t-sign.py
lang/python/tests/t-signers.py
lang/python/tests/t-trustlist.py
lang/python/tests/t-verify.py
lang/python/tests/t-wait.py
lang/python/tests/t-wrapper.py
lang/python/version.py.in
lang/qt/Makefile.am
lang/qt/Makefile.in
lang/qt/doc/Makefile.in
lang/qt/src/Makefile.in
lang/qt/src/qgpgmenewcryptoconfig.cpp
lang/qt/src/threadedjobmixin.cpp
lang/qt/tests/Makefile.am
lang/qt/tests/Makefile.in
lang/qt/tests/t-various.cpp
m4/python.m4
src/Makefile.am
src/Makefile.in
src/cJSON.c
src/context.h
src/data-estream.c [new file with mode: 0644]
src/data-mem.c
src/data.c
src/data.h
src/decrypt-verify.c
src/decrypt.c
src/edit.c
src/encrypt-sign.c
src/encrypt.c
src/engine-backend.h
src/engine-gpg.c
src/engine-gpgsm.c
src/engine.c
src/engine.h
src/export.c
src/genkey.c
src/getauditlog.c
src/gpgme-json.c
src/gpgme.c
src/gpgme.def
src/gpgme.h.in
src/keysign.c
src/libgpgme.vers
src/op-support.c
src/ops.h
src/passwd.c
src/sign.c
src/w32-util.c
tests/Makefile.am
tests/Makefile.in
tests/gpg/Makefile.am
tests/gpg/Makefile.in
tests/gpgsm/Makefile.am
tests/gpgsm/Makefile.in
tests/opassuan/Makefile.am
tests/opassuan/Makefile.in
tests/run-decrypt.c
tests/run-keylist.c

diff --git a/AUTHORS b/AUTHORS
index c989eff..64a675e 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -24,8 +24,9 @@ List of Copyright holders
   Copyright (C) 2004-2008 Igor Belyi
   Copyright (C) 2002 John Goerzen
   Copyright (C) 2014, 2015 Martin Albrecht
-  Copyright (C) 2015 Ben McGinnes
-  Copyright (C) 2015-2016 Bundesamt für Sicherheit in der Informationstechnik
+  Copyright (C) 2015, 2018 Ben McGinnes
+  Copyright (C) 2015, 2016, 2018
+                Bundesamt für Sicherheit in der Informationstechnik
   Copyright (C) 2016 Intevation GmbH
 
 
@@ -59,6 +60,12 @@ Colin Watson <cjwatson@debian.org>
 Tobias Mueller <muelli@cryptobitch.de>
 2016-11-23:1479937342.11180.3.camel@cryptobitch.de:
 
+Ben McGinnes <ben@adversary.org>
+2017-12-16:20171216002102.l6aejk5xdp6xhtfi@adversary.org:
+
+Jacob Adams <tookmund@gmail.com>
+2018-06-03:ad5141df-b6cc-6c2a-59df-b2f18f7160fd@gmail.com:
+
 
  Copyright 2001, 2002, 2012, 2013 g10 Code GmbH
 
index 3a4fcca..de9dbca 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
+2018-10-08  Werner Koch  <wk@gnupg.org>
+
+       Release 1.12.0.
+       + commit 1aff2512d846ea640d400caa31c20c40230b3b04
+       * configure.ac: Bump core LT version to C32/A21/R0.  Bump C++ LT
+       version to C14/A8/R0.
+
+       * lang/qt/tests/Makefile.am (CLEANFILES): Add reader status files.
+       * Makefile.am (EXTRA_DIST): Add conf/whatisthis.
+
+2018-10-05  Werner Koch  <wk@gnupg.org>
+
+       core: Apply GPGME_EXPORT_MODE_NOUID also to keyserver exports.
+       + commit 913601f4879f011878b53b885f35524df98b5570
+       * src/engine-gpg.c (export_common): Add keyserver-options to the
+       send-keys commands.
+
+       build: Move config.h to conf/config.h.
+       + commit ecfa48fffa93f3d1b66354d2d46872ff614c4a68
+       * conf/: New dir.
+       * configure.ac (AC_CONFIG_HEADER): Move header to conf dir.
+       * doc/Makefile.am (mkdefsinc): Adjust rule.
+       * lang/python/Makefile.am (copystamp): Ditto.
+
+2018-10-04  Ben McGinnes  <ben@adversary.org>
+
+       docs: whitespace bug.
+       + commit 033da18b88af69b7846de62af4aeea359d27ec20
+       * Same as before, but exported.
+
+       docs: org to texinfo whitspace bug.
+       + commit a8d4aa6f763599bbd8ea36ef98b7b4931bf326dc
+       * replaced "." in headings with "·" to prevent whitspace being
+         appended by texinfo and breaking git.
+
+       docs: python.
+       + commit 0b5930b276186afd1ca1dd91d82db7e60dd20606
+       * Fixed an error in the new index page and then exported to the other
+         two "source" formats.
+
+       docs: python.
+       + commit 48258879720a4f423d0efa955da942d69f3c49d4
+       * lang/python/doc/meta/TODO.org: Added the two major documentation
+         build system tasks to the TODO list.
+       * Added an index page in preparation for sorting out the second of
+         those TODO lists (Docutils is a lot easier to handle than Texinfo).
+       * Meanwhile, have confirmed that it all builds just fine under
+         GNU/Linux, OS X and FreeBSD while retaining the documentation, so
+         that's a nice improvement from 1.11.1.
+
+       python: makefiles.
+       + commit f3fc73738264d21baf0e9b49fdd5cb67faa11d31
+       * doc/Makefile.am: Removed a bit I forgot about.
+       * Renamed lang/python/docs to lang/python/doc bvecause apparently
+         automake cares about that too.
+       * Decided to be extra explicit in the manifest because if I don't then
+         all sorts of things get deleted ... like lang/python
+       * Tested on an external linux system just in case my osx workstation
+         introduces too much weirdness.
+
+       python: make file.
+       + commit 2cca422ca4b2df831a824393077d0b52d79532aa
+       * lang/python/Makefile.am: Attempting to remove the docs/meta
+         directory did a lot worse than I thought it was doing, so better to
+         just be sure the documentation is available than destroy the entire
+         bindings directory.
+
+       python: make file.
+       + commit 582f14d97a900106dce0c8d8666a443c6ec25050
+       * lang/python/Makefile.am: Now that gpg2 has been renamed back to gpg
+         and gpg1 is semi-deprecated, we should check what the actual gpg
+         binary is with gpgconf and use that rather than make assumptions per
+         system.
+       * Also, it means less worry if gpg3 is ever a thing.  (Trust me, I
+         remember the Python 1 to 2 transition as well as the current 2 to 3
+         transition).  ;)
+
+2018-10-03  Ben McGinnes  <ben@adversary.org>
+
+       docs: more whitespace checks.
+       + commit a174b269432fd37e8546c43e0127580e826432da
+       * lang/python/docs/texinfo/short-history.texi:
+
+       dpcs: python howto.
+       + commit 962dfca9b86b53bb00f5b89e453b9eed95454934
+       * More updates to the docs themselves and the versions to be available
+         with the next release.
+       * .texi and .rst copies of the HOWTO and the short history of (this
+         part) of the project.
+
+       docs: python.
+       + commit 2151b9828af3760867e856ab939b835a6e2f42e7
+       * More restructuring.  Also the painstaking task of deciphering some
+         of the more eclectic aspects of Texinfo and make.
+
+       docs: python bindings.
+       + commit a982f9131520bce0b25f84cba19040bbe5f275bb
+       * Restructured the docs directory to account for the GNU preferred
+         source doc format (.texi) and the Python preferred source doc
+         format (.rst) and the real source doc format (.org).
+       * Both the perceived source formats will need to be generated from the
+         .org files and included at this stage.  Unfortunately there is not
+         yet a native org-to-rst transformation method in the org-mode
+         software in Emacs nor is there a a direct means of going from reST
+         to Org-mode from Docutils.  There's only third party packages like
+         Pandoc and, while very good, there is no guarantee of consistency;
+         so we can't entirely automate this bit (yet).
+
+       docs: pre-python preparation.
+       + commit b2802053192ba0000866b145b715a557d34ed0eb
+       * doc/Makefile.am: removed the python howto from this file, restoring
+         it to just the main project and the newer .js files.
+       * deleted: doc/gpgme-python-howto.texi
+       * renamed the Short_History.org file to short-history.org to keep the
+         naming conventions similar.
+       * All the Python files can (and should) live together.
+
+2018-10-02  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: CPython order.
+       + commit 23894ac9a96081428a590791010ca8beebaf6f9f
+       * Changed the order of python versions the configure/make process
+         checks for, placing Python 3.7 ahead of 3.6.
+       * Updated the HOWTO documentation to reflect this change.
+
+2018-10-02  Werner Koch  <wk@gnupg.org>
+
+       core: add experimental GPGME_EXPORT_MODE_NOUID.
+       + commit 7b861945fdc71fd3c82c91f824172dadd2220fa0
+       * src/gpgme.h.in (GPGME_EXPORT_MODE_NOUID): New.
+       * src/export.c (export_start): Adjust option check.
+       * src/engine-gpg.c (export_common): Implement option.
+
+2018-09-30  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings.
+       + commit 76bdfabb057f4ffc56beee15b48d8fa836ba7cce
+       * Tightened up the docs a little bit, updated the "what's new"
+         section, dropped the "-draft" version in preparation for GPGME
+         1.12.0's release.
+       * Exported another .texi version (and updated the draft copies to this
+         commit (which ought to be 1.11.1-beta313).
+
+       python bindings: fixing decrypt-verify.
+       + commit 837a4760533e80f075cd727f18354904b8a54132
+       * lang/python/src/core.py: First restoring the exception to the being
+         just that.
+       * The means to manipulate the error output is temporarily in commented
+         out code, but ought to be added to a proper test later.
+       * In the mean time the original test, with a very slight change, works
+         again.
+
+       python bindings: ctx.decrypt.
+       + commit 11403a46358f9b6e98776974f3c70f211d9adf85
+       * lang/python/src/core.py: Fixed methods of detecting whether verify
+         is a boolean variable or a list.
+       * Added methods of catching the missing keys exceptions.
+       * Still retained PEP8 compliance (which might have been where one or
+         two problems crept in).
+       * Though this is essentially the correct behaviour, it still does not
+         quite fit the otiginal test; so that will also require some adjustment.
+
+2018-09-27  Ben McGinnes  <ben@adversary.org>
+
+       example: local signatures.
+       + commit ce045a1ef9c63042fcffb5487b19646d67addba0
+       * lang/python/examples/howto/local-sign-group.py: added the bit where
+         specifying the signing key is actually used for signing rather than
+         just pruning the list of keys to certify.
+
+       example: python bindings.
+       + commit a047e0f68ef471097e514665249063f267257b19
+       * lang/python/examples/howto/local-sign-group.py: locally sign every
+         key in a group line except one's own keys.  Intended to address the
+         sort of thing one might see on lists like PGPNET or other closed
+         groups amongst activists, journalists, etc. where everyone encrypts
+         to all recipients, but may not sign everyone's keys publicly..
+
+       docs: python bindings installation.
+       + commit 1d40d360bccb05b4c66313a60a902d04a0d3c9ff
+       * lang/python/docs/gpgme-python-howto.org: added a section on
+         installing the bindings to a python virtualenv.
+
+       tests: python bindings.
+       + commit b9aea3b9c2c2762a9ae8d677196f82acc6a1c028
+       * Fixed the final assertion to look for what will actually be reported
+         in that case instead of something else (i.e. it looks for an
+         IMPORT_ERROR status code).
+
+       examples: python bindings.
+       + commit 7c9f49a4991c1ccd0cb582875207b373d4fccf50
+       * Fixed homedir specifying logic in several cases.
+
+2018-09-25  Ben McGinnes  <ben@adversary.org>
+
+       docs: a typographical two-step.
+       + commit b625258d54782040195cc5ad3b255685af6f6c4e
+       * Sometimes you really do need or want punctuation in a heading, but
+         ideally without something else generating whitespace and other
+         annoyances to go with it.
+       * Trying a real decimal point instead.
+
+       docs and examples: python bindings.
+       + commit 62e4e2cb5edb09183b9f9d448f992fe65cb6db85
+       * Woumd up the "what's new" section.
+       * Added an example for sending a key to the keyservers via hkp4py.
+       * Updated the export key code to use a more complete check for the
+         $GNUPGHOME location.
+       * Expanded on the installation and reinstallation troubleshooting
+         section.
+
+2018-09-24  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings howto.
+       + commit e9da4d97107b40a48583775df34783f07be5dfdb
+       * Added a What's New section to summarise changes since the last
+         release.  There have been quite a few and some attention does need
+         to be drawn to some of them.
+       * Confirming certain issues with some platform builds, especially
+         BSD/OSX vs. Linux issues which will need to update the installation
+         troubleshooting guides.
+
+2018-09-23  Ben McGinnes  <ben@adversary.org>
+
+       docs and examples: python bindings howto.
+       + commit b12b2cc99621fe32a2d698ce7f091f3225f35bd0
+       * Added more comprehensive examples using hkp4py and added a couple
+         more example scripts for protonmail.
+
+       examples: python bindings and hkp4py updates.
+       + commit ced4bdbbb239c1fe209665b4f5a7aeb9651889ed
+       * Mostly tightening up the details on the hkp4py example script.
+       * Also fixed a typo in the LGPL boiler plate text included in all the
+         other example scripts for the HOWTO.
+
+       python bindings: importing from keyservers with hkp4py.
+       + commit 6ed9a77c92c32f77092b36c149185d4359cd6e55
+       * added a new example script to search the keyservers and import the
+         results, this time using Marcel Fest's hkp4py module.
+       * Updated the key importing section to match this addition.
+       * Tested with the current version of hkp4py from github.
+
+2018-09-22  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings howto.
+       + commit 3622576105ae1924d9b40ce0d09bf9a7accc0ed1
+       * Confirmed that updates to the tests have significant'y improved that
+         output.
+       * Updated some of the additional notes for the section on hkp4py.
+       ** This is in anticipation adding at least import examples using that
+          module as well.  It may also include adding examples of exporting a
+          key and uploading it to the keyservers.
+
+2018-09-20  Werner Koch  <wk@gnupg.org>
+
+       python: Fix a couple of syntax errors.
+       + commit 6878126b6f53cdf7daeeaf68116dda008564c2fa
+       * lang/python/tests/t-keylist-from-data.py: Add missing line
+       continuation.
+       * lang/python/tests/t-keylist.py: Ditto.
+       * lang/python/tests/t-quick-key-creation.py: Ditto.
+       * lang/python/tests/t-quick-subkey-creation.py: Ditto.
+
+       python: Silence a few warnings.
+       + commit dcdabf5f2ef84e1f304fcc0590ec4bb160354af5
+       * src/gpgme.h.in: Obsolete "class" also for Python.
+       * lang/python/gpgme.i: Silenece a swig warning.  Silence a gcc
+       warning.
+
+       python: Fix regression in the test suite.
+       + commit 9f19b3aaecd23a12b2e6692faeefa936e490d343
+       * lang/python/tests/Makefile.am (GNUPGHOME): Remove stray backslash.
+
+       tests: Don't try using keys from a scmartcard.
+       + commit a824f4498ea9bae5dca515c56e2455ec0ce98b50
+       * tests/gpg/Makefile.am: Disable scdaemon
+       * lang/python/tests/Makefile.am: Ditto.
+
+2018-09-20  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings and its special request.
+       + commit 5cb67257f2b3b5fcfd3444e366f26a0f2ae09504
+       * Added some material on using the new-ish hkp4py module with GPGME.
+       * Example code will be added later once a couple of little issues are
+         addressed.
+
+2018-09-19  Werner Koch  <wk@gnupg.org>
+
+       json: Remove subkey-algo from createkey command.
+       + commit c569adb5e3e3082bd68cdc34a2d349b7c28d3768
+       * src/gpgme-json.c (op_createkey): Remove subkey-algo param.
+       (GPG_AGENT_ALLOWS_KEYGEN_TRHOUGH_BROWSER): Fix typo.
+       * lang/js/src/Keyring.js: Remove subkey-algo support.
+       * lang/js/src/permittedOperations.js: Ditto.
+
+2018-09-18  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings.
+       + commit 362caaf02f3a25b7e626572aa30b87771c2c8f4d
+       * Updated the Cython example code slightly, along with the
+         corresponding explanation.
+
+2018-09-17  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings howto.
+       + commit f4d83800d89173e618cceca44b584778217fadb3
+       * Tightened up the Cython demonstration.
+
+2018-09-16  Ben McGinnes  <ben@adversary.org>
+
+       docs: cython sanitized.
+       + commit 44c846345e91413b9d6a1f7b26f59db5b1c32433
+       * Sanitized the shell command examples of extraneous whitespace.
+       * Removed keycount.c as sanitising it is pointless and it will be
+         generated by Cython when the example is followed.
+       * Regenerated the .texi version.
+
+       docs: python bindings howto.
+       + commit 61c08f7435570783f5c267e42d288d31bf77e560
+       * Added new advanced section with an example of using the Python
+         bindings with CPython code compiled back to C code using Cython.
+       * Though it may seem a bit counter-intuitive to use the bindings just
+         to go back to C via a different route, this is not actually stupid.
+       * Added examples/howto/advanced/cython/ directory.
+       * Added keycount.pyx, setup.py and the keycount.c file which the first
+         two generated with Cython.  Not including the .so and .o files from
+         the build.
+       * Exported the .texi version of the howto for the main docs.
+
+       docs: even more edits.
+       + commit fbec29fdac7a4f162e73f24dac2a6b205239ef03
+       * doc/gpgme-python-howto.texi: hunting down and killing commas,
+         Oxford or not it can't stay in a heading.
+
+       docs: more edits.
+       + commit 2a1b0b88d96b722817f3ac9edf002a54c65084b8
+       * lang/python/docs/gpgme-python-howto.org: more tweaks and edits,
+         along with another build of output formats.
+       * doc/gpgme-python-howto.texi: updated texinfo version for parent docs.
+
+       docs: whitespace culled.
+       + commit 91b26b0638d9f92f0e90aab1c3750c14d72db301
+       * lang/python/docs/gpgme-python-howto.org: Identified and fixed the
+         headings which kept generating lines with trailing whitespace when
+         exporting to Texinfo format and adjusted them to prevent that.
+
+       docs: renaming and drafts.
+       + commit 766ec0e4a36c120ed4ef5463b5fdf8e7ed3b4563
+       * lang/python/docs/gpgme-python-howto.org: Renamed file to better fit
+         the rest of the project's docs.
+       * Added a section on the very unofficial drafts I periodically post
+         links to since they're often the easiest way to get a web version in
+         front of someone in a hurry.
+
+2018-09-15  Ben McGinnes  <ben@adversary.org>
+
+       docs: python howto texinfo update.
+       + commit 76f77022848894ee9d8490255cefdd0100248b45
+       * doc/gpgme-python-howto.texi:
+
+       docs: Python howto update.
+       + commit 7e9df9b9e33131f5d7c58ad58249f9ae766f1341
+       * lang/python/docs/GPGMEpythonHOWTOen.org: Added corresponding GPGME
+         version number to table at the start and cut the shortcut from the
+         groups.py example.
+       * doc/gpgme-python-howto.texi: New export of Texinfo file for docs
+         build.
+
+       docs: generated whitespace.
+       + commit 93a2ea0207fac0a18a48b671df11dc8575579c39
+       * doc/gpgme-python-howto.texi: culled whitespace included with the
+         org-mode export.
+
+       Docs: including howto with standard docs.
+       + commit 2e5b0603709855eb1d8a1a3fd51d3245f4301ada
+       * gpgme.spec.in: Added gpgme-python-howto.info build on the grounds
+         that it now deals with certain possible installation issues and end
+         users encountering them would want the docs to hand.
+       * doc/gpgme.texi: updated the copyright year to this year.
+
+       Docs: Python bindings HOWTO.
+       + commit b19faa26e01df4d78286e013313e5ab25f517d49
+       * Added doc/gpgme-python-howto.texi: generated from
+         GPGMEpythonHOWTOen.org and then slightly modified so the generated
+         Info file doesn't use camelCase.
+       * doc/Makefile.am: Updated makefile to include the Python HOWTO with
+         gpgme_TEXINFOS and to export the generated files to the webserver
+         along with the main GPGME one.
+
+       Python bindings: docs.
+       + commit d04fb0bf1271c91b88686c947a5b14ffc9b505ef
+       * lang/python/docs/GPGMEpythonHOWTOen.org: Fixed a few errors in the
+         newer sections.
+       * Updated code in the examples using secret key exporting and group
+         lines to reflect the Python 2.7 compatibility fixes added.
+
+       Python bindings: examples.
+       + commit 4e8a92ed14ea3da3d92f07d5f62fd325a2adebde
+       * lang/python/examples/howto/export-secret-keys.py and groups.py:
+         Updated the backwards compatibility adjustments to account for
+         unicode differences between python 2 and 3.
+
+       Python examples: backwards compatibility.
+       + commit 864ef9b40f5f9d0c66a458b6033277938d7d1d50
+       * lang/python/examples/howto/groups.py: subprocess update
+       * lang/python/examples/howto/export-secret-keys.py: subprocess update
+
+         Both of these try the nice and easy method of getting the subprocess
+         output available in Python 3, but will fall back to the older Popen
+         method if it doesn't work.  Essentially this is to be a little nicer
+         to Python 2.7.15 (even though the examples are filled with warnings
+         that py2 support is not guaranteed with the examples).
+
+2018-09-10  Thomas Oberndörfer  <thomas@mailvelope.com>
+
+       js: Fix errorDetails of GPGME_Signature.
+       + commit dd7d37ca21684d4d77db4f513c6212776fc6ea82
+       * lang/js/src/Signature.js (GPGME_Signature.errorDetails):
+       Access properties from the summary object.
+
+2018-09-08  Ben McGinnes  <ben@adversary.org>
+
+       estreams revised.
+       + commit 2375959180aa8eb0d23cc3f8240c3f5b5262b819
+       * Egon Spengler was right, crossing the streams is bad.
+       * Restored both src/gpgme.def and src/libgpgme.vers to use the
+         estreams symbols without the leading underscore.
+       * The new_from_estream() function added to lang/python/src/core.py and
+         set to alias the new_from_stream() function remains.
+       * Opted for the solution favouring Linux onthree main grounds:
+         1. Andre reported major problems with Windows as well, so the number
+            of potentially affected systems would vastly increase.
+         2. All the BSDs and OS X have spent far more time and development
+            work in order to accommodate the eccentricities of both Microsoft
+            and the GNU Project (ref. GCC), so they're more likely to be able
+            to cope with doing so again than the other way around.
+         3. If I really have to I can write a custom installer for OS X to
+            try this and, if it fails, to then patch the two symbol entries and
+            recompile from scratch.  That said, I may not have to since it
+            actually behaved during the most recent tests for this
+            commit; into ten separate CPython installations and all five
+            supported versions (standard source installs and OS X Framework
+            installs for each version).
+
+2018-09-02  Ben McGinnes  <ben@adversary.org>
+
+       estreams symbols for python bindings.
+       + commit 879cc1f84fbf09cb4eeb78bce16f52a1a36032ba
+       * It turns out that even though some platforms detect differing
+         symbols for estreams, the two types do not appear to be in
+         conflict.  At least they don't from the BSD/OS X side of things.
+         As a consequence both versions are now included.
+
+2018-08-31  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings.
+       + commit 55991aa91667b9184cc9fc86a3ac21b9640511ef
+       * minor typographic update.
+
+2018-08-30  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: estreams fix.
+       + commit 1d00fb987b903e245d484bddfe3c0a0aba670ac1
+       * lang/python/src/core.py: Adjusted new_from_estream function to alias
+         new_from_stream instead of fd.
+       * fixed the _gpgme import errors introduced in commit
+         08cd34afb762975b0273575035dacf69449ef241 by changing the exported
+         functions/types to match the inner module where all the work is
+         done, rather than the outer one(s).
+
+2018-08-29  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: core.
+       + commit 18ea83867168e8db0a2f2c8057d087363083486c
+       * lang/python/src/core.py: expanded gpgme_error check lists.
+
+       python bindings: core.
+       + commit dcedddecb6f8f11682aed3b09b88ee1511010faf
+       * lang/python/src/core.py: added gpgme_data_set_flag to the errorcheck
+         funtion.
+
+       python bindings: core.
+       + commit 6078b5303362b2e5ce56660967fac313ca3d5ae9
+       * lang/python/src/core.py: added new function new_from_estream to wrap
+         new_from_fd just like new_from_stream does and for the same reason.
+
+2018-08-29  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Fix detached verify.
+       + commit 1420c3bd681648b032d756536311621a3627bedb
+       * src/gpgme-json.c (op_verify): Only create output and
+       use it for clearsigned and opaque signed.
+
+2018-08-29  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings - protonmail examples.
+       + commit f7e5ae7f1618bd355b885c5c32dd028afad35453
+       * lang/python/docs/GPGMEpythonHOWTOen.org: Updated links to the
+         ProtonMail keyserver import scripts and added a warning regarding
+         being unable to update third party keys.
+       * lang/python/examples/howto/pmkey-import-alt.py: added usage.
+       * lang/python/examples/howto/pmkey-import.py: added usage.
+
+2018-08-28  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings HOWTO.
+       + commit 02d0b97bfedc732351cc4e89c92fcd9d31209535
+       * Finished CFFI vs SWIG bit in known issues.
+       * tidied up some of the structure.
+       * Fixed some minor errors and links.
+
+       docs: python bindings.
+       + commit a8a983c5bc0f0deeeebda455ad73309fff48b61f
+       * Added section on why no CFFI.
+
+       docs: python howto.
+       + commit c2831e2377843c8625df158ef32e01f8c19494eb
+       * Added another key import example using ProtonMail's new keyserver.
+
+2018-08-28  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Delete primary key if subkey gen fails.
+       + commit 3bdf8be6d2c57319399fe14e27e52b323a17750a
+       * src/gpgme-json.c (op_delete): Delete primary key on
+       subkey gen error.
+
+       json: Allow NULL request in encode and chunk.
+       + commit 7d3c13df263ed88c17005920e75e0486abeae5b9
+       * src/gpgme-json.c (encode_and_chunk): Don't error on NULL
+       request.
+
+2018-08-27  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings.
+       + commit 1c261b9fa3462360b6c3f43b243091cd50534903
+       * Added details on installation troubleshooting.
+
+2018-08-27  Werner Koch  <wk@gnupg.org>
+
+       json: Do not put FILE_NAME into the verify result.
+       + commit 53c5b9a265d33f2cc54f489375a929602338aee8
+       * src/gpgme-json.c (verify_result_to_json): Remove "file_name".
+
+2018-08-27  Jasper Spaans  <jasper@startmail.com>
+
+       core: Export gpgme_data_new_from_estream function.
+       + commit 08cd34afb762975b0273575035dacf69449ef241
+
+
+2018-08-23  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add sender and file name to encrypt.
+       + commit a5f8dac77d50480a208c99398df323c58ce6dc58
+       * src/gpgme-json.c (hlp_encrypt, op_encrypt): Support sender
+       and file_name.
+
+2018-08-21  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add proper decrypt_result_t handling.
+       + commit 263dadb04aed4f973248b32c52af6ca59bcb7c1f
+       * src/gpgme-json.c (recipient_to_json, decrypt_result_to_json):
+       New.
+       (op_decrypt, hlp_decrypt): Update.
+
+       js: Update extra_dist files.
+       + commit 738a8e6f950af08305c082d59a91d3d5d45800fa
+       * lang/js/BrowserTestExtension/Makefile.am,
+       lang/js/Makefile.am (EXTRA_DIST): Update.
+
+       Add example manifests for gpgme-json.
+       + commit 9608996d88549b60da490e5eeb41db023f97a038
+       * doc/examples/gpgme-chrome.json, doc/examples/gpgme-mozilla.json: New.
+       * doc/Makefile.am (EXTRA_DIST): Include them.
+
+       js: Improve README.
+       + commit 605eb8a8bfcb12141d7cc5626e75af812cda6c75
+       * lang/js/README: Clarify structure at the beginning.
+
+       Remove js as language from configure.ac.
+       + commit fe3de5b86b4a25f5b23cf1af2fd1809ef6c087a0
+       * configure.ac: Remove js language.
+
+2018-08-20  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add subkey_algo and defaults to createkey.
+       + commit 8103eeba809b6e7156d861783309574b93909169
+       * src/gpgme-json.c (op_createkey, hlp_createkey): Add subkey_algo
+       handling.
+       (hlp_createkey): Fix documentation of expiry.
+
+2018-08-19  Ben McGinnes  <ben@adversary.org>
+
+       Python bindings examples.
+       + commit 75bc5e6356eca1b7fb76653e7c82c2477f8859b0
+       * import-key.py: fixed a minor typo.
+       * pmkey-import.py: locates and imports keys from the ProtonMail keyserver.
+       * pmkey-import-alt.py: the same as the previous except with setting an
+         alternative $GNUPGHOME directory.
+
+2018-08-18  Ben McGinnes  <ben@adversary.org>
+
+       Python bindings setup file.
+       + commit 03b899dbe14a3c32fd018c20c7d5156366fdcc3d
+       * Moved the build import back up where it belongs.
+       * Included comments indicating how to build and install for multiple
+         Python versions beyond the first 2 on the same system.
+
+       Python bindings tests: Near PEP8 compliance.
+       + commit 5facba45c83f7daaacc49e66306e13a35aeb74be
+       * PEP8 compliance for the vast majoeity of the tests.
+
+       Python bindings examples: PEP8 conpliance.
+       + commit b5fbe90676f46b9615919133753a65b20318fe63
+       * Appears to be complete compliance.
+
+       Python bindings setup: Near PEP8 compliance.
+       + commit fc55caccfc87253e1703df66b21917cc399fba62
+       * lang/python/version.py.in: Fixed most things, but there's still an
+         issue near the build portion with the existing Python bugs referenced.
+       * lang/python/setup.py.in: Now PEP8 compliant.
+
+       Python bindings constants: PEP8 compliance (almost)
+       + commit 8a6a73b9c4f92b159450edb5e4a780fc7389ca82
+       * PEP8 compliance for all constants except the globals in
+         src/constants/__init__.py depending on whether the import sequence
+         affects the globals themselves.
+
+       Python bindings src: PEP8 compliance.
+       + commit 7962cde13c5cbdc643bbde795e2c29e638dfc186
+       * import namespace clearance for src/*.py.
+       * Fixed a handful of is/is not None checks as well.
+
+2018-08-13  Ben McGinnes  <ben@adversary.org>
+
+       Symmetric example.
+       + commit 279cac0ffbb3d865d997dc7fc9c1b53bbb55d3a2
+       * lang/python/examples/howto/symcrypt-file.py: *sigh*; passphrase was
+         right the first time, just the error check that wasn't.
+       * I really should stop second guessing myself one of these days ...
+
+       Symmetric encryption example.
+       + commit a256d84882616341b3f357340533893a1db8b5c1
+       * lang/python/examples/howto/symcrypt-file.py: Fixed the error code
+         and the passphrase key word arg.
+
+       Symmetric encryption example.
+       + commit ed5ef8293cdbfcc3b91268eeae1eb6b4f8d271bb
+       * lang/python/examples/howto/symcrypt-file.py: A variation on standard
+         key based encryption.
+
+2018-08-10  Ben McGinnes  <ben@adversary.org>
+
+       PEP8 compliance and other code fixes.
+       + commit 94bf13e78e65e1d1bc2e5d6b2311f9c9657bfe5f
+       * Ran all the .py files in src/ and below through Yapf.
+       * Included some manual edits of core.py, this time successfully making
+         two notorious sections a bit more pythonic than scheming.
+       * Left the module imports as is.
+       * This will be committed if it passes the most essential test:
+         compiling, installing and running it.
+
+2018-08-09  Ben McGinnes  <ben@adversary.org>
+
+       Link fixes.
+       + commit b6d2a66b41fee5b7fb484566aefcdbcc4cdad094
+       * lang/python/README: Fixed links in both versions of the README.
+
+2018-08-08  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Wipe memory in cJSON_Delete.
+       + commit 4dd1d0abd34a382d1cd67cabb737950a39cb3fdc
+       * src/cJSON.c (cJSON_Delete): Wipe memory on deletion.
+
+       json: Only use calloc instead of malloc.
+       + commit fdc07b3ddc2f68e6fcb33703ea41126d0a841290
+       * src/cJSON.c, src/gpgme-json.c (CALLOC_ONLY): New define
+       to change xmalloc / xtrymalloc to use calloc.
+
+       json: Add checks when skipping byte.
+       + commit 974a95db04f9cdea02867f0246445b4679517ba0
+       * src/cJSON.c (parse_string, cJSON_Minify): Check for
+       terminating NULL byte when skipping the byte after a an escaped
+       quote.
+
+       json: Don't error out if chunksize is omitted.
+       + commit 6e48bb0f1cbf662026bf0f70549b52bafe00c017
+       * src/gpgme-json.c (encode_and_chunk): Don't error out
+       if no chunksize is provided.
+
+       cpp: Fix use after free in gencardkeyinteractor.
+       + commit 3b782443600e8091c5f19580cb218100bcbb4ad7
+       * lang/cpp/src/gpggencardkeyinteractor.cpp
+       (GpgGenCardKeyInteractor::Private::keysize): Change to string.
+
+2018-08-06  Andre Heinecke  <aheinecke@intevation.de>
+
+       Make GNUPGHOME for tests overridable.
+       + commit d09d19fa9fe1e81dd819b32208b4bd09f83e3918
+       * lang/python/tests/Makefile.am,
+       lang/qt/tests/Makefile.am,
+       tests/Makefile.am,
+       tests/gpg/Makefile.am,
+       tests/gpgsm/Makefile.am,
+       tests/opassuan/Makefile.am (GNUPGHOME): Make variable explict.
+
+2018-07-24  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Add safety checks for key update.
+       + commit a6e5c8bf18696007c48c6f362aa355020fe82f21
+       * lang/cpp/src/key.cpp (Key::update): Check that the key is
+       not NULL.
+       * lang/cpp/src/verificationresult.cpp (GpgME::Signature::key):
+       Check for fingerprint.
+
+2018-07-22  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings howto.
+       + commit 4d1642b11ea64b8009a8720d9b59e89636d691d2
+       * Fixed and tested the changes necessary for org-mode to correctly
+         parse pythonic (Python 3) indentation.
+       * Updated the source blocks to recommended upper case for BEGIN_SRC
+         and END_SRC.
+       * Tested and confirmed XHTML output matches correct examples.
+       * Tested against pseudo-control output via exporting from org-mode to
+         org-mode and then exporting that to XHTML.  Remaining differences
+         appear to be discarding the custom tags used to provide X[HT]ML id
+         elements to each section which does not appear to offer any benefit.
+       * Exporting directly to XHTML or other HTML output should no longer
+         cause problems, but if there are any then the first step should be
+         exporting from org-to-org and then exporting that to XHTML.
+
+       doc: python bindings howto.
+       + commit d7c5366d58d035d7b7119f955824e842d0b6bbe0
+       * Another retrofitting of the HOWTO Python example code, this time
+         following adjustments to python-mode configuration and having
+         trawled through the org-mode mailing lists for clues.
+
+       doc: python bindings howto.
+       + commit b47e1bb98a8ff93cae560449bfa9103c47f4d4f8
+       * Added org-mode byline.
+
+2018-07-19  Werner Koch  <wk@gnupg.org>
+
+       core: Clear all flags for a new data property.
+       + commit 5ef492c5635ae1677eed6f439a75a86a99dbbe18
+       * src/data.c (PROPERTY_TABLE_ALLOCATION_CHUNK): New.
+       (insert_into_property_table): Use it here.  Clear all flags.
+
+       core: Blank out the plaintext after decryption failure.
+       + commit 085cdeddef637cc057362fcbde13b0261b8699ec
+       * src/data.h (data_prop_t): New enum.
+       (struct gpgme_data): Add field propidx.
+       * src/data.c (property_t): New.
+       (property_table, property_table_size, property_table_lock): New.
+       (insert_into_property_table): New.
+       (remove_from_property_table): New.
+       (_gpgme_data_get_dserial): New.
+       (_gpgme_data_set_prop): New.
+       (_gpgme_data_get_prop): New.
+       (_gpgme_data_new): Connect new object to property_table.
+       (_gpgme_data_release): Remove from property_table.
+       (gpgme_data_read): With DATA_PROP_BLANKOUT set don't fill the buffer.
+       * src/data-mem.c (gpgme_data_release_and_get_mem): Likewise.
+       * src/decrypt.c (struct op_data): Add field plaintext_dserial.
+       (_gpgme_op_decrypt_init_result): Add arg plaintext and init new field.
+       (_gpgme_decrypt_status_handler): Set DATA_PROP_BLANKOUT on decryption
+       failure.
+       (_gpgme_decrypt_start): Pass PLAIN to the init function.
+       * src/decrypt-verify.c (decrypt_verify_start): Ditto.
+       * configure.ac: Check for stdint.h and bail out if uint64_t is not
+       available.
+
+2018-07-19  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Print origin and last update for key/uid.
+       + commit 8168dfbeb12042a7f7b1bad918ef60df92bfd1a7
+       * lang/cpp/src/key.cpp: Print origin and last update in
+       iostream operators.
+
+2018-07-19  Werner Koch  <wk@gnupg.org>
+
+       json: Don't use strdup but the xtrystrdup wrapper.
+       + commit af2c74d6c06a9fb08f7de15d41162d09f871a62e
+       * src/gpgme-json.c (create_keylist_patterns): Use CNT as first arg for
+       xcalloc.
+       (process_request): s/strdup/xtrystrdup/.
+
+       core: New interface gpgme_data_new_from_estream.
+       + commit f42cd70f18d53df47cc2d027bade736377d39b71
+       * src/gpgme.h.in (gpgme_data_new_from_estream): New.
+       * src/data-estream.c: New.
+       * src/data.h (gpgme_data): New union member e_stream.
+
+2018-07-18  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Fix memleak in native msging repl.
+       + commit 98a75a16ccdfe51799a27894d2eb26dba04b34f2
+       * src/gpgme-json.c (native_messaging_repl): Free request and
+       response after each loop.
+
+       json: Ensure that native msging request is string.
+       + commit 6d7b4382c3e12ba1dbbd0762dfa850c76750d838
+       * src/gpgme-json.c (native_messaging_repl): Ensure that the
+       request is NULL terminated.
+
+       json: Fix crash by ensuring response is never NULL.
+       + commit 82e4b900a96c837392259469a9a5821a95e7a707
+       * src/gpgme-json.c (encode_and_chunk): Try to always
+       return at least an error.
+       (process_request): Double check that it does not return NULL.
+
+       json: Fix memory errors in create_keylist_patterns.
+       + commit b78140daf7720132711314a4e5ed878b49da99f4
+       * src/gpgme-json.c (create_keylist_patterns): Reserve two
+       pointers more then linefeeds.
+       (create_keylist_patterns): Fix loop to count linebreaks.
+       (create_keylist_patterns): Use calloc for good measure.
+
+       qt: Handle encoding for diagnostics.
+       + commit 16462c54b3503e77bc48c2486234531d7bc31b6d
+       * lang/qt/src/threadedjobmixin.cpp (fromEncoding)
+       (stringFromGpgOutput): New helpers.
+       (markupDiagnostics): Use it.
+
+2018-07-16  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Fix crash on invalid json.
+       + commit 1686e07e77a1eee3b2708d0231a5a549956021f7
+       * src/gpgme-json.c (process_request): Init res. Check for
+       json object before encode and chunk.
+
+       json: Fix uninitialized key unref in op_delete.
+       + commit cc21101a7494ea0a17c3012fcb86e77b00b494fa
+       * src/gpgme-json.c (op_delete): Init key.
+
+2018-07-16  Werner Koch  <wk@gnupg.org>
+
+       json: Minor cleanups in cJSON.c.
+       + commit 1933f5b8056b2671301379106cca4504c4187795
+       * src/cJSON.c: Add comments on the origin of the code.
+       (parse_string): Allocate an extra byte for safeness.
+       (cJSON_AddItemToArray): Allo ARRAY to be NULL.
+
+       json: Fix buffer overflow in cJSON.c.
+       + commit 013a7f47ab486c380b3c97637d3b15f11de74fe8
+       * src/cJSON.c (parse_string): Correctly detect bad hex.
+
+2018-07-13  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: example scripts.
+       + commit 1bb3f8409d4bbec403bea768184a691d9d7ea42d
+       * Fixed incorrect mention of output prompt referencing secret keys
+         when the scripts are only for exporting public keys in whole or
+         minimised forms.
+
+2018-07-11  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add with-sec-fprs param to export.
+       + commit 6cc842c9aa76d19448141e5117ac59452d7a1ff3
+       * src/gpgme-json.c (add_secret_fprs): New helper.
+       (op_export, hlp_export): Extend for with-sec fprs.
+
+2018-07-10  Ben McGinnes  <ben@adversary.org>
+
+       example scripts: python work-arounds.
+       + commit 0e760e396fbf13e902d0dc0c048bff0d5410fa16
+       * fixed three typos which were guaranteed to break said script.
+
+2018-07-09  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings howto.
+       + commit 1eceacaff4ad5d6a4b759a7d00907dbc8278f12c
+       * Complete typographic overhaul.
+       * Removed all section level indentation since it does not affect
+         output formatting, but might affect source code examples.
+       * In text-mode stripped out all tabs which had crept in and replaced
+         them with four spaces.
+       * Updated all code examples (again) to conform with Python-mode.
+       * Bumped version number in preparation for next release of GPG 2.2.9
+         and corresponding GPGME release.
+
+2018-07-09  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add with-secret without secret only.
+       + commit 40471ec12f7b4ba37922813a02ebb2aaaa963b51
+       * src/gpgme-json.c (op_keylist, hlp_keylist): Add "with-secret"
+       as a flag to do a public keylist with secret information.
+
+       tests: Add --with-secret to run-keylist.c.
+       + commit c287f09ac0f090a65299ac7c15e3c9b4947d4b67
+       * run-keylist.c (show_usage, main): Add --with-secret.
+
+       doc: More explicit doc for EXTERN and LOCAL modes.
+       + commit 31bc51e2aff0a28b551410c9def2f578d0d28cea
+       * doc/gpgme.texi (GPGME_KEYLIST_MODE_EXTERN),
+       (GPGME_KEYLIST_MODE_LOCATE, GPGME_KEYLIST_MODE_LOCAL): Mention
+       command line flags for CMS and OpenPGP Protocol.
+
+       Add ctx flag for auto-key-locate.
+       + commit 7bc5d3c7e41c6e42a583a61a4c9504058fbb2976
+       * src/context.h (gpgme_context): Add auto_key_locate.
+       * src/engine-gpg.c (engine_gpg): Add auto_key_locate.
+       (gpg_set_engine_flags, build_argv): Handle auto_key_locate.
+       (gpg_release): Free auto_key_locate.
+       * src/gpgme.c (gpgme_release): Free auto_key_locate.
+       (gpgme_get_ctx_flag, gpgme_set_ctx_flag): Handle auto-key-locate.
+       * doc/gpgme.texi: Document auto-key-locate flag.
+       * tests/run-keylist.c (show_usage, main): Add --from-wkd option.
+
+2018-07-07  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: howto examples.
+       + commit cacca62d06c6cf4a291f7ab2571cf52d671d140f
+       * Made sure all example scripts meet PEP8 compliance.
+       * Required fixing approx. a dozen of them in minor ways.
+
+2018-07-05  Andre Heinecke  <aheinecke@intevation.de>
+
+       qt: Handle OpenPGP Diagnostic log.
+       + commit 66c2a99422dd9a52d8342165ed1d033c4f29b9e0
+       * lang/qt/src/threadedjobmixin.cpp (_detail::audit_log_as_html):
+       Handle OpenPGP audit log differently.
+
+       cpp: Add enum mapping for GPGME_AUDIT_LOG_DIAG.
+       + commit 629afebe5017db97f2a318f6878fe1f9d3e60189
+       * src/context.cpp (to_auditlog_flags): Map DIAG value.
+       * src/context.h (AuditLogFlags): Add it.
+
+       core: Add gpg auditlog to get diagnostics.
+       + commit a2458806f8bf04b66795e1dde765b42fe1ef6797
+       * src/engine-gpg.c (engine_gpg): Add diagnostics member.
+       (gpg_release): Release diagnostics data.
+       (gpg_new): Set up logger-fd and diagnostics.
+       (gpg_getauditlog): New. Copy diagnostics to a user data.
+       (engine_ops): Add getauditlog.
+       * src/engine-gpgsm.c (gpgsm_getauditlog): Return not implemented
+       for GPGME_AUDITLOG_DIAG.
+       * src/getauditlog.c (getauditlog_start): Don't reset engine
+       for diagnostics.
+       * src/gpgme.h.in (GPGME_AUDITLOG_DIAG): New.
+       (GPGME_AUDITLOG_DEFAULT): New alias to 0.
+       * tests/run-decrypt.c (show_usage, main): Add --diagnostics.
+       * doc/gpgme.texi(Additional Logs): Document getauditlog.
+
+2018-07-04  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Fix memory of DecryptionResult::symkeyAlgo.
+       + commit 7d65dc2a5c4f32139a1b9b1f0bd375f7ab1c58f6
+       * lang/cpp/src/decryptionresult.cpp (Private, ~Private): strdup
+       the symkey algo.
+
+       json: Add keylist mode locate.
+       + commit 76b847091593669c8a7e38918267d6be97dbd4d0
+       * src/gpgme-json.c (op_keylist, hlp_keylist): Add locate.
+
+2018-07-01  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: scheming serpents.
+       + commit 5bca49975063f788b2499342d5a565faf54511db
+       * Apparently I am wrong and Scheme is the new Python after all.
+       * Non-import related PEP8 compliance must wait for another day, though
+         the other PEP8 fixes remain.
+
+       python bindings: gpg.core.
+       + commit 789ea1b019885d5d1db1662e3cd4fda33636e30c
+       * Changed id/else statements to a more pythonic form from scheme
+         masquerading as python - sorry Justus, it had to go ;).
+       * With the added bonus of enabling PEP8 compliance in those sections.
+       * Fixed remaining PEP8 compliance issues with the exception of the
+         imports at the beginning of the file (changing those will break the
+         entire module, so we'll cope with it as it is).
+
+2018-06-29  Ben McGinnes  <ben@adversary.org>
+
+       m4 update: python 3.7.
+       + commit 35e29e139534ed217340879732a7adfdbd57c91d
+       * Fixed an error in arcane m4 syntax.
+
+       python bindings: python 3.7.
+       + commit 43a2b5754571292b25402e20cd044ebda9316c77
+       * Bindings confirmed to work with the newly released 3.7.0.
+       * Updated M4 file to reflect this change and correct the Python binary
+         search order (3.7 is not yet given priority, but will still be found
+         first via the more generic python3 executable).
+       * Updated setup.py.in, bindings documentation and README to reflect this.
+
+2018-06-28  Ben McGinnes  <ben@adversary.org>
+
+       whitespace police:
+       + commit 48174b2bcc319e4542aefd0cc3aae02c4083784e
+       * There's always one or, in this case, two.
+
+       docs: python bindings howto.
+       + commit 6aec7d6e4a5173f54a079719020704a0098b8f0a
+       * Updated official doc (the org-mode file) with the instructions on
+         importing and exporting both public and secret keys.
+
+       python bindings examples.
+       + commit a7ccdc51efd8c199b902eb942e9db7b3549e721f
+       * Added a secret key export variant which saves output as both GPG
+         binary and ASCII armoured, plus saves in $GNUPGHOME and uses
+         multiple methods of determining what that location is.
+
+       python bindings examples.
+       + commit 7fc7e80e54235c558051cbf72b51dd60b4ca485e
+       * Added a key import variant which accesses the SKS keyservers in a
+         RESTful fashion and then imports or attempts to import the response.
+
+       python bindings examples: three export scripts.
+       + commit 0d163a7d121eacacc5f6da11a3dee3548f98f124
+       * Example of default exporting keys.
+       * Example of exporting minimised keys.
+       * Example of exporting secret keys to a file with correct permissions.
+
+       python bindings: import example.
+       + commit 4251cae34da0d825a29f509ac828f7ecb90c1752
+       * Added an example script for importing a key from a file (either
+         ASCII armoured or not).
+
+       python bindings: export secret keys.
+       + commit a5b91b21f561d7b13ddcb62fca3749ed92ea8720
+       * The holy grail: a function to export secret keys.
+       * GPGME will still invoke pinentry and gpg-agent as usual to authorise
+         the export.
+       * Mostly similar to the two previous export functions for public keys
+         except that it will return None if the result had a length of zero
+         bytes.  Meaning that the difference between the specified pattern
+         (if any) not matching available keys and an incorrect passphrase is
+         not able to be determined from this function (or the underlying one
+         for that matter).
+
+       python bindings: export public keys.
+       + commit 7faef33d13fa8efce152ca7aa6e9d39030c1cf08
+       * Updated key_export and key_export_minimal to return None where a
+         pattern matched no keys in a manner simnilar to the possible result
+         of key_export_secret.
+
+       python bindings: export public keys.
+       + commit 89c548efdf46bd7d9e6f0ca34a07efbbf420e821
+       * Added functions for exporting public keys to gpg.core in both
+         complete form and in minimised form.
+       * Rather than letting people need to worry about the export modes we
+         are simply separating the functions as people would be more familiar
+         with from the command line usage anyway.
+       * Functions added for Context are: ctx.key_export_minimal and
+         ctx.key_export as the default or full export.
+
+2018-06-20  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add file handling for debug output.
+       + commit d8beab30c44482fb7a3e445b92cec482792b2ca0
+       * src/gpgme-json.c (main): Add possibilty to set log file.
+
+2018-06-19  Andre Heinecke  <aheinecke@intevation.de>
+
+       Prepare build system for gpgme-js and dist it.
+       + commit d27703ea4f0eed950cddf0157dc78bcb5d8d1c65
+       * configure.ac: Add js as language.
+       * lang/Makefile.am: Add js as dist language.
+       * lang/js/BrowserTestExtension/Makefile.am,
+       lang/js/DemoExtension/Makefile.am,
+       lang/js/Makefile.am,
+       lang/js/src/Makefile.am: Populate EXTRA_DIST variables.
+
+2018-06-17  Ben McGinnes  <ben@adversary.org>
+
+       python bindings: core import statements.
+       + commit 19c5267f868aa41e73752ac1c76ec7ae1efe07b8
+       * Fixed the bit I broke while fixing the PEP8 compliance issues.
+
+       python bindings: core — PEP8 compliance.
+       + commit a5b24ae46c81d3abd3bb18bf0390cab6ebb2bd77
+       * Fixed most of the PEP8 errors in core.py
+       * Those remaining may need more than little edits and are a bit
+         strange (too clearly the result of a programmer who has spent far
+         too much time dealing with Lisp so that for Python it looks
+         ... strange).
+
+       python bindings: core - key import.
+       + commit 5a80e755008bbb3f4c7f91ffccd38f26cd8b3960
+       * Wrapped the key import function in the try/exception statements
+         needed to catch at least the most likely unsuccessful import attempt
+         errors.
+       * Mostly draws on the file error and no data import statuses for
+         errors, with a couple of exceptions.
+
+       python bindings: core key import.
+       + commit 0e762608ef5a598030b8d0e56261a830e1b7b724
+       * The foundation of a pythonic key import function authored by Jacob
+         Adams.
+       * A unit testing script for the same function originally authored by
+         Tobias Mueller
+       * Added DCO reference for Jacob Adams to the GPGME AUTHORS file.
+       * Additional details regarding this patch are available here:
+         https://dev.gnupg.org/T4001
+
+2018-06-10  Ben McGinnes  <ben@adversary.org>
+
+       script: groups.py.
+       + commit 92cd060f5e2f4fdbfbe4812ebe8ef57e82e1609f
+       * Added check for if it is run on a Windows system so that the correct
+         binary filename is invoked.
+
+2018-06-08  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Add proper gpgme_op_createkey.
+       + commit 8dff414e170e4df8ea661028c4ac1588311ca26e
+       * lang/cpp/src/context.cpp, lang/cpp/src/context.h
+       (Context::createKeyEx): New.
+
+       json: Return fingerprint as createkey result.
+       + commit 54146d90dd5518c24c30e6d2ebeb7f47ed4eb29e
+       * src/gpgme-json.c (op_createkey): Return fingerprint of new key.
+
+       json: Add op_createkey.
+       + commit 6c74a59e8855d1cac0d8001ad1b7843d2d15be5e
+       * src/gpgme-json.c (hlp_createkey, op_createkey): New.
+       (process_request, hlp_help): Add it.
+
+       json: Generalize chunking and getmore.
+       + commit af8510fb7f4d5c90cc53f10dcc740f377af73d68
+       * src/gpgme-json.c (MIN_REPLY_CHUNK_SIZE): Lower value to
+       new real minimum.
+       (DEF_REPLY_CHUNK_SIZE): Don't chunk by default.
+       (pending_data): Remove type and base64.
+       (make_data_object): Remove chunksize handling as this is now
+       generic.
+       (encode_and_chunk): Setup the pending_data buffer for chunking
+       if required.
+       (op_getmore): Changed to generically work on a response.
+       (hlp_getmore): Update accordingly.
+       (hlp_help): Document chunksize as generic parameter for all commands.
+       (process_request): Use encode_and_chunk on the response.
+       (hlp_encrypt, op_encrypt, hlp_decrypt, op_decrypt),
+       (hlp_verify, op_verify, hlp_sign, op_sign),
+       (op_keylist, hlp_keylist, hlp_export, op_export): Update accordingly.
+
+2018-06-07  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Rework verify_result_to_json.
+       + commit e48f4a18f807ee42b31968b84f6ae7811d8d8a45
+       * src/gpgme-json.c (sigsum_to_json): Add bool repr.
+       (signature_to_json, verify_result_to_json): Extend and follow better
+       pattern.
+       (hlp_decrypt, hlp_verify): Expand doc.
+
+       json: Add direct way to query a config option.
+       + commit 906ea48df3e178bea3e2c744ddd834b23fe553fb
+       * src/gpgme-json.c (op_config_opt, hlp_config_opt): New operation.
+       (process_request, hlp_help): Add it.
+
+       json: Add op_config to query gpgconf.
+       + commit 7e18c7a07a1e2c58ef4d5ddeed46bcce098bdf6c
+       * src/gpgme-json.c (op_config, hlp_config): New.
+       (hlp_help, process_request): Add config.
+       (conf_arg_to_json, conf_opt_to_json, conf_comp_to_json): New
+       helpers.
+
+2018-06-07  Ben McGinnes  <ben@adversary.org>
+
+       examples: python howto.
+       + commit a3a08584d6e57c4b2a40d74dc52a7d8c5c11b501
+       * Uses the groups module to prepare a list of recipients and encrypt
+         to those.
+       * The main version (encrypt-to-group.py) tries to check for invalid
+         recipients, but still falls back to always trust for the second
+         encryption attempt.
+       * The gullible version doesn't try pruning the recipient list at all,
+         it just tries to encrypt and if it fails, switches straight to
+         always trust.
+       * The trustno1 version doesn't use the always trust model at all and
+         only attempts pruning the list of invalid recipients.
+
+2018-06-06  Werner Koch  <wk@gnupg.org>
+
+       core: Return a better error code on certain decryption failures.
+       + commit 2c4c5692472f5870f907c2c3f01870879cb0c34b
+       * src/decrypt.c (op_data_t): Add field first_status_error.
+       (parse_status_error): Set it.
+       (_gpgme_decrypt_status_handler): Prefer an ERROR code over a
+       NO_SECKEY.
+
+2018-06-05  Werner Koch  <wk@gnupg.org>
+
+       json: Allow to compile with libgpg-error < 1.28.
+       + commit 998fec8a4fbc46315fe6836980954eed402b38c5
+       * src/cJSON.c: Use gpgrt fucntion only if available.
+
+2018-06-02  Ben McGinnes  <ben@adversary.org>
+
+       docs: python bindings howto.
+       + commit 897423422b9d3b856bfb72fbe1995b91d153a54e
+       * Another attempt at fixing the org-mode version.
+       * A proof reader ascertained there were tabs in it instead of whitespace.
+       * Stripped the lot out and replaced with standard 4 spaces, fixed
+         every incorrect example ... and it still breaks upon save and/or export.
+       * Added the reference to the mutt-groups.py script to demonstrate the
+         groups.py module/code.
+
+       Authors: DCO.
+       + commit e144a6d70657675e28d43e42b48d879ff9b81d73
+       * Added mine to the GPGME AUTHORS file.  Mainly so I'm doing what I
+         say myself when about to request another from someone ...  ;)
+
+2018-06-01  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Add gpgme_(get)set_ctx_flag.
+       + commit 00b027af86f33782933c6200fe1ffe40e85f4346
+       * NEWS: Mention API extensions.
+       * lang/cpp/src/context.cpp, lang/cpp/src/context.h
+       (Context::setFlag, Context::getFlag): New.
+
+       cpp: Add legacy_cipher_nomdc.
+       + commit d46768c96082b4fd076506d7d3f0c03e61c59d51
+       * lang/cpp/src/decryptionresult.cpp, lang/cpp/src/decryptionresult.h
+       (DecryptionResult::isLegacyCipherNoMDC): New.
+
+2018-06-01  Werner Koch  <wk@gnupg.org>
+
+       core: New context flag "ignore-mdc-error".
+       + commit 662604c5bcb4e03d3c9ecc670d4f320a2418ebb3
+       * src/context.h (gpgme_context): Add field ignore_mdc_error.
+       * src/gpgme.c (gpgme_set_ctx_flag, gpgme_get_ctx_flag): Set/get it.
+       * src/engine-gpg.c (engine_gpg): Add flags.ignore_mdc_error.
+       (gpg_set_engine_flags): Set it.
+       (build_argv): Pass option to gpg.
+       * src/decrypt.c (_gpgme_decrypt_status_handler): Take care of flag.
+       (gpgme_op_decrypt_result): Clear flag.
+       (gpgme_op_decrypt): Clear flag.
+       * src/decrypt-verify.c (gpgme_op_decrypt_verify): Clear flag
+       (gpgme_op_decrypt_ext): Clear flag.
+
+       * tests/run-decrypt.c (show_usage): Add option --ignore-mdc-error.
+
+       core: New decryption result flag 'legacy_cipher_nomdc'.
+       + commit dd19cabe81b7bf4177ea2ca741f6eb6cd1cab25e
+       * src/gpgme.h.in (_gpgme_op_decrypt_result): Add flag
+       legacy_cipher_nomdc.
+       * src/decrypt.c (parse_status_error): Set this flag.
+       * tests/run-decrypt.c (print_result): print it.
+       (main): Print the result even on error.
+
+       core: Remove cruft from the engine-gpg code.
+       + commit e2aa38b56a991a0da052acfe7566cc7a146d3bb6
+       * src/engine-gpg.c (read_status): Remove the handling of
+       GPGME_STATUS_END_STREAM; this was used only by the former experimental
+       --pipemode of gpg but that is not even anymore invoked here.
+       (struct engine_gpg): Remove cmd.linked_data and .linked_idx.
+       (build_argv): Remove code for linked_data.
+       (gpg_new): Ditto.
+       (gpg_set_command_handler): Remove arr linked_data.
+       * src/engine-backend.h (engine_ops): Remove arg data from
+       set_command_handler.
+       * src/engine.c (_gpgme_engine_set_command_handler): Remove arg
+       linked_data and adjust all callers.
+
+       core: Minor cleanup in engine-gpg and -gpgsm.
+       + commit 2219fc19d58ae1071493de92093b711f3c71454a
+       * src/engine-gpg.c: Remove errno.h.
+       (build_argv): Use gpg_error_from_syserror instead of ERRNO.
+       * src/engine-gpgsm.c: Remove errno.h.
+       (status_handler): Remove check for EINTR; gpgme_data_write already
+       handles EINTR.
+
+2018-05-29  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Add gpgme_data_rewind to cpp API.
+       + commit 618aa7f08db41911f25632d9fba23bca80908ebe
+       * lang/cpp/src/data.h, lang/cpp/src/data.cpp (Data::rewind): New.
+       * lang/qt/tests/t-various.cpp (testDataRewind): Test it.
+
+2018-05-28  Werner Koch  <wk@gnupg.org>
+
+       json: Fix compiler warning.
+       + commit 77166851f165b1220dcf0116bb61f81e58e4512f
+       * src/gpgme-json.c (op_version): Mark request as unused.
+
+       json: Do not allow to export or delete secret keys.
+       + commit 0de991fee05a9733ac29b2fa35643fe4607e56cb
+       * src/gpgme-json.c (op_export, op_delete): Return GPG_ERR_FORBIDDEN if
+       "secret" is used.
+
+       json: Fix use of get_context.
+       + commit 368f2d9db30df16328b34787419de99fe3e1e2f1
+       * src/gpgme-json.c (create_onetime_context): New.
+       (release_onetime_context): New.
+       (op_sign): Use the new fucntions to create a separate context.
+       (op_encrypt): Use a separate context for key listings.
+       (create_keylist_patterns): Remove unneeded cast.
+
+2018-05-25  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Implement encrypt-sign.
+       + commit 7aa00917c7f13294584daba31a506730f0015ef5
+       * src/gpgme-json.c (op_encrypt): Add optional signing_keys param.
+       (get_keys, create_keylist_patterns): Add param for json object name.
+
+       json: Implement delete command.
+       + commit 73dc5e933d609989bd9ec428c89ada95d5eaec8a
+       * src/gpgme-json.c (op_delete): New.
+       (hlp_help, process_request): Add it.
+
+       json: Implement op_export.
+       + commit a1bbe7473a4d0f31d471d6cceb2f7e1382860194
+       * src/gpgme-json.c (op_export): New.
+       (hlp_getmore, process_request): Add it.
+
+       json: Fix double ctx alloc.
+       + commit 897522527d493307d15809a41496f8b76ec4edfe
+       * src/gpgme-json.c (op_keylist): Only get one ctx.
+
+       json: Add guard in create_keylist_patterns.
+       + commit 10683b1a913c39238c9871f5aa607334f32467f7
+       * src/gpgme-json.c (create_keylist_patterns): Guard against
+       a string ending with a linbreak.
+
+       json: Implement import operation.
+       + commit 1ff16dad595946af140b324fad2bceca7ddbc378
+       * src/gpgme-json.c (op_import): New.
+       (import_status_to_json, import_result_to_json): New.
+       (hlp_help, process_request): Add import.
+
+       json: Improve keylist help.
+       + commit fe06929deec6544e2f267937dc5dafe2555bf485
+       * src/gpgme-json.c (hlp_keylist): Clarify doc about keys parameter.
+
+       json: Fix keylist pattern handling.
+       + commit 10724e363d6b5b06f51808287ad63e142e419ae6
+       * src/gpgme-json.c (xfree_array): New helper.
+       (create_keylist_patterns): Convert keystring to gpgme patterns.
+       (op_sign, op_keylist): Use it.
+
+       json: Add additional commands to help.
+       + commit ed20936ce4d5bb066828346b9a06d2dfa8d6afb2
+       * src/gpgme-json.c (hlp_help): Add additional commands.
+
+       json: Refactor signature and ei code.
+       + commit aa59f4970ac106ccd17fa9fadfc264d11e4cd347
+       * src/gpgme-json.c (add_summary_to_object): Changed to:
+       sigsum_to_json.
+       (add_signature_to_object): Changed to signature_to_json.
+       (add_signatures_to_object): Changed to verify_result_to_json.
+       (add_ei_to_object): Changed to engine_info_to_json.
+       (op_decrypt, op_verify, op_version): Use new functions.
+
+       json: Implement keylist.
+       + commit 546e3295eaade073c34364c4ea6ab007532824d8
+       * src/gpgme-json.c (xjson_AddStringToObject0)
+       (xjson_AddItemToObject): New helpers.
+       (sig_notation_to_json, key_sig_to_json, tofu_to_json)
+       (uid_to_json, subkey_to_json, key_to_json): New
+       GPGME to JSON functions.
+       (op_keylist): New.
+       (process_request): Add op_keylist.
+
+       json: Deduplicate input handling code.
+       + commit a46c27b32111b1737a405c5be48c0f9ddbbbb353
+       * gpgme-json.c (get_string_data): New.
+       (op_verify, op_sign, op_decrypt, op_encrypt): Use it.
+
+2018-05-24  Ben McGinnes  <ben@adversary.org>
+
+       examples: mutt crypt-hooks generator.
+       + commit 321005c12f716814d86e139eb265437bda01380f
+       * Added a script which demonstrates how the groups module works.
+       * Script generates Mutt/Neomutt crypt-hooks for every group entry in
+         gpg.conf, including those entries for multiple keys (Mutt handles
+         that differently).
+
+       docs: python bindings howto.
+       + commit 48e946a96d05effd56b34761b98eebb6b2a6fae1
+       * Fixed the groups.py script so it really does what is described (the
+         old code had the same result for groups, group_lines and
+         group_lists).
+       * Updated the corresponding example in the doc to match.
+
+       doc: python bindings howto.
+       + commit 9038d30017b2e38b183d093f9b5d46e1f24c70a6
+       * Fixed org-mode python source indenting.
+       ** Note: nested indented blocks do not indent correctly when exported
+          to [X]HTML.
+
+2018-05-24  Andre Heinecke  <aheinecke@intevation.de>
+
+       json: Add code to gpg_error based messages.
+       + commit 9fca7c84067c0d4c968c8920dc8ef9449769c5ea
+       * src/gpgme-json.c (gpg_error_object): New.
+       (error_object_v): Extend to take error.
+
+       json: Implement op_version.
+       + commit 61f4532ba979dea1acd8c7de9b7d56bb8ff552d5
+       * src/gpgme-json.c (op_version): New.
+       (process_request): Extend for version.
+       (protocol_to_string, add_ei_to_object): New helpers.
+
+       json: Implement op_verify.
+       + commit 5fbf81c18b277b30639ba09e8c5d21573b444b00
+       * src/gpgme-json.c (op_verify): New.
+       (hlp_help): Add verify.
+       (process_request): Add verify.
+
+       json: Put signature info before data output.
+       + commit c679ed24778c997fee72d3613babad8680855882
+       * src/gpgme-json.c (op_decrypt): Move info before data.
+
+       json: Add sign to help.
+       + commit a6cd3a1197eb4efea0950394959c252f24475f67
+       * src/gpgme-json.c (hlp_help): Add sign.
+
+       json: Add op_sign.
+       + commit 1c0a55a60847563fecf92a383457ab3576aec5d8
+       * src/gpgme-json.c (op_sign): New.
+
+       json: Fix invalid function call.
+       + commit b344933e4cb17f2f26c4ed355217428bda8b8c40
+       * src/gpgme-json.c (add_signatures_to_object): Fix call to
+       xjson_CreateArray.
+
+       json: Print signatures for decrypt/verify.
+       + commit 45036c3c4c11f7bd56a00805564108e9377b657e
+       * gpgme-json.c (xJSON_CreateArray),
+       (add_summary_to_object, validity_to_string): New helpers.
+       (add_signature_to_object, add_signatures_to_object)
+       (add_signatures_object): New.
+       (op_decrypt): Handle verify_result.
+       (hlp_help): Mention decrypt.
+
+       json: Minor typo fixes.
+       + commit fd5e14660a6f4eb1a89d69534e3e435f7fb05f8a
+       * src/gpgme-json.c: Minor typo fixes.
+
+2018-05-23  Ben McGinnes  <ben@adversary.org>
+
+       docs and examples: python howto.
+       + commit 3a9e6a8e088e233097866bb0560a36cfbbc4470e
+       * Updated the decryption example code in the HOWTO and the
+         corresponding decrypt-file.py script to gracefully handle a
+         decryption failure.  This error will always be triggered when GPGME
+         is used to try to decrypt an old, MDC-less encrypted message or
+         file.
+
+2018-05-22  Andre Heinecke  <aheinecke@intevation.de>
+
+       cpp: Expose sessionKey and symkeyAlgo.
+       + commit 28e3778ce21069006153bc156a414de6d9347962
+       * lang/cpp/decryptionresult.cpp, lang/cpp/decryptionresult.h
+       (DecryptionResult::symkeyAlgo, DecryptionResult::sessionKey): New.
+
+2018-05-17  Andre Heinecke  <aheinecke@intevation.de>
+
+       core, w32: Add w64 handling for regkeys.
+       + commit e04b8142df21a49e6c4a3f8234cc14bfec217222
+       * src/w32-util.c (_gpgme_get_gpg_path): Use new defines.
+       (GNUPG_REGKEY_2): x64 aware regkey as used by GnuPG in Gpg4win 2.x
+       (GNUPG_REGKEY_3): x64 aware regkey as used by GnuPG in Gpg4win 3.x
+       (_gpgme_get_gpgconf_path): Use new regkeys. Add another fallback.
+
+2018-05-17  Werner Koch  <wk@gnupg.org>
+
+       core: Always fail if an OpenPG message is not integrity protected.
+       + commit 8a0c8c52510d9c2d934f85159f04b666286b1786
+       * src/decrypt.c (struct op_data_t): Add field not_integrity_protected.
+       (parse_decryption_info): Set this.  Also rename mode to aead_algo for
+       clarity.
+       (_gpgme_decrypt_status_handler): Force failure in case of a missing
+       MDC.
+
+2018-05-09  Werner Koch  <wk@gnupg.org>
+
+       json: Improve auto-base64 encoding to not split UTF-8 chars.
+       + commit e54b110aec3165a32ff9551d0c5227b88aa3dd4f
+       * src/gpgme-json.c (make_data_object): Switch to Base64 also for UTF-8
+       characters.
+
+       core: Make the status-fd monitor work for all gpgsm commands.
+       + commit e2a8a87bf9cfae5d4e8a5953c2a5303b44feb398
+       * src/engine-gpgsm.c (status_handler): Call the status monitor also
+       here.
+
+2018-05-05  Ben McGinnes  <ben@adversary.org>
+
+       python: key expiration datetime stamp tests.
+       + commit 46da79e3de99a7b65748994921d6aab73b9974e7
+       * Changed the expiration date for the generated test key to NYE this
+         century, rather than the NYE this millennium as originally suggested
+         in job #3815.
+       * This covers the lifetimes of current users (except, maybe, some very
+         healthy millennials) as well as the 32-bit clock end date in 2038;
+         without falling foul of OpenPGP's 2106 expiration.
+
+2018-05-04  Andre Heinecke  <aheinecke@intevation.de>
+
+       qt: Respect --disable-gpg-test for tests.
+       + commit 26820ba62920acfe2775bce8fc2bc8b3f17b1c10
+       * lang/qt/Makefile.am: Respect --disable-gpg-test
+
+2018-04-26  Andre Heinecke  <aheinecke@intevation.de>
+
+       qt: Fix filename handling in cryptoconfig.
+       + commit 6b267c56fd6e54fb1c254455c04495534260b547
+       * src/qgpgmenewcryptoconfig.cpp (QGpgMENewCryptoConfigEntry::urlValue):
+       Build url from local file.
+       (QGpgMENewCryptoConfigEntry::setURLValue): Set native seperated
+       path.
+
+2018-04-20  Maximilian Krambach  <maximilian.krambach@intevation.de>
+
+       js: encrypt improvement and decrypt method.
+       + commit 6ab25e40d904007755c5d999bf66ae264236e745
+       * Compatibility class gpgme_openpgpjs offers an API that should accept
+         openpgpjs syntax, throwing errors if a parameter is unexpected/not
+         implemented
+       * tried to be more generic in methods
+       * waiting for multiple answers if 'more' is in the answer
+       * more consistency checking on sending and receiving
+       * updated the example extension
+
+2018-04-20  Andre Heinecke  <aheinecke@intevation.de>
+
+       core: Do not modify args for ignored failures.
+       + commit 7706fa2c922f5e02570b01f145ed474e82341042
+       * src/op-support.c (_gpgme_parse_failure): Ignore gpg-exit failures
+       before modifying args.
+
 2018-04-20  Werner Koch  <wk@gnupg.org>
 
        Release 1.11.1.
index 2dc02e7..829cf14 100644 (file)
@@ -34,7 +34,8 @@ ACLOCAL_AMFLAGS = -I m4
 DISTCHECK_CONFIGURE_FLAGS =
 
 EXTRA_DIST = autogen.sh autogen.rc gpgme.spec.in                       \
-             ChangeLog-2011 m4/ChangeLog-2011 contrib/ChangeLog-2011
+             ChangeLog-2011 m4/ChangeLog-2011 contrib/ChangeLog-2011    \
+             conf/whatisthis
 
 
 if RUN_GPG_TESTS
index 7803997..b9f42aa 100644 (file)
@@ -100,13 +100,13 @@ subdir = .
 DIST_COMMON = INSTALL NEWS README AUTHORS ChangeLog \
        $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
        $(top_srcdir)/configure $(am__configure_deps) \
-       $(srcdir)/config.h.in $(top_srcdir)/build-aux/mkinstalldirs \
-       COPYING COPYING.LESSER THANKS TODO build-aux/compile \
-       build-aux/config.guess build-aux/config.sub build-aux/depcomp \
-       build-aux/install-sh build-aux/mdate-sh missing \
-       build-aux/missing build-aux/mkinstalldirs \
-       build-aux/texinfo.tex build-aux/ltmain.sh \
-       $(top_srcdir)/build-aux/compile \
+       $(top_srcdir)/conf/config.h.in \
+       $(top_srcdir)/build-aux/mkinstalldirs COPYING COPYING.LESSER \
+       THANKS TODO build-aux/compile build-aux/config.guess \
+       build-aux/config.sub build-aux/depcomp build-aux/install-sh \
+       build-aux/mdate-sh missing build-aux/missing \
+       build-aux/mkinstalldirs build-aux/texinfo.tex \
+       build-aux/ltmain.sh $(top_srcdir)/build-aux/compile \
        $(top_srcdir)/build-aux/config.guess \
        $(top_srcdir)/build-aux/config.sub \
        $(top_srcdir)/build-aux/install-sh \
@@ -129,7 +129,7 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
  configure.lineno config.status.lineno
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
@@ -167,8 +167,7 @@ am__recursive_targets = \
   $(am__extra_recursive_targets)
 AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
        cscope distdir dist dist-all distcheck
-am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \
-       $(LISP)config.h.in
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
 # Read a list of newline-separated strings from the standard input,
 # and print each of them once, without duplicates.  Input order is
 # *not* preserved.
@@ -439,7 +438,8 @@ RELEASE_SIGNING_KEY = D8692123C4065DEA5E0F3AB5249B39D24F25E3B6
 ACLOCAL_AMFLAGS = -I m4
 DISTCHECK_CONFIGURE_FLAGS = 
 EXTRA_DIST = autogen.sh autogen.rc gpgme.spec.in                       \
-             ChangeLog-2011 m4/ChangeLog-2011 contrib/ChangeLog-2011
+             ChangeLog-2011 m4/ChangeLog-2011 contrib/ChangeLog-2011    \
+             conf/whatisthis
 
 @RUN_GPG_TESTS_FALSE@tests = 
 @RUN_GPG_TESTS_TRUE@tests = tests
@@ -448,8 +448,7 @@ gen_start_date = 2011-12-01T00:00:00
 
 # Macro to help the release target.
 RELEASE_NAME = $(PACKAGE_TARNAME)-$(PACKAGE_VERSION)
-all: config.h
-       $(MAKE) $(AM_MAKEFLAGS) all-recursive
+all: all-recursive
 
 .SUFFIXES:
 am--refresh: Makefile
@@ -487,20 +486,20 @@ $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
        $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
 $(am__aclocal_m4_deps):
 
-config.h: stamp-h1
-       @test -f $@ || rm -f stamp-h1
-       @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1
+conf/config.h: conf/stamp-h1
+       @test -f $@ || rm -f conf/stamp-h1
+       @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) conf/stamp-h1
 
-stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
-       @rm -f stamp-h1
-       cd $(top_builddir) && $(SHELL) ./config.status config.h
-$(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) 
+conf/stamp-h1: $(top_srcdir)/conf/config.h.in $(top_builddir)/config.status
+       @rm -f conf/stamp-h1
+       cd $(top_builddir) && $(SHELL) ./config.status conf/config.h
+$(top_srcdir)/conf/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) 
        ($(am__cd) $(top_srcdir) && $(AUTOHEADER))
-       rm -f stamp-h1
+       rm -f conf/stamp-h1
        touch $@
 
 distclean-hdr:
-       -rm -f config.h stamp-h1
+       -rm -f conf/config.h conf/stamp-h1
 
 mostlyclean-libtool:
        -rm -f *.lo
@@ -810,7 +809,7 @@ distcleancheck: distclean
               exit 1; } >&2
 check-am: all-am
 check: check-recursive
-all-am: Makefile config.h
+all-am: Makefile
 installdirs: installdirs-recursive
 installdirs-am:
 install: install-recursive
@@ -913,7 +912,7 @@ ps-am:
 
 uninstall-am:
 
-.MAKE: $(am__recursive_targets) all install-am install-strip
+.MAKE: $(am__recursive_targets) install-am install-strip
 
 .PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
        am--refresh check check-am clean clean-cscope clean-generic \
diff --git a/NEWS b/NEWS
index 7603130..883159f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,47 @@
+Noteworthy changes in version 1.12.0 (2018-10-08)
+-------------------------------------------------
+
+ * Enhanced the JSON based interface tool gpgme-json to support Native
+   Messaging as well as new Javascript code to support the browser
+   site.  See lang/js/README for details.
+
+ * Major overhaul of the Python language bindings documentation.
+
+ * Even for old versions of gpg a missing MDC will now lead to a
+   decryption failure.
+
+ * Added context flag "auto-key-locate" to control the
+   behavior of GPGME_KEYLIST_MODE_LOCATE.
+
+ * New data function to create a data object from an estream.
+
+ * Add more interfaces to the C++ bindings.
+
+ * Improved error codes on decryption failure.
+
+ * Lots of minor fixes.
+
+ * Interface changes relative to the 1.11.1 release:
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ gpgme_data_new_from_estream                NEW.
+ gpgme_decrypt_result_t           EXTENDED: New field legacy_cipher_nomdc.
+ gpgme_set_ctx_flag               EXTENDED: New flag 'ignore-mdc-error'.
+ GPGME_AUDITLOG_DEFAULT                     NEW.
+ GPGME_AUDITLOG_DIAG                        NEW.
+ gpgme_set_ctx_flag               EXTENDED: New flag 'auto-key-locate'.
+ cpp: DecryptionResult::sessionKey          NEW.
+ cpp: DecryptionResult::symkeyAlgo          NEW.
+ cpp: DecryptionResult::isLegacyCipherNoMDC New.
+ cpp: Data::rewind                          NEW.
+ cpp: Context::setFlag                      NEW.
+ cpp: Context::getFlag                      NEW.
+ cpp: Context::createKeyEx                  NEW.
+
+ [c=C32/A21/R0 cpp=C14/A8/R0 qt=C10/A3/R2]
+
+ Release-info: https://dev.gnupg.org/T4109
+
+
 Noteworthy changes in version 1.11.1 (2018-04-20)
 -------------------------------------------------
 
diff --git a/VERSION b/VERSION
index 720c738..0eed1a2 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.11.1
+1.12.0
similarity index 99%
rename from config.h.in
rename to conf/config.h.in
index d388ae5..294284a 100644 (file)
@@ -1,4 +1,4 @@
-/* config.h.in.  Generated from configure.ac by autoheader.  */
+/* conf/config.h.in.  Generated from configure.ac by autoheader.  */
 
 /* GIT commit id revision used to build this package */
 #undef BUILD_REVISION
diff --git a/conf/whatisthis b/conf/whatisthis
new file mode 100644 (file)
index 0000000..9005fe9
--- /dev/null
@@ -0,0 +1,2 @@
+Configuration files may go here.  Note that config.h.in is
+auto-generated so that this file is not in git.
index 5678a32..20affd3 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for gpgme 1.11.1.
+# Generated by GNU Autoconf 2.69 for gpgme 1.12.0.
 #
 # Report bugs to <http://bugs.gnupg.org>.
 #
@@ -590,8 +590,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='gpgme'
 PACKAGE_TARNAME='gpgme'
-PACKAGE_VERSION='1.11.1'
-PACKAGE_STRING='gpgme 1.11.1'
+PACKAGE_VERSION='1.12.0'
+PACKAGE_STRING='gpgme 1.12.0'
 PACKAGE_BUGREPORT='http://bugs.gnupg.org'
 PACKAGE_URL=''
 
@@ -1457,7 +1457,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures gpgme 1.11.1 to adapt to many kinds of systems.
+\`configure' configures gpgme 1.12.0 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1527,7 +1527,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of gpgme 1.11.1:";;
+     short | recursive ) echo "Configuration of gpgme 1.12.0:";;
    esac
   cat <<\_ACEOF
 
@@ -1677,7 +1677,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-gpgme configure 1.11.1
+gpgme configure 1.12.0
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2507,7 +2507,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by gpgme $as_me 1.11.1, which was
+It was created by gpgme $as_me 1.12.0, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2791,6 +2791,7 @@ as_fn_append ac_header_list " locale.h"
 as_fn_append ac_header_list " sys/select.h"
 as_fn_append ac_header_list " sys/uio.h"
 as_fn_append ac_header_list " argp.h"
+as_fn_append ac_header_list " stdint.h"
 as_fn_append ac_header_list " unistd.h"
 as_fn_append ac_header_list " sys/time.h"
 as_fn_append ac_header_list " sys/types.h"
@@ -2870,15 +2871,15 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 #   (Interfaces added:                 AGE++)
 #   (Interfaces removed/changed:       AGE=0)
 #
-LIBGPGME_LT_CURRENT=31
-LIBGPGME_LT_AGE=20
-LIBGPGME_LT_REVISION=1
+LIBGPGME_LT_CURRENT=32
+LIBGPGME_LT_AGE=21
+LIBGPGME_LT_REVISION=0
 
 # If there is an ABI break in gpgmepp or qgpgme also bump the
 # version in IMPORTED_LOCATION in the GpgmeppConfig-w32.cmake.in.in
 
-LIBGPGMEPP_LT_CURRENT=13
-LIBGPGMEPP_LT_AGE=7
+LIBGPGMEPP_LT_CURRENT=14
+LIBGPGMEPP_LT_AGE=8
 LIBGPGMEPP_LT_REVISION=0
 
 LIBQGPGME_LT_CURRENT=10
@@ -2898,8 +2899,8 @@ PACKAGE=$PACKAGE_NAME
 VERSION=$PACKAGE_VERSION
 
 VERSION_MAJOR=1
-VERSION_MINOR=11
-VERSION_MICRO=1
+VERSION_MINOR=12
+VERSION_MICRO=0
 
 ac_aux_dir=
 for ac_dir in build-aux "$srcdir"/build-aux; do
@@ -2932,7 +2933,7 @@ ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
 
 
 
-ac_config_headers="$ac_config_headers config.h"
+ac_config_headers="$ac_config_headers conf/config.h"
 
 am__api_version='1.14'
 
@@ -3420,7 +3421,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='gpgme'
- VERSION='1.11.1'
+ VERSION='1.12.0'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -6470,7 +6471,7 @@ cat >>confdefs.h <<_ACEOF
 #define VERSION "$VERSION"
 _ACEOF
 
-VERSION_NUMBER=0x010b01
+VERSION_NUMBER=0x010c00
 
 
 # We need to compile and run a program on the build machine.  A
@@ -19509,7 +19510,7 @@ if ${am_cv_pathless_PYTHON+:} false; then :
   $as_echo_n "(cached) " >&6
 else
 
-       for am_cv_pathless_PYTHON in python2 python2.7  python  python3 python3.0 python3.1 python3.2 python3.3   python3.4 python3.5 python3.6 python3.7 python3.8 none; do
+       for am_cv_pathless_PYTHON in python2 python2.7  python  python3 python3.7 python3.6 python3.5 python3.4   none; do
          test "$am_cv_pathless_PYTHON" = none && break
          prog="import sys
 # split strings by '.' and convert to numeric.  Append some zeros
@@ -20143,7 +20144,7 @@ if ${am_cv_pathless_PYTHON+:} false; then :
   $as_echo_n "(cached) " >&6
 else
 
-       for am_cv_pathless_PYTHON in python2 python2.7  python  python3 python3.0 python3.1 python3.2 python3.3   python3.4 python3.5 python3.6 python3.7 python3.8 none; do
+       for am_cv_pathless_PYTHON in python2 python2.7  python  python3 python3.7 python3.6 python3.5 python3.4   none; do
          test "$am_cv_pathless_PYTHON" = none && break
          prog="import sys
 # split strings by '.' and convert to numeric.  Append some zeros
@@ -20754,7 +20755,7 @@ ENABLED_LANGUAGES=$enabled_languages
 #
 # Provide information about the build.
 #
-BUILD_REVISION="2e9a149"
+BUILD_REVISION="1aff2512"
 
 
 cat >>confdefs.h <<_ACEOF
@@ -20763,7 +20764,7 @@ _ACEOF
 
 
 BUILD_FILEVERSION=`echo "$PACKAGE_VERSION"|sed 's/\([0-9.]*\).*/\1./;s/\./,/g'`
-BUILD_FILEVERSION="${BUILD_FILEVERSION}11930"
+BUILD_FILEVERSION="${BUILD_FILEVERSION}6911"
 
 
 # Check whether --enable-build-timestamp was given.
@@ -20886,6 +20887,8 @@ done
 
 
 
+
+
 # Type checks.
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for inline" >&5
 $as_echo_n "checking for inline... " >&6; }
 
 
 
+# We require uint64_t
+if test "$ac_cv_header_stdint_h" != yes; then
+   as_fn_error $? "
+***
+*** No stdint.h and thus no uint64_t type.  Can't build this library.
+***" "$LINENO" 5
+fi
+
+
 # A simple compile time check in gpgme.h for GNU/Linux systems that
 # prevents a file offset bits mismatch between gpgme and the application.
 NEED__FILE_OFFSET_BITS=0
@@ -22398,6 +22410,8 @@ if test -z "$HAVE_DOXYGEN_TRUE"; then :
   ac_config_files="$ac_config_files lang/qt/doc/Doxyfile"
 
 fi
+ac_config_files="$ac_config_files lang/js/Makefile lang/js/src/Makefile lang/js/BrowserTestExtension/Makefile lang/js/DemoExtension/Makefile"
+
 ac_config_files="$ac_config_files lang/qt/doc/Makefile"
 
 ac_config_files="$ac_config_files lang/python/Makefile lang/python/version.py lang/python/tests/Makefile"
@@ -23006,7 +23020,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by gpgme $as_me 1.11.1, which was
+This file was extended by gpgme $as_me 1.12.0, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -23072,7 +23086,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-gpgme config.status 1.11.1
+gpgme config.status 1.12.0
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -23664,7 +23678,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 for ac_config_target in $ac_config_targets
 do
   case $ac_config_target in
-    "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+    "conf/config.h") CONFIG_HEADERS="$CONFIG_HEADERS conf/config.h" ;;
     "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
     "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;;
     "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
     "lang/cl/Makefile") CONFIG_FILES="$CONFIG_FILES lang/cl/Makefile" ;;
     "lang/cl/gpgme.asd") CONFIG_FILES="$CONFIG_FILES lang/cl/gpgme.asd" ;;
     "lang/qt/doc/Doxyfile") CONFIG_FILES="$CONFIG_FILES lang/qt/doc/Doxyfile" ;;
+    "lang/js/Makefile") CONFIG_FILES="$CONFIG_FILES lang/js/Makefile" ;;
+    "lang/js/src/Makefile") CONFIG_FILES="$CONFIG_FILES lang/js/src/Makefile" ;;
+    "lang/js/BrowserTestExtension/Makefile") CONFIG_FILES="$CONFIG_FILES lang/js/BrowserTestExtension/Makefile" ;;
+    "lang/js/DemoExtension/Makefile") CONFIG_FILES="$CONFIG_FILES lang/js/DemoExtension/Makefile" ;;
     "lang/qt/doc/Makefile") CONFIG_FILES="$CONFIG_FILES lang/qt/doc/Makefile" ;;
     "lang/python/Makefile") CONFIG_FILES="$CONFIG_FILES lang/python/Makefile" ;;
     "lang/python/version.py") CONFIG_FILES="$CONFIG_FILES lang/python/version.py" ;;
@@ -25384,7 +25402,7 @@ fi
 echo "
         GPGME v${VERSION} has been configured as follows:
 
-        Revision:          2e9a149  (11930)
+        Revision:          1aff2512  (6911)
         Platform:          $host
 
         UI Server:         $uiserver
index 2a35404..af569ac 100644 (file)
@@ -27,8 +27,8 @@ min_automake_version="1.14"
 # commit and push so that the git magic is able to work.  See below
 # for the LT versions.
 m4_define(mym4_version_major, [1])
-m4_define(mym4_version_minor, [11])
-m4_define(mym4_version_micro, [1])
+m4_define(mym4_version_minor, [12])
+m4_define(mym4_version_micro, [0])
 
 # Below is m4 magic to extract and compute the revision number, the
 # decimalized short revision number, a beta version string, and a flag
@@ -54,15 +54,15 @@ AC_INIT([gpgme],[mym4_full_version],[http://bugs.gnupg.org])
 #   (Interfaces added:                 AGE++)
 #   (Interfaces removed/changed:       AGE=0)
 #
-LIBGPGME_LT_CURRENT=31
-LIBGPGME_LT_AGE=20
-LIBGPGME_LT_REVISION=1
+LIBGPGME_LT_CURRENT=32
+LIBGPGME_LT_AGE=21
+LIBGPGME_LT_REVISION=0
 
 # If there is an ABI break in gpgmepp or qgpgme also bump the
 # version in IMPORTED_LOCATION in the GpgmeppConfig-w32.cmake.in.in
 
-LIBGPGMEPP_LT_CURRENT=13
-LIBGPGMEPP_LT_AGE=7
+LIBGPGMEPP_LT_CURRENT=14
+LIBGPGMEPP_LT_AGE=8
 LIBGPGMEPP_LT_REVISION=0
 
 LIBQGPGME_LT_CURRENT=10
@@ -88,7 +88,7 @@ VERSION_MICRO=mym4_version_micro
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR(src/gpgme.h.in)
-AC_CONFIG_HEADER(config.h)
+AC_CONFIG_HEADER(conf/config.h)
 AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip])
 AM_MAINTAINER_MODE
 AC_CANONICAL_HOST
@@ -537,7 +537,7 @@ AM_CONDITIONAL(RUN_G13_TESTS, test "$run_g13_test" = "yes")
 
 
 # Checks for header files.
-AC_CHECK_HEADERS_ONCE([locale.h sys/select.h sys/uio.h argp.h
+AC_CHECK_HEADERS_ONCE([locale.h sys/select.h sys/uio.h argp.h stdint.h
                        unistd.h sys/time.h sys/types.h sys/stat.h])
 
 
@@ -548,6 +548,15 @@ AC_SYS_LARGEFILE
 AC_TYPE_OFF_T
 AC_TYPE_UINTPTR_T
 
+# We require uint64_t
+if test "$ac_cv_header_stdint_h" != yes; then
+   AC_MSG_ERROR([[
+***
+*** No stdint.h and thus no uint64_t type.  Can't build this library.
+***]])
+fi
+
+
 # A simple compile time check in gpgme.h for GNU/Linux systems that
 # prevents a file offset bits mismatch between gpgme and the application.
 NEED__FILE_OFFSET_BITS=0
@@ -899,6 +908,9 @@ AC_CONFIG_FILES(lang/qt/tests/Makefile)
 AC_CONFIG_FILES(lang/qt/src/qgpgme_version.h)
 AC_CONFIG_FILES([lang/Makefile lang/cl/Makefile lang/cl/gpgme.asd])
 AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([lang/qt/doc/Doxyfile])])
+AC_CONFIG_FILES([lang/js/Makefile lang/js/src/Makefile
+                 lang/js/BrowserTestExtension/Makefile
+                 lang/js/DemoExtension/Makefile])
 AC_CONFIG_FILES(lang/qt/doc/Makefile)
 AC_CONFIG_FILES([lang/python/Makefile
                 lang/python/version.py
index 905f953..2b4730e 100644 (file)
@@ -23,7 +23,8 @@ DISTCLEANFILES = gpgme.tmp
 CLEANFILES = mkdefsinc defs.inc
 
 EXTRA_DIST = module-overview.sk HACKING DCO ChangeLog-2011 \
-             mkdefsinc.c defsincdate
+             mkdefsinc.c defsincdate \
+             examples/gpgme-mozilla.json examples/gpgme-chrome.json
 
 BUILT_SOURCES = defsincdate defs.inc
 
@@ -33,9 +34,9 @@ gpgme_TEXINFOS = uiserver.texi lesser.texi gpl.texi
 
 gpgme.texi : defs.inc
 
-mkdefsinc: mkdefsinc.c Makefile ../config.h
-       $(CC_FOR_BUILD) -I. -I.. -I$(srcdir) $(AM_CPPFLAGS) \
-          -o $@ $(srcdir)/mkdefsinc.c
+mkdefsinc: mkdefsinc.c Makefile $(top_builddir)/conf/config.h
+       $(CC_FOR_BUILD) -I. -I$(top_builddir)/conf  -I$(srcdir) \
+          $(AM_CPPFLAGS) -o $@ $(srcdir)/mkdefsinc.c
 
 dist-hook: defsincdate
 
@@ -59,4 +60,3 @@ online: gpgme.html gpgme.pdf
        (cd gpgme.html && rsync -vr --exclude='.svn' .  \
          $${user}@ftp.gnupg.org:webspace/manuals/gpgme/ ); \
         rsync -v gpgme.pdf $${user}@ftp.gnupg.org:webspace/manuals/
-
index 3c1a9d1..4903d28 100644 (file)
@@ -115,7 +115,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
@@ -405,7 +405,8 @@ top_srcdir = @top_srcdir@
 DISTCLEANFILES = gpgme.tmp
 CLEANFILES = mkdefsinc defs.inc
 EXTRA_DIST = module-overview.sk HACKING DCO ChangeLog-2011 \
-             mkdefsinc.c defsincdate
+             mkdefsinc.c defsincdate \
+             examples/gpgme-mozilla.json examples/gpgme-chrome.json
 
 BUILT_SOURCES = defsincdate defs.inc
 info_TEXINFOS = gpgme.texi
@@ -854,9 +855,9 @@ uninstall-am: uninstall-dvi-am uninstall-html-am uninstall-info-am \
 
 gpgme.texi : defs.inc
 
-mkdefsinc: mkdefsinc.c Makefile ../config.h
-       $(CC_FOR_BUILD) -I. -I.. -I$(srcdir) $(AM_CPPFLAGS) \
-          -o $@ $(srcdir)/mkdefsinc.c
+mkdefsinc: mkdefsinc.c Makefile $(top_builddir)/conf/config.h
+       $(CC_FOR_BUILD) -I. -I$(top_builddir)/conf  -I$(srcdir) \
+          $(AM_CPPFLAGS) -o $@ $(srcdir)/mkdefsinc.c
 
 dist-hook: defsincdate
 
index 1012c60..af97c9b 100644 (file)
@@ -1 +1 @@
-1524207413
+1538471880
diff --git a/doc/examples/gpgme-chrome.json b/doc/examples/gpgme-chrome.json
new file mode 100644 (file)
index 0000000..d250ecb
--- /dev/null
@@ -0,0 +1,9 @@
+{
+    "name": "gpgmejson",
+    "description": "Integration with GnuPG",
+    "path": "/usr/bin/gpgme-json",
+    "type": "stdio",
+    "allowed_origins": [
+        "chrome-extension://kajibbejlbohfaggdiogboambcijhkke/"
+    ]
+}
diff --git a/doc/examples/gpgme-mozilla.json b/doc/examples/gpgme-mozilla.json
new file mode 100644 (file)
index 0000000..493b398
--- /dev/null
@@ -0,0 +1,9 @@
+{
+    "name": "gpgmejson",
+    "description": "Integration with GnuPG",
+    "path": "/usr/bin/gpgme-json",
+    "type": "stdio",
+    "allowed_extensions": [
+        "jid1-AQqSMBYb0a8ADg@jetpack"
+    ]
+}
index 9dc709a..0d611d7 100644 (file)
@@ -1,6 +1,6 @@
 This is gpgme.info, produced by makeinfo version 6.3 from gpgme.texi.
 
-Copyright © 2002–2008, 2010, 2012–2017 g10 Code GmbH.
+Copyright © 2002–2008, 2010, 2012–2018 g10 Code GmbH.
 
      Permission is granted to copy, distribute and/or modify this
      document under the terms of the GNU General Public License as
@@ -19,10 +19,10 @@ END-INFO-DIR-ENTRY
 
    This file documents the GPGME library.
 
-   This is Edition 1.11.1-beta7, last updated 20 April 2018, of ‘The
-‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.1-beta7.
+   This is Edition 1.11.2-beta291, last updated 30 September 2018, of
+‘The ‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.2-beta291.
 
-   Copyright © 2002–2008, 2010, 2012–2017 g10 Code GmbH.
+   Copyright © 2002–2008, 2010, 2012–2018 g10 Code GmbH.
 
      Permission is granted to copy, distribute and/or modify this
      document under the terms of the GNU General Public License as
@@ -37,130 +37,131 @@ Public License for more details.
 
 \1f
 Indirect:
-gpgme.info-1: 1683
-gpgme.info-2: 301966
+gpgme.info-1: 1691
+gpgme.info-2: 304536
 \1f
 Tag Table:
 (Indirect)
-Node: Top\7f1683
-Node: Introduction\7f9287
-Node: Getting Started\7f10077
-Node: Features\7f11538
-Node: Overview\7f12850
-Node: Preparation\7f13959
-Node: Header\7f14956
-Node: Building the Source\7f15703
-Node: Largefile Support (LFS)\7f17847
-Node: Using Automake\7f23263
-Node: Using Libtool\7f25816
-Node: Library Version Check\7f26178
-Node: Signal Handling\7f32238
-Node: Multi-Threading\7f33508
-Ref: Multi-Threading-Footnote-1\7f34924
-Node: Protocols and Engines\7f35347
-Node: Engine Version Check\7f38098
-Node: Engine Information\7f40621
-Node: Engine Configuration\7f44481
-Node: OpenPGP\7f45785
-Node: Cryptographic Message Syntax\7f46125
-Node: Assuan\7f46438
-Node: Algorithms\7f46812
-Ref: Algorithms-Footnote-1\7f47291
-Node: Public Key Algorithms\7f47419
-Node: Hash Algorithms\7f50021
-Node: Error Handling\7f51235
-Node: Error Values\7f53109
-Node: Error Sources\7f58312
-Node: Error Codes\7f60752
-Node: Error Strings\7f65545
-Node: Exchanging Data\7f67352
-Node: Creating Data Buffers\7f69237
-Node: Memory Based Data Buffers\7f69753
-Node: File Based Data Buffers\7f73186
-Node: Callback Based Data Buffers\7f75388
-Node: Destroying Data Buffers\7f79579
-Node: Manipulating Data Buffers\7f81086
-Node: Data Buffer I/O Operations\7f81578
-Node: Data Buffer Meta-Data\7f83951
-Node: Data Buffer Convenience\7f88470
-Node: Contexts\7f90690
-Node: Creating Contexts\7f91876
-Node: Destroying Contexts\7f92723
-Node: Result Management\7f93062
-Node: Context Attributes\7f94643
-Node: Protocol Selection\7f95680
-Node: Crypto Engine\7f96712
-Node: Setting the Sender\7f98601
-Node: ASCII Armor\7f100114
-Node: Text Mode\7f100743
-Node: Offline Mode\7f101677
-Node: Pinentry Mode\7f103175
-Node: Included Certificates\7f105069
-Node: Key Listing Mode\7f106515
-Node: Passphrase Callback\7f110973
-Node: Progress Meter Callback\7f114535
-Node: Status Message Callback\7f116520
-Node: Locale\7f122488
-Node: Key Management\7f124066
-Node: Key objects\7f125294
-Node: Listing Keys\7f139518
-Node: Information About Keys\7f148171
-Node: Manipulating Keys\7f149479
-Node: Generating Keys\7f150049
-Node: Signing Keys\7f168371
-Node: Exporting Keys\7f172004
-Node: Importing Keys\7f178811
-Ref: Importing Keys-Footnote-1\7f186214
-Node: Deleting Keys\7f186342
-Node: Changing Passphrases\7f188622
-Node: Changing TOFU Data\7f189949
-Node: Advanced Key Editing\7f192057
-Node: Trust Item Management\7f194790
-Node: Listing Trust Items\7f195826
-Node: Manipulating Trust Items\7f198187
-Node: Crypto Operations\7f198830
-Node: Decrypt\7f200094
-Node: Verify\7f206846
-Node: Decrypt and Verify\7f219310
-Node: Sign\7f222165
-Node: Selecting Signers\7f222729
-Node: Creating a Signature\7f224135
-Node: Signature Notation Data\7f228905
-Node: Encrypt\7f231190
-Node: Encrypting a Plaintext\7f231546
-Node: Miscellaneous\7f245958
-Node: Running other Programs\7f246370
-Node: Using the Assuan protocol\7f248533
-Node: Checking for updates\7f251331
-Node: Run Control\7f256148
-Node: Waiting For Completion\7f256892
-Node: Using External Event Loops\7f259010
-Node: I/O Callback Interface\7f260982
-Node: Registering I/O Callbacks\7f266222
-Node: I/O Callback Example\7f268261
-Node: I/O Callback Example GTK+\7f274886
-Node: I/O Callback Example GDK\7f276675
-Node: I/O Callback Example Qt\7f278317
-Node: Cancellation\7f280605
-Node: UI Server Protocol\7f282913
-Ref: UI Server Protocol-Footnote-1\7f284348
-Node: UI Server Encrypt\7f284467
-Node: UI Server Sign\7f289825
-Node: UI Server Decrypt\7f292178
-Node: UI Server Verify\7f293833
-Node: UI Server Set Input Files\7f297405
-Node: UI Server Sign/Encrypt Files\7f298475
-Node: UI Server Verify/Decrypt Files\7f301966
-Node: UI Server Import/Export Keys\7f303842
-Node: UI Server Checksum Files\7f304904
-Node: Miscellaneous UI Server Commands\7f307122
-Ref: command SENDER\7f309053
-Node: Debugging\7f310755
-Node: Deprecated Functions\7f312504
-Node: Library Copying\7f337721
-Node: Copying\7f365941
-Node: Concept Index\7f403691
-Node: Function and Data Index\7f418470
+Node: Top\7f1691
+Node: Introduction\7f9303
+Node: Getting Started\7f10093
+Node: Features\7f11554
+Node: Overview\7f12866
+Node: Preparation\7f13975
+Node: Header\7f14972
+Node: Building the Source\7f15719
+Node: Largefile Support (LFS)\7f17863
+Node: Using Automake\7f23279
+Node: Using Libtool\7f25832
+Node: Library Version Check\7f26194
+Node: Signal Handling\7f32254
+Node: Multi-Threading\7f33524
+Ref: Multi-Threading-Footnote-1\7f34940
+Node: Protocols and Engines\7f35363
+Node: Engine Version Check\7f38114
+Node: Engine Information\7f40637
+Node: Engine Configuration\7f44497
+Node: OpenPGP\7f45801
+Node: Cryptographic Message Syntax\7f46141
+Node: Assuan\7f46454
+Node: Algorithms\7f46828
+Ref: Algorithms-Footnote-1\7f47307
+Node: Public Key Algorithms\7f47435
+Node: Hash Algorithms\7f50037
+Node: Error Handling\7f51251
+Node: Error Values\7f53125
+Node: Error Sources\7f58328
+Node: Error Codes\7f60768
+Node: Error Strings\7f65561
+Node: Exchanging Data\7f67368
+Node: Creating Data Buffers\7f69253
+Node: Memory Based Data Buffers\7f69769
+Node: File Based Data Buffers\7f73202
+Node: Callback Based Data Buffers\7f76315
+Node: Destroying Data Buffers\7f80506
+Node: Manipulating Data Buffers\7f82013
+Node: Data Buffer I/O Operations\7f82505
+Node: Data Buffer Meta-Data\7f84878
+Node: Data Buffer Convenience\7f89397
+Node: Contexts\7f91617
+Node: Creating Contexts\7f92803
+Node: Destroying Contexts\7f93650
+Node: Result Management\7f93989
+Node: Context Attributes\7f95570
+Node: Protocol Selection\7f96671
+Node: Crypto Engine\7f97703
+Node: Setting the Sender\7f99592
+Node: ASCII Armor\7f101105
+Node: Text Mode\7f101734
+Node: Offline Mode\7f102668
+Node: Pinentry Mode\7f104166
+Node: Included Certificates\7f106060
+Node: Key Listing Mode\7f107506
+Node: Passphrase Callback\7f112247
+Node: Progress Meter Callback\7f115809
+Node: Status Message Callback\7f117794
+Node: Locale\7f124758
+Node: Additional Logs\7f126360
+Node: Key Management\7f128558
+Node: Key objects\7f129786
+Node: Listing Keys\7f144010
+Node: Information About Keys\7f152663
+Node: Manipulating Keys\7f153971
+Node: Generating Keys\7f154541
+Node: Signing Keys\7f172863
+Node: Exporting Keys\7f176496
+Node: Importing Keys\7f183435
+Ref: Importing Keys-Footnote-1\7f190838
+Node: Deleting Keys\7f190966
+Node: Changing Passphrases\7f193246
+Node: Changing TOFU Data\7f194573
+Node: Advanced Key Editing\7f196681
+Node: Trust Item Management\7f199414
+Node: Listing Trust Items\7f200450
+Node: Manipulating Trust Items\7f202811
+Node: Crypto Operations\7f203454
+Node: Decrypt\7f204718
+Node: Verify\7f212031
+Node: Decrypt and Verify\7f224750
+Node: Sign\7f227605
+Node: Selecting Signers\7f228169
+Node: Creating a Signature\7f229575
+Node: Signature Notation Data\7f234345
+Node: Encrypt\7f236630
+Node: Encrypting a Plaintext\7f236986
+Node: Miscellaneous\7f251398
+Node: Running other Programs\7f251810
+Node: Using the Assuan protocol\7f253973
+Node: Checking for updates\7f256771
+Node: Run Control\7f261588
+Node: Waiting For Completion\7f262332
+Node: Using External Event Loops\7f264450
+Node: I/O Callback Interface\7f266422
+Node: Registering I/O Callbacks\7f271662
+Node: I/O Callback Example\7f273701
+Node: I/O Callback Example GTK+\7f280326
+Node: I/O Callback Example GDK\7f282115
+Node: I/O Callback Example Qt\7f283757
+Node: Cancellation\7f286045
+Node: UI Server Protocol\7f288353
+Ref: UI Server Protocol-Footnote-1\7f289788
+Node: UI Server Encrypt\7f289907
+Node: UI Server Sign\7f295265
+Node: UI Server Decrypt\7f297618
+Node: UI Server Verify\7f299273
+Node: UI Server Set Input Files\7f304536
+Node: UI Server Sign/Encrypt Files\7f305606
+Node: UI Server Verify/Decrypt Files\7f307414
+Node: UI Server Import/Export Keys\7f309290
+Node: UI Server Checksum Files\7f310352
+Node: Miscellaneous UI Server Commands\7f312570
+Ref: command SENDER\7f314501
+Node: Debugging\7f316203
+Node: Deprecated Functions\7f317952
+Node: Library Copying\7f343169
+Node: Copying\7f371389
+Node: Concept Index\7f409139
+Node: Function and Data Index\7f424064
 \1f
 End Tag Table
 
index af6d6b4..4851eeb 100644 (file)
@@ -1,6 +1,6 @@
 This is gpgme.info, produced by makeinfo version 6.3 from gpgme.texi.
 
-Copyright © 2002–2008, 2010, 2012–2017 g10 Code GmbH.
+Copyright © 2002–2008, 2010, 2012–2018 g10 Code GmbH.
 
      Permission is granted to copy, distribute and/or modify this
      document under the terms of the GNU General Public License as
@@ -19,10 +19,10 @@ END-INFO-DIR-ENTRY
 
    This file documents the GPGME library.
 
-   This is Edition 1.11.1-beta7, last updated 20 April 2018, of ‘The
-‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.1-beta7.
+   This is Edition 1.11.2-beta291, last updated 30 September 2018, of
+‘The ‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.2-beta291.
 
-   Copyright © 2002–2008, 2010, 2012–2017 g10 Code GmbH.
+   Copyright © 2002–2008, 2010, 2012–2018 g10 Code GmbH.
 
      Permission is granted to copy, distribute and/or modify this
      document under the terms of the GNU General Public License as
@@ -41,9 +41,9 @@ File: gpgme.info,  Node: Top,  Next: Introduction,  Up: (dir)
 Main Menu
 *********
 
-This is Edition 1.11.1-beta7, last updated 20 April 2018, of ‘The ‘GnuPG
-Made Easy’ Reference Manual’, for Version 1.11.1-beta7 of the GPGME
-library.
+This is Edition 1.11.2-beta291, last updated 30 September 2018, of ‘The
+‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.2-beta291 of the
+GPGME library.
 
 * Menu:
 
@@ -1780,6 +1780,26 @@ of the data objects is not limited by GPGME.
      object was successfully created, and ‘GPG_ERR_ENOMEM’ if not enough
      memory is available.
 
+ -- Function: gpgme_error_t gpgme_data_new_from_estream
+          (gpgme_data_t *DH, gpgrt_stream_t STREAM)
+     The function ‘gpgme_data_new_from_estream’ creates a new
+     ‘gpgme_data_t’ object and uses the gpgrt stream STREAM to read from
+     (if used as an input data object) and write to (if used as an
+     output data object).
+
+     When using the data object as an input buffer, the function might
+     read a bit more from the stream than is actually needed by the
+     crypto engine in the desired operation because of internal
+     buffering.
+
+     Note that GPGME assumes that the stream is in blocking mode.
+     Errors during I/O operations, except for EINTR, are usually fatal
+     for crypto operations.
+
+     The function returns the error code ‘GPG_ERR_NO_ERROR’ if the data
+     object was successfully created, and ‘GPG_ERR_ENOMEM’ if not enough
+     memory is available.
+
 \1f
 File: gpgme.info,  Node: Callback Based Data Buffers,  Prev: File Based Data Buffers,  Up: Creating Data Buffers
 
@@ -2260,6 +2280,7 @@ File: gpgme.info,  Node: Context Attributes,  Next: Key Management,  Prev: Resul
 * Progress Meter Callback::       Being informed about the progress.
 * Status Message Callback::       Status messages received from gpg.
 * Locale::                        Setting the locale of a context.
+* Additional Logs::               Additional logs of a context.
 
 \1f
 File: gpgme.info,  Node: Protocol Selection,  Next: Crypto Engine,  Up: Context Attributes
@@ -2569,6 +2590,8 @@ File: gpgme.info,  Node: Key Listing Mode,  Next: Passphrase Callback,  Prev: In
           keyring should be searched for keys in the keylisting
           operation.  This is the default.
 
+          Using only this option results in a ‘--list-keys’.
+
      ‘GPGME_KEYLIST_MODE_EXTERN’
           The ‘GPGME_KEYLIST_MODE_EXTERN’ symbol specifies that an
           external source should be searched for keys in the keylisting
@@ -2577,10 +2600,15 @@ File: gpgme.info,  Node: Key Listing Mode,  Next: Passphrase Callback,  Prev: In
           ‘GPGME_KEYLIST_MODE_LOCAL’.  For example, it can be a remote
           keyserver or LDAP certificate server.
 
+          Using only this option results in a ‘--search-keys’ for
+          ‘GPGME_PROTOCOL_OpenPGP’ and something similar to
+          ‘--list-external-keys’ for ‘GPGME_PROTOCOL_CMS’.
+
      ‘GPGME_KEYLIST_MODE_LOCATE’
           This is a shortcut for the combination of
-          ‘GPGME_KEYLIST_MODE_LOCAL’ and ‘GPGME_KEYLIST_MODE_EXTERN’ and
-          convenient when the –locate-key feature of OpenPGP is desired.
+          ‘GPGME_KEYLIST_MODE_LOCAL’ and ‘GPGME_KEYLIST_MODE_EXTERN’,
+          which results in a ‘--locate-keys’ for
+          ‘GPGME_PROTOCOL_OpenPGP’.
 
      ‘GPGME_KEYLIST_MODE_SIGS’
           The ‘GPGME_KEYLIST_MODE_SIGS’ symbol specifies that the key
@@ -2876,7 +2904,7 @@ File: gpgme.info,  Node: Status Message Callback,  Next: Locale,  Prev: Progress
           The string given in VALUE is passed to the GnuPG engines to
           request restrictions based on the origin of the request.
           Valid values are documented in the GnuPG manual and the gpg
-          man page under the option â\80\9câ\80\93request-originâ\80\9d.  Requires at
+          man page under the option â\80\98--request-originâ\80\99.  Requires at
           least GnuPG 2.2.6 to have an effect.
 
      ‘"no-symkey-cache"’
@@ -2885,6 +2913,25 @@ File: gpgme.info,  Node: Status Message Callback,  Next: Locale,  Prev: Progress
           specific salt value.  Requires at least GnuPG 2.2.7 to have an
           effect.
 
+     ‘"ignore-mdc-error"’
+          This flag passes the option ‘--ignore-mdc-error’ to gpg.  This
+          can be used to force decryption of a message which failed due
+          to a missing integrity check.  This flag must be used with
+          great caution and only if it is a known non-corrupted old
+          message and the decryption result of the former try had the
+          decryption result flag ‘legacy_cipher_nomdc’ set.  For
+          failsafe reasons this flag is reset after each operation.
+
+     ‘"auto-key-locate"’
+          The string given in VALUE is passed to gpg.  This can be used
+          to change the behavior of a ‘GPGME_KEYLIST_MODE_LOCATE’
+          keylisting.  Valid values are documented in the GnuPG manual
+          and the gpg man page under the option ‘--auto-key-locate’.
+          Requires at least GnuPG 2.1.18.
+
+          Note: Keys retrieved through ‘auto-key-locate’ are
+          automatically imported in the keyring.
+
      This function returns ‘0’ on success.
 
  -- Function: const char * gpgme_get_ctx_flag (gpgme_ctx_t CTX,
@@ -2899,7 +2946,7 @@ File: gpgme.info,  Node: Status Message Callback,  Next: Locale,  Prev: Progress
      test for an empty string can be used to get the boolean value.
 
 \1f
-File: gpgme.info,  Node: Locale,  Prev: Status Message Callback,  Up: Context Attributes
+File: gpgme.info,  Node: Locale,  Next: Additional Logs,  Prev: Status Message Callback,  Up: Context Attributes
 
 7.4.13 Locale
 -------------
@@ -2938,6 +2985,66 @@ contexts created afterwards.
      The function returns an error if not enough memory is available.
 
 \1f
+File: gpgme.info,  Node: Additional Logs,  Prev: Locale,  Up: Context Attributes
+
+7.4.14 Additional Logs
+----------------------
+
+Additional logs can be associated with a context.  These logs are engine
+specific and can be be obtained with ‘gpgme_op_getauditlog’.
+
+ -- Function: gpgme_error_t gpgme_op_getauditlog (gpgme_ctx_t CTX,
+          gpgme_data_t OUTPUT, unsigned int FLAGS)
+     SINCE: 1.1.1
+
+     The function ‘gpgme_op_getauditlog’ is used to obtain additional
+     logs as specified by FLAGS into the OUTPUT data.  If
+
+     The function returns the error code ‘GPG_ERR_NO_ERROR’ if a log
+     could be queried from the engine, and ‘GPG_ERR_NOT_IMPLEMENTED’ if
+     the log specified in FLAGS is not available for this engine.  If no
+     log is available ‘GPG_ERR_NO_DATA’ is returned.
+
+     The value in FLAGS is a bitwise-or combination of one or multiple
+     of the following bit values:
+
+     ‘GPGME_AUDITLOG_DIAG’
+          SINCE: 1.11.2
+
+          Obtain diagnostic output which would be written to ‘stderr’ in
+          interactive use of the engine.  This can be used to provide
+          additional diagnostic information in case of errors in other
+          operations.
+
+          Note: If log-file has been set in the configuration the log
+          will be empty and ‘GPG_ERR_NO_DATA’ will be returned.
+
+          Implemented for: ‘GPGME_PROTOCOL_OpenPGP’
+
+     ‘GPGME_AUDITLOG_DEFAULT’
+          SINCE: 1.11.2
+
+          This flag has the value 0 for compatibility reasons.  Obtains
+          additional information from the engine by issuing the
+          ‘GETAUDITLOG’ command.  For ‘GPGME_PROTOCOL_CMS’ this provides
+          additional information about the X509 certificate chain.
+
+          Implemented for: ‘GPGME_PROTOCOL_CMS’
+
+     ‘GPGME_AUDITLOG_HTML’
+          SINCE: 1.1.1
+
+          Same as ‘GPGME_AUDITLOG_DEFAULT’ but in HTML.
+
+          Implemented for: ‘GPGME_PROTOCOL_CMS’
+
+ -- Function: gpgme_error_t gpgme_op_getauditlog_start (gpgme_ctx_t CTX,
+          gpgme_data_t OUTPUT, unsigned int FLAGS)
+     SINCE: 1.1.1
+
+     This is the asynchronous variant of ‘gpgme_op_getauditlog’.
+
+\1f
 File: gpgme.info,  Node: Key Management,  Next: Trust Item Management,  Prev: Context Attributes,  Up: Contexts
 
 7.5 Key Management
@@ -4178,6 +4285,10 @@ or-ed together.
      key the export format will be changed to PKCS#12 which also
      includes the certificate.  This flag may not be used with OpenPGP.
 
+‘GPGME_EXPORT_MODE_NOUID’
+     SINCE: 1.12.0 - experimental Do not export user ids.  Works only
+     with certain gpg version.
+
  -- Function: gpgme_error_t gpgme_op_export (gpgme_ctx_t CTX,
           const char *PATTERN, gpgme_export_mode_t MODE,
           gpgme_data_t KEYDATA)
@@ -4927,7 +5038,7 @@ File: gpgme.info,  Node: Decrypt,  Next: Verify,  Up: Crypto Operations
      you can retrieve the pointer to the result with
      ‘gpgme_op_decrypt_result’.  As with all result structures, it this
      structure shall be considered read-only and an application must not
-     allocated such a strucure on its own.  The structure contains the
+     allocate such a strucure on its own.  The structure contains the
      following members:
 
      ‘char *unsupported_algorithm’
@@ -4935,9 +5046,22 @@ File: gpgme.info,  Node: Decrypt,  Next: Verify,  Up: Crypto Operations
           describes the algorithm that is not supported.
 
      ‘unsigned int wrong_key_usage : 1’
-          SINCE: 0.9.0
+          SINCE: 0.9.0 This is true if the key was not used according to
+          its policy.
 
-          This is true if the key was not used according to its policy.
+     ‘unsigned int legacy_cipher_nomdc : 1’
+          SINCE: 1.11.2 The message was made by a legacy algorithm
+          without any integrity protection.  This might be an old but
+          legitimate message.
+
+     ‘unsigned int is_mime : 1;’
+          SINCE: 1.11.0 The message claims that the content is a MIME
+          object.
+
+     ‘unsigned int is_de_vs : 1;’
+          SINCE: 1.10.0 The message was encrypted in a VS-NfD compliant
+          way.  This is a specification in Germany for a restricted
+          communication level.
 
      ‘gpgme_recipient_t recipients’
           SINCE: 1.1.0
@@ -4968,8 +5092,8 @@ File: gpgme.info,  Node: Decrypt,  Next: Verify,  Up: Crypto Operations
           SINCE: 1.11.0
 
           A string with the symmetric encryption algorithm and mode
-          using the format "<algo>.<mode>".  Note that old non-MDC
-          encryption mode of OpenPGP is given as "PGPCFB".
+          using the format "<algo>.<mode>".  Note that the deprecated
+          non-MDC encryption mode of OpenPGP is given as "PGPCFB".
 
  -- Function: gpgme_decrypt_result_t gpgme_op_decrypt_result
           (gpgme_ctx_t CTX)
@@ -5262,7 +5386,14 @@ File: gpgme.info,  Node: Verify,  Next: Decrypt and Verify,  Prev: Decrypt,  Up:
 
      ‘char *file_name’
           This is the filename of the original plaintext message file if
-          it is known, otherwise this is a null pointer.
+          it is known, otherwise this is a null pointer.  Warning: The
+          filename is not covered by the signature.
+
+     ‘unsigned int is_mime : 1;’
+          SINCE: 1.11.0
+
+          The message claims that the content is a MIME object.
+          Warning: This flag is not covered by the signature.
 
  -- Function: gpgme_verify_result_t gpgme_op_verify_result
           (gpgme_ctx_t CTX)
@@ -7203,61 +7334,3 @@ before the final OK response:
      The server shall send one status line for every signature found on
      the message.
 
-\1f
-File: gpgme.info,  Node: UI Server Set Input Files,  Next: UI Server Sign/Encrypt Files,  Prev: UI Server Verify,  Up: UI Server Protocol
-
-A.5 UI Server: Specifying the input files to operate on.
-========================================================
-
-All file related UI server commands operate on a number of input files
-or directories, specified by one or more ‘FILE’ commands:
-
- -- Command: FILE [--clear] NAME
-     Add the file or directory NAME to the list of pathnames to be
-     processed by the server.  The parameter NAME must be an absolute
-     path name (including the drive letter) and is percent espaced (in
-     particular, the characters %, = and white space characters are
-     always escaped).  If the option ‘--clear’ is given, the list of
-     files is cleared before adding NAME.
-
-     Historical note: The original spec did not define ‘--clear’ but the
-     keyword ‘--continued’ after the file name to indicate that more
-     files are to be expected.  However, this has never been used and
-     thus removed from the specs.
-
-\1f
-File: gpgme.info,  Node: UI Server Sign/Encrypt Files,  Next: UI Server Verify/Decrypt Files,  Prev: UI Server Set Input Files,  Up: UI Server Protocol
-
-A.6 UI Server: Encrypting and signing files.
-============================================
-
-First, the input files need to be specified by one or more ‘FILE’
-commands.  Afterwards, the actual operation is requested:
-
- -- Command: ENCRYPT_FILES --nohup
- -- Command: SIGN_FILES --nohup
- -- Command: ENCRYPT_SIGN_FILES --nohup
-     Request that the files specified by ‘FILE’ are encrypted and/or
-     signed.  The command selects the default action.  The UI server may
-     allow the user to change this default afterwards interactively, and
-     even abort the operation or complete it only on some of the
-     selected files and directories.
-
-     What it means to encrypt or sign a file or directory is specific to
-     the preferences of the user, the functionality the UI server
-     provides, and the selected protocol.  Typically, for each input
-     file a new file is created under the original filename plus a
-     protocol specific extension (like ‘.gpg’ or ‘.sig’), which contain
-     the encrypted/signed file or a detached signature.  For
-     directories, the server may offer multiple options to the user (for
-     example ignore or process recursively).
-
-     The ‘ENCRYPT_SIGN_FILES’ command requests a combined sign and
-     encrypt operation.  It may not be available for all protocols (for
-     example, it is available for OpenPGP but not for CMS).
-
-     The option ‘--nohup’ is mandatory.  It is currently unspecified
-     what should happen if ‘--nohup’ is not present.  Because ‘--nohup’
-     is present, the server always returns ‘OK’ promptly, and completes
-     the operation asynchronously.
-
index 541764b..90274af 100644 (file)
@@ -1,6 +1,6 @@
 This is gpgme.info, produced by makeinfo version 6.3 from gpgme.texi.
 
-Copyright © 2002–2008, 2010, 2012–2017 g10 Code GmbH.
+Copyright © 2002–2008, 2010, 2012–2018 g10 Code GmbH.
 
      Permission is granted to copy, distribute and/or modify this
      document under the terms of the GNU General Public License as
@@ -19,10 +19,10 @@ END-INFO-DIR-ENTRY
 
    This file documents the GPGME library.
 
-   This is Edition 1.11.1-beta7, last updated 20 April 2018, of ‘The
-‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.1-beta7.
+   This is Edition 1.11.2-beta291, last updated 30 September 2018, of
+‘The ‘GnuPG Made Easy’ Reference Manual’, for Version 1.11.2-beta291.
 
-   Copyright © 2002–2008, 2010, 2012–2017 g10 Code GmbH.
+   Copyright © 2002–2008, 2010, 2012–2018 g10 Code GmbH.
 
      Permission is granted to copy, distribute and/or modify this
      document under the terms of the GNU General Public License as
@@ -36,6 +36,64 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 Public License for more details.
 
 \1f
+File: gpgme.info,  Node: UI Server Set Input Files,  Next: UI Server Sign/Encrypt Files,  Prev: UI Server Verify,  Up: UI Server Protocol
+
+A.5 UI Server: Specifying the input files to operate on.
+========================================================
+
+All file related UI server commands operate on a number of input files
+or directories, specified by one or more ‘FILE’ commands:
+
+ -- Command: FILE [--clear] NAME
+     Add the file or directory NAME to the list of pathnames to be
+     processed by the server.  The parameter NAME must be an absolute
+     path name (including the drive letter) and is percent espaced (in
+     particular, the characters %, = and white space characters are
+     always escaped).  If the option ‘--clear’ is given, the list of
+     files is cleared before adding NAME.
+
+     Historical note: The original spec did not define ‘--clear’ but the
+     keyword ‘--continued’ after the file name to indicate that more
+     files are to be expected.  However, this has never been used and
+     thus removed from the specs.
+
+\1f
+File: gpgme.info,  Node: UI Server Sign/Encrypt Files,  Next: UI Server Verify/Decrypt Files,  Prev: UI Server Set Input Files,  Up: UI Server Protocol
+
+A.6 UI Server: Encrypting and signing files.
+============================================
+
+First, the input files need to be specified by one or more ‘FILE’
+commands.  Afterwards, the actual operation is requested:
+
+ -- Command: ENCRYPT_FILES --nohup
+ -- Command: SIGN_FILES --nohup
+ -- Command: ENCRYPT_SIGN_FILES --nohup
+     Request that the files specified by ‘FILE’ are encrypted and/or
+     signed.  The command selects the default action.  The UI server may
+     allow the user to change this default afterwards interactively, and
+     even abort the operation or complete it only on some of the
+     selected files and directories.
+
+     What it means to encrypt or sign a file or directory is specific to
+     the preferences of the user, the functionality the UI server
+     provides, and the selected protocol.  Typically, for each input
+     file a new file is created under the original filename plus a
+     protocol specific extension (like ‘.gpg’ or ‘.sig’), which contain
+     the encrypted/signed file or a detached signature.  For
+     directories, the server may offer multiple options to the user (for
+     example ignore or process recursively).
+
+     The ‘ENCRYPT_SIGN_FILES’ command requests a combined sign and
+     encrypt operation.  It may not be available for all protocols (for
+     example, it is available for OpenPGP but not for CMS).
+
+     The option ‘--nohup’ is mandatory.  It is currently unspecified
+     what should happen if ‘--nohup’ is not present.  Because ‘--nohup’
+     is present, the server always returns ‘OK’ promptly, and completes
+     the operation asynchronously.
+
+\1f
 File: gpgme.info,  Node: UI Server Verify/Decrypt Files,  Next: UI Server Import/Export Keys,  Prev: UI Server Sign/Encrypt Files,  Up: UI Server Protocol
 
 A.7 UI Server: Decrypting and verifying files.
@@ -2164,6 +2222,8 @@ Concept Index
 * ASSUAN:                                Assuan.                (line 6)
 * attributes, of a key:                  Information About Keys.
                                                                 (line 6)
+* auditlog:                              Additional Logs.       (line 6)
+* auditlog, of the engine:               Additional Logs.       (line 6)
 * autoconf:                              Using Automake.        (line 6)
 * automake:                              Using Automake.        (line 6)
 * backend:                               Protocols and Engines. (line 6)
@@ -2429,6 +2489,8 @@ Function and Data Index
                                                               (line  12)
 * gpgme_data_new_from_cbs:               Callback Based Data Buffers.
                                                               (line  80)
+* gpgme_data_new_from_estream:           File Based Data Buffers.
+                                                              (line  49)
 * gpgme_data_new_from_fd:                File Based Data Buffers.
                                                               (line  10)
 * gpgme_data_new_from_file:              Memory Based Data Buffers.
@@ -2516,7 +2578,7 @@ Function and Data Index
 * gpgme_genkey_result_t:                 Generating Keys.     (line 381)
 * gpgme_get_armor:                       ASCII Armor.         (line  13)
 * gpgme_get_ctx_flag:                    Status Message Callback.
-                                                              (line 122)
+                                                              (line 141)
 * gpgme_get_dirinfo:                     Engine Version Check.
                                                               (line   6)
 * gpgme_get_engine_info:                 Engine Information.  (line  46)
@@ -2525,7 +2587,7 @@ Function and Data Index
 * gpgme_get_io_cbs:                      Registering I/O Callbacks.
                                                               (line  44)
 * gpgme_get_key:                         Listing Keys.        (line 178)
-* gpgme_get_keylist_mode:                Key Listing Mode.    (line  88)
+* gpgme_get_keylist_mode:                Key Listing Mode.    (line  95)
 * gpgme_get_offline:                     Offline Mode.        (line  31)
 * gpgme_get_passphrase_cb:               Passphrase Callback. (line  63)
 * gpgme_get_pinentry_mode:               Pinentry Mode.       (line  18)
@@ -2591,7 +2653,7 @@ Function and Data Index
 * gpgme_op_decrypt:                      Decrypt.             (line   6)
 * gpgme_op_decrypt_ext:                  Decrypt.             (line  30)
 * gpgme_op_decrypt_ext_start:            Decrypt.             (line  60)
-* gpgme_op_decrypt_result:               Decrypt.             (line 151)
+* gpgme_op_decrypt_result:               Decrypt.             (line 164)
 * gpgme_op_decrypt_start:                Decrypt.             (line  20)
 * gpgme_op_decrypt_verify:               Decrypt and Verify.  (line   6)
 * gpgme_op_decrypt_verify_start:         Decrypt and Verify.  (line  30)
@@ -2621,15 +2683,17 @@ Function and Data Index
                                                               (line 274)
 * gpgme_op_encrypt_start:                Encrypting a Plaintext.
                                                               (line 113)
-* gpgme_op_export:                       Exporting Keys.      (line  46)
-* gpgme_op_export_ext:                   Exporting Keys.      (line  79)
-* gpgme_op_export_ext_start:             Exporting Keys.      (line 101)
-* gpgme_op_export_keys:                  Exporting Keys.      (line 113)
-* gpgme_op_export_keys_start:            Exporting Keys.      (line 140)
-* gpgme_op_export_start:                 Exporting Keys.      (line  67)
+* gpgme_op_export:                       Exporting Keys.      (line  50)
+* gpgme_op_export_ext:                   Exporting Keys.      (line  83)
+* gpgme_op_export_ext_start:             Exporting Keys.      (line 105)
+* gpgme_op_export_keys:                  Exporting Keys.      (line 117)
+* gpgme_op_export_keys_start:            Exporting Keys.      (line 144)
+* gpgme_op_export_start:                 Exporting Keys.      (line  71)
 * gpgme_op_genkey:                       Generating Keys.     (line 304)
 * gpgme_op_genkey_result:                Generating Keys.     (line 418)
 * gpgme_op_genkey_start:                 Generating Keys.     (line 369)
+* gpgme_op_getauditlog:                  Additional Logs.     (line   9)
+* gpgme_op_getauditlog_start:            Additional Logs.     (line  54)
 * gpgme_op_import:                       Importing Keys.      (line   9)
 * gpgme_op_import_ext:                   Deprecated Functions.
                                                               (line  22)
@@ -2677,7 +2741,7 @@ Function and Data Index
 * gpgme_op_trustlist_next:               Listing Trust Items. (line  27)
 * gpgme_op_trustlist_start:              Listing Trust Items. (line   6)
 * gpgme_op_verify:                       Verify.              (line   6)
-* gpgme_op_verify_result:                Verify.              (line 283)
+* gpgme_op_verify_result:                Verify.              (line 290)
 * gpgme_op_verify_start:                 Verify.              (line  26)
 * gpgme_passphrase_cb_t:                 Passphrase Callback. (line  10)
 * gpgme_pinentry_mode_t:                 Pinentry Mode.       (line  26)
index c4a29d5..d99e9bb 100644 (file)
@@ -14,7 +14,7 @@
 @syncodeindex pg fn
 
 @copying
-Copyright @copyright{} 2002--2008, 2010, 2012--2017 g10 Code GmbH.
+Copyright @copyright{} 2002--2008, 2010, 2012--2018 g10 Code GmbH.
 
 @quotation
 Permission is granted to copy, distribute and/or modify this document
@@ -1909,6 +1909,25 @@ data object was successfully created, and @code{GPG_ERR_ENOMEM} if not
 enough memory is available.
 @end deftypefun
 
+@deftypefun gpgme_error_t gpgme_data_new_from_estream (@w{gpgme_data_t *@var{dh}}, @w{gpgrt_stream_t @var{stream}})
+The function @code{gpgme_data_new_from_estream} creates a new
+@code{gpgme_data_t} object and uses the gpgrt stream @var{stream} to read
+from (if used as an input data object) and write to (if used as an
+output data object).
+
+When using the data object as an input buffer, the function might read
+a bit more from the stream than is actually needed by the crypto
+engine in the desired operation because of internal buffering.
+
+Note that GPGME assumes that the stream is in blocking mode.  Errors
+during I/O operations, except for EINTR, are usually fatal for crypto
+operations.
+
+The function returns the error code @code{GPG_ERR_NO_ERROR} if the
+data object was successfully created, and @code{GPG_ERR_ENOMEM} if not
+enough memory is available.
+@end deftypefun
+
 
 @node Callback Based Data Buffers
 @subsection Callback Based Data Buffers
@@ -2426,6 +2445,7 @@ started.  In fact, these references are accessed through the
 * Progress Meter Callback::       Being informed about the progress.
 * Status Message Callback::       Status messages received from gpg.
 * Locale::                        Setting the locale of a context.
+* Additional Logs::               Additional logs of a context.
 @end menu
 
 
@@ -2762,6 +2782,8 @@ The @code{GPGME_KEYLIST_MODE_LOCAL} symbol specifies that the local
 keyring should be searched for keys in the keylisting operation.  This
 is the default.
 
+Using only this option results in a @code{--list-keys}.
+
 @item GPGME_KEYLIST_MODE_EXTERN
 The @code{GPGME_KEYLIST_MODE_EXTERN} symbol specifies that an external
 source should be searched for keys in the keylisting operation.  The
@@ -2769,10 +2791,14 @@ type of external source is dependent on the crypto engine used and
 whether it is combined with @code{GPGME_KEYLIST_MODE_LOCAL}.  For
 example, it can be a remote keyserver or LDAP certificate server.
 
+Using only this option results in a @code{--search-keys} for
+@code{GPGME_PROTOCOL_OpenPGP} and something similar to
+@code{--list-external-keys} for @code{GPGME_PROTOCOL_CMS}.
+
 @item GPGME_KEYLIST_MODE_LOCATE
 This is a shortcut for the combination of
-@code{GPGME_KEYLIST_MODE_LOCAL} and @code{GPGME_KEYLIST_MODE_EXTERN}
-and convenient when the --locate-key feature of OpenPGP is desired.
+@code{GPGME_KEYLIST_MODE_LOCAL} and @code{GPGME_KEYLIST_MODE_EXTERN}, which
+results in a @code{--locate-keys} for @code{GPGME_PROTOCOL_OpenPGP}.
 
 @item GPGME_KEYLIST_MODE_SIGS
 The @code{GPGME_KEYLIST_MODE_SIGS} symbol specifies that the key
@@ -3078,7 +3104,7 @@ the time when you verified the signature.
 The string given in @var{value} is passed to the GnuPG engines to
 request restrictions based on the origin of the request.  Valid values
 are documented in the GnuPG manual and the gpg man page under the
-option ``--request-origin''.  Requires at least GnuPG 2.2.6 to have an
+option @option{--request-origin}.  Requires at least GnuPG 2.2.6 to have an
 effect.
 
 @item "no-symkey-cache"
@@ -3086,6 +3112,25 @@ For OpenPGP disable the passphrase cache used for symmetrical en- and
 decryption.  This cache is based on the message specific salt value.
 Requires at least GnuPG 2.2.7 to have an effect.
 
+@item "ignore-mdc-error"
+This flag passes the option @option{--ignore-mdc-error} to gpg.  This
+can be used to force decryption of a message which failed due to a
+missing integrity check.  This flag must be used with great caution
+and only if it is a known non-corrupted old message and the decryption
+result of the former try had the decryption result flag
+@code{legacy_cipher_nomdc} set.  For failsafe reasons this flag is
+reset after each operation.
+
+@item "auto-key-locate"
+The string given in @var{value} is passed to gpg.  This can be used
+to change the behavior of a @code{GPGME_KEYLIST_MODE_LOCATE} keylisting.
+Valid values are documented in the GnuPG manual and the gpg man page under
+the option @option{--auto-key-locate}.
+Requires at least GnuPG 2.1.18.
+
+Note: Keys retrieved through @code{auto-key-locate} are automatically
+imported in the keyring.
+
 @end table
 
 This function returns @code{0} on success.
@@ -3146,6 +3191,70 @@ The function returns an error if not enough memory is available.
 @end deftypefun
 
 
+@node Additional Logs
+@subsection Additional Logs
+@cindex auditlog, of the engine
+@cindex auditlog
+
+Additional logs can be associated with a context.  These logs are
+engine specific and can be be obtained with @code{gpgme_op_getauditlog}.
+
+@deftypefun gpgme_error_t gpgme_op_getauditlog @
+            (@w{gpgme_ctx_t @var{ctx}}, @w{gpgme_data_t @var{output}}, @
+            @w{unsigned int @var{flags}})
+@since{1.1.1}
+
+The function @code{gpgme_op_getauditlog} is used to obtain additional
+logs as specified by @var{flags} into the @var{output} data.  If
+
+The function returns the error code @code{GPG_ERR_NO_ERROR} if a
+log could be queried from the engine, and @code{GPG_ERR_NOT_IMPLEMENTED}
+if the log specified in @var{flags} is not available for this engine.
+If no log is available @code{GPG_ERR_NO_DATA} is returned.
+
+The value in @var{flags} is a bitwise-or combination of one or
+multiple of the following bit values:
+
+@table @code
+@item GPGME_AUDITLOG_DIAG
+@since{1.11.2}
+
+Obtain diagnostic output which would be written to @code{stderr} in
+interactive use of the engine.  This can be used to provide additional
+diagnostic information in case of errors in other operations.
+
+Note: If log-file has been set in the configuration the log will
+be empty and @code{GPG_ERR_NO_DATA} will be returned.
+
+Implemented for: @code{GPGME_PROTOCOL_OpenPGP}
+
+@item GPGME_AUDITLOG_DEFAULT
+@since{1.11.2}
+
+This flag has the value 0 for compatibility reasons.  Obtains additional
+information from the engine by issuing the @code{GETAUDITLOG} command.
+For @code{GPGME_PROTOCOL_CMS} this provides additional information about
+the X509 certificate chain.
+
+Implemented for: @code{GPGME_PROTOCOL_CMS}
+
+@item GPGME_AUDITLOG_HTML
+@since{1.1.1}
+
+Same as @code{GPGME_AUDITLOG_DEFAULT} but in HTML.
+
+Implemented for: @code{GPGME_PROTOCOL_CMS}
+@end table
+@end deftypefun
+
+@deftypefun gpgme_error_t gpgme_op_getauditlog_start @
+            (@w{gpgme_ctx_t @var{ctx}}, @w{gpgme_data_t @var{output}}, @
+            @w{unsigned int @var{flags}})
+@since{1.1.1}
+
+This is the asynchronous variant of @code{gpgme_op_getauditlog}.
+@end deftypefun
+
 @node Key Management
 @section Key Management
 @cindex key management
@@ -4554,6 +4663,10 @@ If this flag is used with @code{GPGME_EXPORT_MODE_SECRET} for an X.509
 key the export format will be changed to PKCS#12 which also includes
 the certificate.  This flag may not be used with OpenPGP.
 
+@item GPGME_EXPORT_MODE_NOUID
+@since{1.12.0 - experimental}
+Do not export user ids.  Works only with certain gpg version.
+
 @end table
 
 
@@ -5368,7 +5481,7 @@ This is a pointer to a structure used to store the result of a
 data, you can retrieve the pointer to the result with
 @code{gpgme_op_decrypt_result}.  As with all result structures, it
 this structure shall be considered read-only and an application must
-not allocated such a strucure on its own.  The structure contains the
+not allocate such a strucure on its own.  The structure contains the
 following members:
 
 @table @code
@@ -5378,9 +5491,22 @@ algorithm that is not supported.
 
 @item unsigned int wrong_key_usage : 1
 @since{0.9.0}
-
 This is true if the key was not used according to its policy.
 
+@item unsigned int legacy_cipher_nomdc : 1
+@since{1.11.2}
+The message was made by a legacy algorithm without any integrity
+protection.  This might be an old but legitimate message.
+
+@item unsigned int is_mime : 1;
+@since{1.11.0}
+The message claims that the content is a MIME object.
+
+@item unsigned int is_de_vs : 1;
+@since{1.10.0}
+The message was encrypted in a VS-NfD compliant way.  This is a
+specification in Germany for a restricted communication level.
+
 @item gpgme_recipient_t recipients
 @since{1.1.0}
 
@@ -5408,7 +5534,7 @@ or @code{gpgme_get_ctx_flag (ctx, "export-session-key")} returns true
 @since{1.11.0}
 
 A string with the symmetric encryption algorithm and mode using the
-format "<algo>.<mode>".  Note that old non-MDC encryption mode of
+format "<algo>.<mode>".  Note that the deprecated non-MDC encryption mode of
 OpenPGP is given as "PGPCFB".
 
 @end table
@@ -5723,7 +5849,15 @@ verification was attempted.
 
 @item char *file_name
 This is the filename of the original plaintext message file if it is
-known, otherwise this is a null pointer.
+known, otherwise this is a null pointer.  Warning: The filename is
+not covered by the signature.
+
+@item unsigned int is_mime : 1;
+@since{1.11.0}
+
+The message claims that the content is a MIME object.  Warning: This
+flag is not covered by the signature.
+
 @end table
 @end deftp
 
index f154edb..13cb0cf 100644 (file)
@@ -1,7 +1,7 @@
 # This is a template.  The dist target uses it to create the real file.
 Summary: GPGME - GnuPG Made Easy
 Name: gpgme
-Version: 1.11.1
+Version: 1.12.0
 Release: 1
 URL: https://gnupg.org/gpgme.html
 Source: ftp://ftp.gnupg.org/gcrypt/alpha/gpgme/%{name}-%{version}.tar.gz
@@ -38,10 +38,12 @@ make distclean
 %post
 /sbin/ldconfig
 /sbin/install-info %{_infodir}/gpgme.info.gz %{_infodir}/dir
+/sbin/install-info %{_infodir}/gpgme-python-howto.info.gz %{_infodir}/dir
 
 %preun
 if [ "$1" = 0 ]; then
  /sbin/install-info --delete %{_infodir}/gpgme.info.gz %{_infodir}/dir
+ /sbin/install-info --delete %{_infodir}/gpgme-python-howto.info.gz %{_infodir}/dir
 fi
 
 %postun
@@ -57,6 +59,7 @@ fi
 %{_includedir}/gpgme.h
 %{_datadir}/aclocal/gpgme.m4
 %{_infodir}/gpgme.info*
+%{_infodir}/gpgme-python-howto.info*
 
 %changelog
 * Sat Aug 30 2003 Robert Schiele <rschiele@uni-mannheim.de>
index ddc0de3..8d9c66a 100644 (file)
@@ -38,10 +38,12 @@ make distclean
 %post
 /sbin/ldconfig
 /sbin/install-info %{_infodir}/gpgme.info.gz %{_infodir}/dir
+/sbin/install-info %{_infodir}/gpgme-python-howto.info.gz %{_infodir}/dir
 
 %preun
 if [ "$1" = 0 ]; then
  /sbin/install-info --delete %{_infodir}/gpgme.info.gz %{_infodir}/dir
+ /sbin/install-info --delete %{_infodir}/gpgme-python-howto.info.gz %{_infodir}/dir
 fi
 
 %postun
@@ -57,6 +59,7 @@ fi
 %{_includedir}/gpgme.h
 %{_datadir}/aclocal/gpgme.m4
 %{_infodir}/gpgme.info*
+%{_infodir}/gpgme-python-howto.info*
 
 %changelog
 * Sat Aug 30 2003 Robert Schiele <rschiele@uni-mannheim.de>
index fd3ce4e..1bf7331 100644 (file)
@@ -18,6 +18,6 @@
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 
 SUBDIRS = $(ENABLED_LANGUAGES)
-DIST_SUBDIRS = cl cpp qt python
+DIST_SUBDIRS = cl cpp qt python js
 
 EXTRA_DIST = README
index 9ca2ef6..8c89676 100644 (file)
@@ -114,7 +114,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
@@ -392,7 +392,7 @@ top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 SUBDIRS = $(ENABLED_LANGUAGES)
-DIST_SUBDIRS = cl cpp qt python
+DIST_SUBDIRS = cl cpp qt python js
 EXTRA_DIST = README
 all: all-recursive
 
index ee99f0f..afd7b08 100644 (file)
@@ -13,4 +13,4 @@ cl            Common Lisp
 cpp            C++
 qt             Qt-Framework API
 python         Python 2 and 3 (module name: gpg)
-javascript      Native messaging client for the gpgme-json server.
+js              Native messaging client for the gpgme-json server.
index 8cb02bd..5c492d7 100644 (file)
@@ -117,7 +117,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES = gpgme.asd
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
index 9c5c719..5f3e4a0 100644 (file)
@@ -27,7 +27,7 @@
 (defsystem gpgme
     :description "GnuPG Made Easy."
     :author "g10 Code GmbH"
-    :version "1.11.1"
+    :version "1.12.0"
     :licence "GPL"
     :depends-on ("cffi" "gpg-error")
     :components ((:file "gpgme-package")
index 0527499..f276c3f 100644 (file)
@@ -116,7 +116,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
index 21b259d..5ab55cc 100644 (file)
@@ -123,7 +123,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES = GpgmeppConfig-w32.cmake.in GpgmeppConfig.cmake.in \
        GpgmeppConfigVersion.cmake gpgmepp_version.h
 CONFIG_CLEAN_VPATH_FILES =
@@ -195,7 +195,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
index 135e4d5..1e4e549 100644 (file)
@@ -1028,6 +1028,9 @@ unsigned int to_auditlog_flags(unsigned int flags)
     if (flags & Context::AuditLogWithHelp) {
         result |= GPGME_AUDITLOG_WITH_HELP;
     }
+    if (flags & Context::DiagnosticAuditLog) {
+        result |= GPGME_AUDITLOG_DIAG;
+    }
     return result;
 }
 
@@ -1436,6 +1439,23 @@ Error Context::createKey (const char *userid,
                  flags));
 }
 
+KeyGenerationResult Context::createKeyEx (const char *userid,
+                                          const char *algo,
+                                          unsigned long reserved,
+                                          unsigned long expires,
+                                          const Key &certkey,
+                                          unsigned int flags)
+{
+    d->lasterr = gpgme_op_createkey(d->ctx,
+                 userid,
+                 algo,
+                 reserved,
+                 expires,
+                 certkey.impl(),
+                 flags);
+    return KeyGenerationResult(d->ctx, Error(d->lasterr));
+}
+
 Error Context::addUid(const Key &k, const char *userid)
 {
     return Error(d->lasterr = gpgme_op_adduid(d->ctx,
@@ -1478,6 +1498,16 @@ Error Context::startCreateSubkey(const Key &k, const char *algo,
                  k.impl(), algo, reserved, expires, flags));
 }
 
+Error Context::setFlag(const char *name, const char *value)
+{
+  return Error(d->lasterr = gpgme_set_ctx_flag(d->ctx, name, value));
+}
+
+const char *Context::getFlag(const char *name) const
+{
+  return gpgme_get_ctx_flag(d->ctx, name);
+}
+
 // Engine Spawn stuff
 Error Context::spawn(const char *file, const char *argv[],
                      Data &input, Data &output, Data &err,
index aff8e49..6e27daa 100644 (file)
@@ -86,6 +86,9 @@ public:
     void setOffline(bool useOfflineMode);
     bool offline() const;
 
+    const char *getFlag(const char *name) const;
+    Error setFlag(const char *name, const char *value);
+
     enum CertificateInclusion {
         DefaultCertificates = -256,
         AllCertificatesExceptRoot = -2,
@@ -231,6 +234,14 @@ public:
                      const Key &certkey,
                      unsigned int flags);
 
+    // Same as create key but returning a result
+    GpgME::KeyGenerationResult createKeyEx (const char *userid,
+                                            const char *algo,
+                                            unsigned long reserved,
+                                            unsigned long expires,
+                                            const Key &certkey,
+                                            unsigned int flags);
+
     Error addUid(const Key &key, const char *userid);
     Error startAddUid(const Key &key, const char *userid);
 
@@ -390,7 +401,9 @@ public:
     //
     //
     enum AuditLogFlags {
+        DefaultAuditLog = 0,
         HtmlAuditLog = 1,
+        DiagnosticAuditLog = 2,
         AuditLogWithHelp = 128
     };
     GpgME::Error startGetAuditLog(Data &output, unsigned int flags = 0);
@@ -453,6 +466,7 @@ public:
     {
         return d;
     }
+
 private:
     // Helper functions that need to be context because they rely
     // on the "Friendlyness" of context to access the gpgme types.
index 52b8da2..2782aa7 100644 (file)
@@ -232,6 +232,11 @@ off_t GpgME::Data::seek(off_t offset, int whence)
     return gpgme_data_seek(d->data, offset, whence);
 }
 
+GpgME::Error GpgME::Data::rewind()
+{
+    return Error(gpgme_data_rewind(d->data));
+}
+
 std::vector<GpgME::Key> GpgME::Data::toKeys(Protocol proto) const
 {
     std::vector<GpgME::Key> ret;
index 446f6fa..df8607e 100644 (file)
@@ -110,6 +110,9 @@ public:
     ssize_t write(const void *buffer, size_t length);
     off_t seek(off_t offset, int whence);
 
+    /* Convenience function to do a seek (0, SEEK_SET).  */
+    Error rewind();
+
     /** Try to parse the data to a key object using the
      * Protocol proto. Returns an empty list on error.*/
     std::vector<Key> toKeys(const Protocol proto = Protocol::OpenPGP) const;
index 1e815cb..ea0a8a5 100644 (file)
@@ -51,6 +51,9 @@ public:
         if (res.file_name) {
             res.file_name = strdup(res.file_name);
         }
+        if (res.symkey_algo) {
+            res.symkey_algo = strdup(res.symkey_algo);
+        }
         //FIXME: copying gpgme_recipient_t objects invalidates the keyid member,
         //thus we use _keyid for now (internal API)
         for (gpgme_recipient_t r = res.recipients ; r ; r = r->next) {
@@ -68,6 +71,10 @@ public:
             std::free(res.file_name);
         }
         res.file_name = 0;
+        if (res.symkey_algo) {
+            std::free(res.symkey_algo);
+        }
+        res.symkey_algo = 0;
     }
 
     _gpgme_op_decrypt_result res;
@@ -155,6 +162,21 @@ std::vector<GpgME::DecryptionResult::Recipient> GpgME::DecryptionResult::recipie
     return result;
 }
 
+const char *GpgME::DecryptionResult::sessionKey() const
+{
+  return d ? d->res.session_key : nullptr;
+}
+
+const char *GpgME::DecryptionResult::symkeyAlgo() const
+{
+  return d ? d->res.symkey_algo : nullptr;
+}
+
+bool GpgME::DecryptionResult::isLegacyCipherNoMDC() const
+{
+  return d && d->res.legacy_cipher_nomdc;
+}
+
 class GpgME::DecryptionResult::Recipient::Private : public _gpgme_recipient
 {
 public:
@@ -231,6 +253,8 @@ std::ostream &GpgME::operator<<(std::ostream &os, const DecryptionResult &result
            << "\n unsupportedAlgorithm: " << protect(result.unsupportedAlgorithm())
            << "\n isWrongKeyUsage:      " << result.isWrongKeyUsage()
            << "\n isDeVs                " << result.isDeVs()
+           << "\n legacyCipherNoMDC     " << result.isLegacyCipherNoMDC()
+           << "\n symkeyAlgo:           " << protect(result.symkeyAlgo())
            << "\n recipients:\n";
         const std::vector<DecryptionResult::Recipient> recipients = result.recipients();
         std::copy(recipients.begin(), recipients.end(),
index 57705b4..e4d542d 100644 (file)
@@ -77,12 +77,18 @@ public:
 
     const char *fileName() const;
 
+    const char *sessionKey() const;
+
+    const char *symkeyAlgo() const;
+
     class Recipient;
 
     unsigned int numRecipients() const;
     Recipient recipient(unsigned int idx) const;
     std::vector<Recipient> recipients() const;
 
+    bool isLegacyCipherNoMDC() const;
+
 private:
     class Private;
     void init(gpgme_ctx_t ctx);
index 6f42e47..0ed6781 100644 (file)
@@ -36,12 +36,11 @@ using namespace GpgME;
 class GpgGenCardKeyInteractor::Private
 {
 public:
-    Private() : keysize(2048), backup(false)
+    Private() : keysize("2048"), backup(false)
     {
 
     }
-    std::string name, email, backupFileName, expiry, serial;
-    int keysize;
+    std::string name, email, backupFileName, expiry, serial, keysize;
     bool backup;
 };
 
@@ -70,7 +69,7 @@ void GpgGenCardKeyInteractor::setDoBackup(bool value)
 
 void GpgGenCardKeyInteractor::setKeySize(int value)
 {
-    d->keysize = value;
+    d->keysize = std::to_string(value);
 }
 
 void GpgGenCardKeyInteractor::setExpiry(const std::string &timeStr)
@@ -132,7 +131,7 @@ const char *GpgGenCardKeyInteractor::action(Error &err) const
     case SIZE:
     case SIZE2:
     case SIZE3:
-        return std::to_string(d->keysize).c_str();
+        return d->keysize.c_str();
     case COMMENT:
         return "";
     case SAVE:
index 034286f..8fc266f 100644 (file)
@@ -347,6 +347,9 @@ const Key &Key::mergeWith(const Key &other)
 
 void Key::update()
 {
+    if (isNull() || !primaryFingerprint()) {
+        return;
+    }
     auto ctx = Context::createForProtocol(protocol());
     if (!ctx) {
         return;
@@ -1042,6 +1045,8 @@ std::ostream &operator<<(std::ostream &os, const UserID &uid)
            << "\n revoked:   " << uid.isRevoked()
            << "\n invalid:   " << uid.isInvalid()
            << "\n numsigs:   " << uid.numSignatures()
+           << "\n origin:    " << uid.origin()
+           << "\n updated:   " << uid.lastUpdate()
            << "\n tofuinfo:\n" << uid.tofuInfo();
     }
     return os << ')';
@@ -1060,6 +1065,8 @@ std::ostream &operator<<(std::ostream &os, const Key &key)
            << "\n canEncrypt: " << key.canEncrypt()
            << "\n canCertify: " << key.canCertify()
            << "\n canAuth:    " << key.canAuthenticate()
+           << "\n origin:     " << key.origin()
+           << "\n updated:    " << key.lastUpdate()
            << "\n uids:\n";
         const std::vector<UserID> uids = key.userIDs();
         std::copy(uids.begin(), uids.end(),
index 2c42d07..fa8237a 100644 (file)
@@ -406,7 +406,7 @@ GpgME::Key GpgME::Signature::key(bool search, bool update) const
     }
 
     GpgME::Key ret = key();
-    if (ret.isNull() && search) {
+    if (ret.isNull() && search && fingerprint ()) {
         auto ctx = Context::createForProtocol (d->proto);
         if (ctx) {
             ctx->setKeyListMode(KeyListMode::Local |
diff --git a/lang/js/.eslintrc.json b/lang/js/.eslintrc.json
new file mode 100644 (file)
index 0000000..dc3be2e
--- /dev/null
@@ -0,0 +1,49 @@
+{
+    "env": {
+        "browser": true,
+        "es6": true
+    },
+    "extends": "eslint:recommended",
+    "parserOptions": {
+        "sourceType": "module"
+    },
+    "rules": {
+        "indent": [
+            "warn",
+            4
+        ],
+        "linebreak-style": [
+            "error",
+            "unix"
+        ],
+        "quotes": [
+            "error",
+            "single"
+        ],
+        "semi": [
+            "error",
+            "always"
+        ],
+        "no-var": [
+            "warn"
+        ],
+        "max-len": 1,
+        "default-case": 2,
+        "no-invalid-this": 2,
+        "no-lone-blocks": 1,
+        "no-self-compare": 2,
+        "radix": 2,
+        "no-use-before-define": ["error", {
+            "functions": false,
+            "classes": false,
+            "variables": true
+        }],
+        "no-useless-constructor": 1,
+        "space-before-function-paren": ["error", "always"],
+        "keyword-spacing": 2,
+        "spaced-comment": 1,
+        "space-unary-ops": 2,
+        "object-curly-spacing": ["error", "always"],
+        "array-bracket-spacing": ["error", "never"]
+    }
+}
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/Makefile.am b/lang/js/BrowserTestExtension/Makefile.am
new file mode 100644 (file)
index 0000000..8f0a4f9
--- /dev/null
@@ -0,0 +1,45 @@
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Intevation GmbH
+#
+# This file is part of GPGME.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+
+EXTRA_DIST = browsertest.html \
+             index.html \
+             longTests.html \
+             Makefile.am \
+             manifest.json \
+             popup.html \
+             popup.js \
+             runbrowsertest.js \
+             rununittests.js \
+             setup_testing.js \
+             testicon.png \
+             testkey2.pub \
+             testkey.pub \
+             testkey.sec \
+             tests/decryptTest.js \
+             tests/encryptDecryptTest.js \
+             tests/encryptTest.js \
+             tests/inputvalues.js \
+             tests/KeyImportExport.js \
+             tests/KeyInfos.js \
+             tests/longRunningTests.js \
+             tests/signTest.js \
+             tests/startup.js \
+             tests/verifyTest.js \
+             unittests.html
diff --git a/lang/js/BrowserTestExtension/Makefile.in b/lang/js/BrowserTestExtension/Makefile.in
new file mode 100644 (file)
index 0000000..842ca1a
--- /dev/null
@@ -0,0 +1,555 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Intevation GmbH
+#
+# This file is part of GPGME.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \  ]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs  ]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lang/js/BrowserTestExtension
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+       $(top_srcdir)/build-aux/mkinstalldirs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+       $(top_srcdir)/m4/ax_pkg_swig.m4 \
+       $(top_srcdir)/m4/ax_python_devel.m4 \
+       $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \
+       $(top_srcdir)/m4/gnupg-ttyname.m4 \
+       $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \
+       $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+       $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+       $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \
+       $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \
+       $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/conf/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_FILEVERSION = @BUILD_FILEVERSION@
+BUILD_REVISION = @BUILD_REVISION@
+BUILD_TIMESTAMP = @BUILD_TIMESTAMP@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENABLED_LANGUAGES = @ENABLED_LANGUAGES@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@
+GLIBC21 = @GLIBC21@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@
+GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@
+GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@
+GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@
+GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@
+GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@
+GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@
+GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@
+GPGME_QT_LIBS = @GPGME_QT_LIBS@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@
+GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@
+GRAPHVIZ = @GRAPHVIZ@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@
+LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@
+LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@
+LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@
+LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@
+LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@
+LIBOBJS = @LIBOBJS@
+LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@
+LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@
+LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MOC = @MOC@
+MOC2 = @MOC2@
+NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PYTHON = @PYTHON@
+PYTHONS = @PYTHONS@
+PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@
+PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@
+PYTHON_LDFLAGS = @PYTHON_LDFLAGS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_SITE_PKG = @PYTHON_SITE_PKG@
+PYTHON_VERSION = @PYTHON_VERSION@
+QTCHOOSER = @QTCHOOSER@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SWIG = @SWIG@
+SWIG_LIB = @SWIG_LIB@
+SYSROOT = @SYSROOT@
+VERSION = @VERSION@
+VERSION_MAJOR = @VERSION_MAJOR@
+VERSION_MICRO = @VERSION_MICRO@
+VERSION_MINOR = @VERSION_MINOR@
+VERSION_NUMBER = @VERSION_NUMBER@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+emacs_local_vars_begin = @emacs_local_vars_begin@
+emacs_local_vars_end = @emacs_local_vars_end@
+emacs_local_vars_read_only = @emacs_local_vars_read_only@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = browsertest.html \
+             index.html \
+             longTests.html \
+             Makefile.am \
+             manifest.json \
+             popup.html \
+             popup.js \
+             runbrowsertest.js \
+             rununittests.js \
+             setup_testing.js \
+             testicon.png \
+             testkey2.pub \
+             testkey.pub \
+             testkey.sec \
+             tests/decryptTest.js \
+             tests/encryptDecryptTest.js \
+             tests/encryptTest.js \
+             tests/inputvalues.js \
+             tests/KeyImportExport.js \
+             tests/KeyInfos.js \
+             tests/longRunningTests.js \
+             tests/signTest.js \
+             tests/startup.js \
+             tests/verifyTest.js \
+             unittests.html
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+               && { if test -f $@; then exit 0; else break; fi; }; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/js/BrowserTestExtension/Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --gnu lang/js/BrowserTestExtension/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(DISTFILES)
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+       -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+       cscopelist-am ctags-am distclean distclean-generic \
+       distclean-libtool distdir dvi dvi-am html html-am info info-am \
+       install install-am install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-ps install-ps-am \
+       install-strip installcheck installcheck-am installdirs \
+       maintainer-clean maintainer-clean-generic mostlyclean \
+       mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+       tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lang/js/BrowserTestExtension/browsertest.html b/lang/js/BrowserTestExtension/browsertest.html
new file mode 100644 (file)
index 0000000..0d3e293
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link href="libs/mocha.css" rel="stylesheet" />
+    </head>
+<body>
+    <h3>Browsertest</h3>
+    <div id="mocha"></div>
+    <!-- load unit tests -->
+    <script src="libs/mocha.js"></script>
+    <script src="libs/chai.js"></script>
+    <script src="setup_testing.js"></script>
+    <script src="libs/gpgmejs.bundle.js"></script>
+    <script src="tests/inputvalues.js"></script>
+<!-- insert tests here-->
+    <script src="tests/startup.js"></script>
+    <script src="tests/KeyInfos.js"></script>
+    <script src="tests/encryptTest.js"></script>
+    <script src="tests/encryptDecryptTest.js"></script>
+    <script src="tests/signTest.js"></script>
+    <script src="tests/verifyTest.js"></script>
+    <script src="tests/decryptTest.js"></script>
+    <script src="tests/KeyImportExport.js"></script>
+<!-- run tests -->
+    <script src="runbrowsertest.js"></script>
+    </body>
+</html>
diff --git a/lang/js/BrowserTestExtension/index.html b/lang/js/BrowserTestExtension/index.html
new file mode 100644 (file)
index 0000000..7f8d97d
--- /dev/null
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link href="libs/mocha.css" rel="stylesheet" />
+    </head>
+<body>
+    <h3>gpgmejs - Tests</h3>
+    <p>
+        The unittests rely on a separately packaged version of gpgmejs,
+        with the different classes and functions exposed. These tests and their
+        input values can be found in gpgme/lang/js/test. They do not test the
+        overall functionality, but the individual behaviour of the components.
+        <ul>
+            <li>
+                <a href="unittests.html">
+                    Unittests of the individual functions and classes.
+                </a>
+            </li>
+        </ul>
+    </p>
+    <p>
+        The functionality tests, to be found in
+        gpgme/lang/js/BrowserTestExtension, check the overall functionality of
+        the standard  packaged version of gpgmejs.
+    </p>
+    <p>
+        Most tests rely on a test gpg key to be available in gpg, which can be
+        found at the bottom of this page, or as "testkey.sec" in the
+        BrowserTestExtension's directory. Please import this key to your tested
+        gpg installation, or adapt the input defined in tests/inputvalues.js
+        if you want to use different values.
+    </p>
+    <p>
+        <ul>
+            <li>
+                <a href="browsertest.html">
+                    Functionality tests using the bundled library.
+                </a>
+            </li>
+            <li>
+                <a href="longTests.html">
+                    Functionality tests with larger/longer running data sets.
+                </a>
+            </li>
+        </ul>
+        </p>
+        <hr />
+        <p>
+
+            <textarea rows="5" cols="65" wrap="hard" readonly>
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQOYBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf
+PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE
+BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c
+PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870
++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M
+yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAEAB/wLJ0gyMjs2fFfT83wM
+5Lzz2yQIwV4t3bblBAujdHTqeN5Zmsm/oakFyjSokULK96Kv0R4ej9eoIgMFvxFk
+HRkrggxTrbsNJ7I6QcKYHTPeIIj318ykNL6fj0WJUcdPIENukXl5jbqNyk3/4D2y
+TTDySyq6jHTgvMH4K4KJUSpglvSJPntTk9RhuFGHAF+sNR9atygDYctAaERMRtSg
+LCoSt/AoX5GRMlQjXT9oqQjwSQoZyF4s8HMC8wdTFIE/E0L4IVdHVp8sz2UszNtT
+W/evmCA+KVruKjRH/Fhrq4hHkEamW28+j4L6uAyagONP7BONs+S5Oo2zTT9+tV2R
+ILTZBADdgLuAgF6C5Lu9jCF6DfFgaT/uafMyQNkEGNlxOHMWHTgLHe475V2eG9gA
+amd4yXKyEFKU1PWnvlGuicQSGdzVcwmq61msvXgYD0FK3LP3yWzKnE4X1tzrC9Vp
+/uHJxKjewCuyt1f5in919v+T8TbUxBYKC0zX/qWtX+10cTx77QQA6leqhToJ95Yc
+u4UBrKMEO+y2v8Svb3LG7yI5oY8tkw0EkJ/kpZ8xTAfZYCe6fXdvVE3PHg2lrxyc
+Wv/EU3QY/qA3G82mbXYeJ2jNZaTNYo4MylMrt4Mx25x4ke7JlsE8SVrQ+4CrHkqp
+OjSIa7fppLrQ78uW980AtN8NNQGrlTsD/A9aoA60Igxy1Q3K2uSyDCyjLknv57ym
+ZSBD3/t7m0l6Q6gbdfhNGosT+Hd4y3actqEqzXZHW2VG4dKZ/wRNkxtSm9adU9vs
+EHyzxjb6mKIH32zAG5TaFT20hC+NK6lsyHr9UE2ZrS6ma2sLxGW2O40hqNsdD+5m
+NrqeBc2I/js1PMK0EHRlc3RAZXhhbXBsZS5vcmeJAVQEEwEIAD4WIQTUFzW5Ejb9
+uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIe
+AQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGwFq07W9N01HWULyhHKoMmcHL6rfZ6
+4oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6kaBe+wF6Kqir6udFSBW9rPcFg6/V
+ZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rNGkYo1JCBR0XdRJYCSX3yB4TWv/eX
+nZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k70DevQeBsv+UjVXjWpNTZmPbvDnd9
+95uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6UunOf9Rlp1oMzdxMool/d1MlCxg2h
+3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ1J8Nsv87SZeEnQOYBFrsKEkBCADj
+oEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6yE+x2hk5FoQCajxKa/d4AVxOnJpd
+whAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0pO7bgAiZxkA6RHxtNqhpPnPQoXvUz
+kzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/8i9Taz67pdZwuJjac8qBuJHjzAo1
+bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt3RuqVGP316Fk+Sy2+60tC/HlX8jg
+MyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyLUGKU+L8cB2g1PGGp2biBFWqZbudZ
+oyRBet/0yH/zirBdQJw1ABEBAAEAB/4lN3gXOI4OuoOcsvHak4pebx61Mt0YP9cT
+qZASIBqxok5x8E28pFh/tYfkYdqRCtdNYZOnxcEoUWh5j6nfwZkEnJ9P/T8GPNk7
+pMKnKXmExi05b5uGHD8nU1rSbf/YkvAF0vpbxd4/RDxbbtQhbUwGzusSI+pBLM0w
+5TreEB+vRGBc2gOvXXOtKLNEa7M9rH2EwbAkP3jOGGwgk6adxbQdBcRxq4merqhL
+YrVz73bCj8TDc0fsNJyIaZZJ++ejfBFYavsF1pvx9z7FNFi8rSXoiB3SBtaWGfhr
+bwNaMZrDc7TRIq/fgGaL6g//bzcWrr1YaHXZ10Bgx6UymDOlYkCpBADm0Hv46sPw
+07SO8+IACcaQliOto1pndOPwTimCeo58/7rf8I2a5uuJloGrnPwAX65bKDnUALp6
+X3lnXRNMhnB3Uewx4i00LQmjsxhJfQiGLpMv0j58tn64s7GqQzGVV1JKcQm992RV
+jFOydyjZ+K4LGWEOITG/bZrMEVNGCM+OnQQA/Haz8xN0NFSlq7tyfFc0pkx/TiCX
+xGfBqbO0wU2b5GMnZbY/06HENpidIzpa231VQaw5/nPTvfhlLKW1iGAkc148cX1q
+lL9w2ksXuaHR3LXud2VcfVTIdxU/7h7u1dD/85+c0+7jlGObD9cXKxlM6OjpIJz1
+l5/1h3C5S0TuxHkEAL/3BGihkhNfv1Xx0rWu0/732usX/nE/A9C26hGu41FUf3fp
+0ilonKpKZUEwWt5hWSEFCSrznNVekiO0rxvuu3RVegvzThPNU4Pf4JZtJpRVhvUQ
+d9ulxJw7V9rs75uNBatTNC0kXuGoXhehw4Bn93xa67gYGd3LfrH+oT0GCDpTSHCJ
+ATwEGAEIACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAK
+CRAjAWNe7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ
+5F32NDJ9PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7
+Jq3e/4Iy0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02L
+kr+2Cc/Qk6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe
+23nntMSDA+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t
+9Mmd7bM1+UyPgbPEr0iWMeyctYsuOLeUyQKMscDT
+=hkUm
+-----END PGP PRIVATE KEY BLOCK-----
+            </textarea>
+
+        </p>
+    </body>
+</html>
diff --git a/lang/js/BrowserTestExtension/longTests.html b/lang/js/BrowserTestExtension/longTests.html
new file mode 100644 (file)
index 0000000..8ff969b
--- /dev/null
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link href="libs/mocha.css" rel="stylesheet" />
+    </head>
+<body>
+    <h3>Browsertest</h3>
+    <div id="mocha"></div>
+    <!-- load unit tests -->
+    <script src="libs/mocha.js"></script>
+    <script src="libs/chai.js"></script>
+    <script src="setup_testing.js"></script>
+    <script src="libs/gpgmejs.bundle.js"></script>
+    <script src="tests/inputvalues.js"></script>
+<!-- insert tests here-->
+    <script src="tests/startup.js"></script>
+    <script src="tests/longRunningTests.js"></script>
+<!-- run tests -->
+    <script src="runbrowsertest.js"></script>
+    </body>
+</html>
diff --git a/lang/js/BrowserTestExtension/manifest.json b/lang/js/BrowserTestExtension/manifest.json
new file mode 100644 (file)
index 0000000..a9e605b
--- /dev/null
@@ -0,0 +1,13 @@
+{
+    "manifest_version": 2,
+
+    "name": "Browsertests for gpgmejs",
+    "description": "Run the browsertests.",
+    "version": "0.1",
+    "content_security_policy": "default-src 'self' filesystem:",
+    "browser_action": {
+      "default_icon": "testicon.png",
+      "default_popup": "popup.html"
+    },
+    "permissions": ["nativeMessaging", "activeTab"]
+  }
diff --git a/lang/js/BrowserTestExtension/popup.html b/lang/js/BrowserTestExtension/popup.html
new file mode 100644 (file)
index 0000000..f17f262
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src="popup.js"></script>
+    </head>
+    <body>
+    </body>
+</html>
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/popup.js b/lang/js/BrowserTestExtension/popup.js
new file mode 100644 (file)
index 0000000..794620b
--- /dev/null
@@ -0,0 +1,30 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global chrome */
+
+document.addEventListener('DOMContentLoaded', function() {
+    chrome.tabs.create({
+        url: './index.html'
+    });
+});
diff --git a/lang/js/BrowserTestExtension/runbrowsertest.js b/lang/js/BrowserTestExtension/runbrowsertest.js
new file mode 100644 (file)
index 0000000..c46eb12
--- /dev/null
@@ -0,0 +1,26 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global mocha */
+
+mocha.run();
diff --git a/lang/js/BrowserTestExtension/rununittests.js b/lang/js/BrowserTestExtension/rununittests.js
new file mode 100644 (file)
index 0000000..df31589
--- /dev/null
@@ -0,0 +1,27 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global Gpgmejs_test, mocha*/
+
+Gpgmejs_test.unittests();
+mocha.run();
diff --git a/lang/js/BrowserTestExtension/setup_testing.js b/lang/js/BrowserTestExtension/setup_testing.js
new file mode 100644 (file)
index 0000000..52aeac5
--- /dev/null
@@ -0,0 +1,28 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global mocha, chai */
+
+mocha.setup('bdd');
+const expect = chai.expect; //eslint-disable-line no-unused-vars
+chai.config.includeStack = true;
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/testicon.png b/lang/js/BrowserTestExtension/testicon.png
new file mode 100644 (file)
index 0000000..a98463d
Binary files /dev/null and b/lang/js/BrowserTestExtension/testicon.png differ
diff --git a/lang/js/BrowserTestExtension/testkey.pub b/lang/js/BrowserTestExtension/testkey.pub
new file mode 100644 (file)
index 0000000..cfc329f
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf
+PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE
+BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c
+PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870
++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M
+yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAG0EHRlc3RAZXhhbXBsZS5v
+cmeJAVQEEwEIAD4WIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8Jn
+AAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGw
+Fq07W9N01HWULyhHKoMmcHL6rfZ64oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6
+kaBe+wF6Kqir6udFSBW9rPcFg6/VZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rN
+GkYo1JCBR0XdRJYCSX3yB4TWv/eXnZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k7
+0DevQeBsv+UjVXjWpNTZmPbvDnd995uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6U
+unOf9Rlp1oMzdxMool/d1MlCxg2h3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ
+1J8Nsv87SZeEuQENBFrsKEkBCADjoEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6
+yE+x2hk5FoQCajxKa/d4AVxOnJpdwhAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0p
+O7bgAiZxkA6RHxtNqhpPnPQoXvUzkzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/
+8i9Taz67pdZwuJjac8qBuJHjzAo1bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt
+3RuqVGP316Fk+Sy2+60tC/HlX8jgMyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyL
+UGKU+L8cB2g1PGGp2biBFWqZbudZoyRBet/0yH/zirBdQJw1ABEBAAGJATwEGAEI
+ACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAKCRAjAWNe
+7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ5F32NDJ9
+PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7Jq3e/4Iy
+0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02Lkr+2Cc/Q
+k6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe23nntMSD
+A+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t9Mmd7bM1
++UyPgbPEr0iWMeyctYsuOLeUyQKMscDT
+=QyY6
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/lang/js/BrowserTestExtension/testkey.sec b/lang/js/BrowserTestExtension/testkey.sec
new file mode 100644 (file)
index 0000000..ced8f3e
--- /dev/null
@@ -0,0 +1,57 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQOYBFrsKEkBCADKw4Wt8J6M/88qD8PO6lSMCxH1cpwH8iK0uPaFFYsJkkXo7kWf
+PTAtrV+REqF/o80dvYcdLvRsV21pvncZz/HXLu1yQ18mC3XObrKokbdgrTTKA5XE
+BZkNsqyaMMJauT18H4hYkSg62/tTdO1cu/zWv/LFf7Xyn6+uA74ovXCJlO1s0N2c
+PShtr98QRzPMf2owgVk37JnDNp4gGVDGHxSZOuUwxgYAZYnA8SFc+c+3ZrQfY870
++O4j3Mz4p7yD13AwP4buQLBsb/icxekeQCqpRJhLH9f7MdEcGXa1x36RcEkHdu+M
+yJ392eMgD+dKNfRCtyTPhjZTxvbNELIBYICfABEBAAEAB/wLJ0gyMjs2fFfT83wM
+5Lzz2yQIwV4t3bblBAujdHTqeN5Zmsm/oakFyjSokULK96Kv0R4ej9eoIgMFvxFk
+HRkrggxTrbsNJ7I6QcKYHTPeIIj318ykNL6fj0WJUcdPIENukXl5jbqNyk3/4D2y
+TTDySyq6jHTgvMH4K4KJUSpglvSJPntTk9RhuFGHAF+sNR9atygDYctAaERMRtSg
+LCoSt/AoX5GRMlQjXT9oqQjwSQoZyF4s8HMC8wdTFIE/E0L4IVdHVp8sz2UszNtT
+W/evmCA+KVruKjRH/Fhrq4hHkEamW28+j4L6uAyagONP7BONs+S5Oo2zTT9+tV2R
+ILTZBADdgLuAgF6C5Lu9jCF6DfFgaT/uafMyQNkEGNlxOHMWHTgLHe475V2eG9gA
+amd4yXKyEFKU1PWnvlGuicQSGdzVcwmq61msvXgYD0FK3LP3yWzKnE4X1tzrC9Vp
+/uHJxKjewCuyt1f5in919v+T8TbUxBYKC0zX/qWtX+10cTx77QQA6leqhToJ95Yc
+u4UBrKMEO+y2v8Svb3LG7yI5oY8tkw0EkJ/kpZ8xTAfZYCe6fXdvVE3PHg2lrxyc
+Wv/EU3QY/qA3G82mbXYeJ2jNZaTNYo4MylMrt4Mx25x4ke7JlsE8SVrQ+4CrHkqp
+OjSIa7fppLrQ78uW980AtN8NNQGrlTsD/A9aoA60Igxy1Q3K2uSyDCyjLknv57ym
+ZSBD3/t7m0l6Q6gbdfhNGosT+Hd4y3actqEqzXZHW2VG4dKZ/wRNkxtSm9adU9vs
+EHyzxjb6mKIH32zAG5TaFT20hC+NK6lsyHr9UE2ZrS6ma2sLxGW2O40hqNsdD+5m
+NrqeBc2I/js1PMK0EHRlc3RAZXhhbXBsZS5vcmeJAVQEEwEIAD4WIQTUFzW5Ejb9
+uIIEjFojAWNe7/DLBQUCWuwoSQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIe
+AQIXgAAKCRAjAWNe7/DLBf9kB/wOQ/S60HGwFq07W9N01HWULyhHKoMmcHL6rfZ6
+4oDqLxolPSasz7WAMW1jN4qtWJ0mFzwO83V6kaBe+wF6Kqir6udFSBW9rPcFg6/V
+ZXPltT0a6uacIHq6DyQ5iMW4YQWbVy9OR2rNGkYo1JCBR0XdRJYCSX3yB4TWv/eX
+nZ37/WjmiTOIZh35rjs+NuU/S5JPDfAp2/k70DevQeBsv+UjVXjWpNTZmPbvDnd9
+95uSmC6UY4hzyP84ORYMYn9n1QAR0goxDN6UunOf9Rlp1oMzdxMool/d1MlCxg2h
+3jheuhv7lgUF4KpvHOuEPXQ7UO417E0TYcDZ1J8Nsv87SZeEnQOYBFrsKEkBCADj
+oEBhG/QPqZHg8VyoD1xYRAWGxyDJkX/GrSs6yE+x2hk5FoQCajxKa/d4AVxOnJpd
+whAfeXeSNaql5Ejgzax+Tdj9BV6vtGVJVv0pO7bgAiZxkA6RHxtNqhpPnPQoXvUz
+kzpRgpuL+Nj4yIg7z1ITH6KQH4u5SI9vd+j/8i9Taz67pdZwuJjac8qBuJHjzAo1
+bjYctFYUSG5pbmMQyNLySzgiNkFa4DajODlt3RuqVGP316Fk+Sy2+60tC/HlX8jg
+MyMONfOGBQx6jk8tvAphS/LAqrrNepnagIyLUGKU+L8cB2g1PGGp2biBFWqZbudZ
+oyRBet/0yH/zirBdQJw1ABEBAAEAB/4lN3gXOI4OuoOcsvHak4pebx61Mt0YP9cT
+qZASIBqxok5x8E28pFh/tYfkYdqRCtdNYZOnxcEoUWh5j6nfwZkEnJ9P/T8GPNk7
+pMKnKXmExi05b5uGHD8nU1rSbf/YkvAF0vpbxd4/RDxbbtQhbUwGzusSI+pBLM0w
+5TreEB+vRGBc2gOvXXOtKLNEa7M9rH2EwbAkP3jOGGwgk6adxbQdBcRxq4merqhL
+YrVz73bCj8TDc0fsNJyIaZZJ++ejfBFYavsF1pvx9z7FNFi8rSXoiB3SBtaWGfhr
+bwNaMZrDc7TRIq/fgGaL6g//bzcWrr1YaHXZ10Bgx6UymDOlYkCpBADm0Hv46sPw
+07SO8+IACcaQliOto1pndOPwTimCeo58/7rf8I2a5uuJloGrnPwAX65bKDnUALp6
+X3lnXRNMhnB3Uewx4i00LQmjsxhJfQiGLpMv0j58tn64s7GqQzGVV1JKcQm992RV
+jFOydyjZ+K4LGWEOITG/bZrMEVNGCM+OnQQA/Haz8xN0NFSlq7tyfFc0pkx/TiCX
+xGfBqbO0wU2b5GMnZbY/06HENpidIzpa231VQaw5/nPTvfhlLKW1iGAkc148cX1q
+lL9w2ksXuaHR3LXud2VcfVTIdxU/7h7u1dD/85+c0+7jlGObD9cXKxlM6OjpIJz1
+l5/1h3C5S0TuxHkEAL/3BGihkhNfv1Xx0rWu0/732usX/nE/A9C26hGu41FUf3fp
+0ilonKpKZUEwWt5hWSEFCSrznNVekiO0rxvuu3RVegvzThPNU4Pf4JZtJpRVhvUQ
+d9ulxJw7V9rs75uNBatTNC0kXuGoXhehw4Bn93xa67gYGd3LfrH+oT0GCDpTSHCJ
+ATwEGAEIACYWIQTUFzW5Ejb9uIIEjFojAWNe7/DLBQUCWuwoSQIbDAUJA8JnAAAK
+CRAjAWNe7/DLBf0pCACPp5hBuUWngu2Hqvg+tNiujfsiYzId3MffFxEk3CbXeHcJ
+5F32NDJ9PYCnra4L8wSv+NZt9gIa8lFwoFSFQCjzH7KE86XcV3MhfdJTNb/+9CR7
+Jq3e/4Iy0N5ip7PNYMCyakcAsxvsNCJKrSaDuYe/OAoTXRBtgRWE2uyT315em02L
+kr+2Cc/Qk6H+vlNOHGRgnpI/OZZjnUuUfBUvMGHr1phW+y7aeymC9PnUGdViRdJe
+23nntMSDA+0/I7ESO9JsWvJbyBmuiZpu9JjScOjYH9xpQLqRNyw4WHpZriN69F0t
+9Mmd7bM1+UyPgbPEr0iWMeyctYsuOLeUyQKMscDT
+=hkUm
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/lang/js/BrowserTestExtension/testkey2.pub b/lang/js/BrowserTestExtension/testkey2.pub
new file mode 100644 (file)
index 0000000..557bd5b
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBFsMHecBCACqdJgqa+CeNYwPCK+MpOwAV6uFVjDyO2LmOs6+XfDWRBU/Zjtz
+8zdYNKSbLjkWN4ujV5aiyA7MtEofszzYLEoKUt1wiDScHMpW8qmEFDvl9g26MeAV
+rTno9D5KodHvEIs8wnrqBs8ix0WLbh6J1Dtt8HQgIbN+v3gaRQrgBFe6z2ZYpHHx
+ZfOu3iFKlm2WE/NekRkvvFIo3ApGvRhGIYw6JMmugBlo7s5xosJK0I9dkPGlEEtt
+aF1RkcMj8sWG9vHAXcjlGgFfXSN9YLppydXpkuZGm4+gjLB2a3rbQCZVFnxCyG4O
+ybjkP8Jw6Udm89bK2ucYFfjdrmYn/nJqRxeNABEBAAG0I1Rlc3QgTm9Qcml2S2V5
+IDxub2JvZHlAZXhhbXBsZS5vcmc+iQFOBBMBCAA4FiEE4Fmh4IZtMa4TEXCITZou
+EzBBU9EFAlsMHecCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQTZouEzBB
+U9F+qwf/SHj4uRnTWgyJ71FBxQDYCBq3jbi6e7hMkRPbJyJdnPIMAb2p0PJjBgjW
+0pp4+kDPZans3UDHbma1u/SFI4/y6isJiK94Bk5xp5YliLGnUceTjgDFe6lBhfQ1
+zVWZC/NF3tPgbziIxXQTNt34nS+9dbV/QFDLW0POcN7C0jR/hgkBjMEH2PezWhSj
+mL/yLfLfUYAoxVpXjfC5aPJKqw0tR7m5ibznjCphE+FUMRg8EOmJcg6soeJ5QspU
+k2dPN3+Y0zCTNRgAHEI+yIQbM6pio6v2c+UCtT1QhW4xSI38/kcEG8QiM55r1TUy
+FcWAY5n5t1nNZtMxxse3LqEon3rKiLkBDQRbDB3nAQgAqfAjSjcngERtM+ZYOwN0
+QF2v2FuEuMe8mhju7Met7SN2zGv1LnjhTNshEa9IABEfjZirE2Tqx4xCWDwDedK4
+u1ToFvcnuAMnq2O47Sh+eTypsf6WPFtPBWf6ctKY31hFXjgoyDBULBvl43XU/D9C
+Mt7nsKDPYHVrrnge/qWPYVcb+cO0sSwNImMcwQSdTQ3VBq7MeNS9ZeBcXi+XCjhN
+kjNum2AQqpkHHDQV7871yQ8RIILvZSSfkLb0/SNDU+bGaw2G3lcyKdIfZi2EWWZT
+oCbH38I/+LV7nAEe4zFpHwW8X0Dkx2aLgxe6UszDH9L3eGhTLpJhOSiaanG+zZKm
++QARAQABiQE2BBgBCAAgFiEE4Fmh4IZtMa4TEXCITZouEzBBU9EFAlsMHecCGwwA
+CgkQTZouEzBBU9H5TQgAolWvIsez/WW8N2tmZEnX0LOFNB+1S4L4X983njwNdoVI
+w19pbj+8RIHF/H9kcPGi7jK96gvlykQn3uez/95D2AiRFW5KYdOouFisKgHpv8Ay
+BrhclHv11yK+X/0iTD0scYaG7np5162xLkaxSO9hsz2fGv20RKaXCWkI69fWw0BR
+XlI5pZh2YFei2ZhH/tIMIW65h3w0gtgaZBBdpZTOOW4zvghyN+0MSObqkI1BvUJu
+caDFI4d6ZTmp5SY+pZyktZ4bg/vMH5VFxdIKgbLx9uVeTvOupvbAW0TNulYGUBQE
+nm+S0zr3W18t64e4sS3oHse8zCqo1iiImpba6F1Oaw==
+=y6DD
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/lang/js/BrowserTestExtension/tests/KeyImportExport.js b/lang/js/BrowserTestExtension/tests/KeyImportExport.js
new file mode 100644 (file)
index 0000000..b3d95bb
--- /dev/null
@@ -0,0 +1,152 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ *     Raimund Renkert <rrenkert@intevation.de>
+ */
+
+/* global describe, it, expect, before, afterEach, Gpgmejs*/
+/* global ImportablePublicKey, inputvalues */
+
+describe('Key importing', function () {
+    const fpr = ImportablePublicKey.fingerprint;
+    const pubKey = ImportablePublicKey.key;
+    const changedKey = ImportablePublicKey.keyChangedUserId;
+
+    let context = null;
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            context.Keyring.getKeys({ pattern: fpr }).then(
+                function (result){
+                    if (result.length === 1) {
+                        result[0].delete().then(function (){
+                            done();
+                        },function (){
+                            done();
+                        });
+                    } else {
+                        done();
+                    }
+                });
+        });
+    });
+
+    afterEach(function (done){
+        // delete the test key if still present
+        context.Keyring.getKeys({ pattern: fpr }).then(
+            function (result){
+                if (result.length === 1) {
+                    result[0].delete().then(function (){
+                        done();
+                    },function (){
+                        done();
+                    });
+                } else {
+                    done();
+                }
+            });
+    });
+
+    it('Importing Key', function (done) {
+        context.Keyring.getKeys({ pattern: fpr }).then(function (result){
+            expect(result).to.be.an('array');
+            expect(result.length).to.equal(0);
+            context.Keyring.importKey(pubKey).then(function (result){
+                expect(result.Keys).to.be.an('array');
+                expect(result.Keys[0]).to.not.be.undefined;
+                expect(result.Keys[0].key).to.be.an('object');
+                expect(result.Keys[0].key.fingerprint).to.equal(fpr);
+                expect(result.Keys[0].status).to.equal('newkey');
+                expect(result.summary.considered).to.equal(1);
+                expect(result.summary.imported).to.equal(1);
+                done();
+            });
+        });
+    });
+
+    it('Updating Key', function (done){
+        context.Keyring.importKey(pubKey)
+            .then(function (result){
+                expect(result.Keys[0].key).to.not.be.undefined;
+                expect(result.Keys[0].status).to.equal('newkey');
+                context.Keyring.importKey(changedKey).then(function (res){
+                    expect(res.Keys[0].key).to.be.an('object');
+                    expect(res.Keys[0].key.fingerprint).to.equal(fpr);
+                    expect(res.Keys[0].status).to.equal('change');
+                    expect(res.Keys[0].changes.userId).to.be.true;
+                    expect(res.Keys[0].changes.subkey).to.be.false;
+                    expect(res.Keys[0].changes.signature).to.be.true;
+                    expect(res.summary.considered).to.equal(1);
+                    done();
+                });
+            });
+    });
+
+    it('Deleting Key', function (done) {
+        context.Keyring.importKey(pubKey).then(function (result){
+            expect(result.Keys[0].key).to.be.an('object');
+            expect(result.Keys[0].key.fingerprint).to.equal(fpr);
+            result.Keys[0].key.delete().then(function (result){
+                expect(result).to.be.true;
+                done();
+            });
+        });
+    });
+
+    it('Import result feedback', function (done){
+        context.Keyring.importKey(pubKey, true).then(function (result){
+            expect(result).to.be.an('object');
+            expect(result.Keys[0]).to.be.an('object');
+            expect(result.Keys[0].key.fingerprint).to.equal(fpr);
+            expect(result.Keys[0].status).to.equal('newkey');
+            result.Keys[0].key.getArmor().then(function (armor){
+                expect(armor).to.be.a('string');
+                done();
+            });
+        });
+    });
+
+    it('exporting armored Key with getKeysArmored', function (done) {
+        context.Keyring.importKey(pubKey).then(function (){
+            context.Keyring.getKeysArmored({ pattern: fpr })
+                .then(function (result){
+                    expect(result).to.be.an('object');
+                    expect(result.armored).to.be.a('string');
+                    expect(result.secret_fprs).to.be.undefined;
+                    done();
+                });
+        });
+    });
+
+    it('Exporting Key (including secret fingerprints)', function (done) {
+        const key_secret = inputvalues.encrypt.good.fingerprint;
+        context.Keyring.getKeysArmored({
+            pattern: key_secret, with_secret_fpr: true })
+            .then(function (result){
+                expect(result).to.be.an('object');
+                expect(result.armored).to.be.a('string');
+                expect(result.secret_fprs).to.be.an('array');
+                expect(result.secret_fprs[0]).to.equal(key_secret);
+                done();
+            });
+    });
+});
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/tests/KeyInfos.js b/lang/js/BrowserTestExtension/tests/KeyInfos.js
new file mode 100644 (file)
index 0000000..d122958
--- /dev/null
@@ -0,0 +1,59 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, expect, before, Gpgmejs */
+/* global inputvalues*/
+
+describe('Key information', function () {
+    let context = null;
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+
+    it('A fingerprint is consistently returned upper case hex', function (done){
+        const mixedCase = inputvalues.encrypt.good.fingerprint_mixedcase;
+        context.Keyring.getKeys({ pattern: mixedCase }).then(function (result){
+            expect(result).to.be.an('array');
+            expect(result.length).to.equal(1);
+            expect(result[0].fingerprint).to.equal(mixedCase.toUpperCase());
+            done();
+        });
+    });
+
+    it('A userId keeps their encoding', function (done){
+        context.Keyring.importKey(inputvalues.publicKeyNonAscii.key, true)
+            .then(function (result){
+                expect(result.Keys[0]).to.be.an('object');
+                const user = result.Keys[0].key.get('userids')[0];
+                expect(user.get('name')).to.equal(
+                    inputvalues.publicKeyNonAscii.userid);
+                result.Keys[0].key.delete().then(function (){
+                    done();
+                });
+            });
+    });
+});
diff --git a/lang/js/BrowserTestExtension/tests/decryptTest.js b/lang/js/BrowserTestExtension/tests/decryptTest.js
new file mode 100644 (file)
index 0000000..5817eb4
--- /dev/null
@@ -0,0 +1,114 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, before, expect, Gpgmejs */
+/* global bigString, inputvalues, sabotageMsg, binaryData, filename_files */
+
+describe('Decryption', function () {
+    let context = null;
+    const good_fpr = inputvalues.encrypt.good.fingerprint;
+
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout:2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+
+    it('Decryption of random string fails', function (done) {
+        let data = bigString(20 * 1024);
+        context.decrypt({ data: data }).then(
+            function (){},
+            function (error){
+                expect(error).to.be.an('error');
+                expect(error.code).to.equal('GNUPG_ERROR');
+                done();
+            });
+    });
+
+    it('Decryption of slightly corrupted message fails', function (done) {
+        const data = bigString(10000);
+        context.encrypt({ data: data, publicKeys:good_fpr }).then(
+            function (enc){
+                context.decrypt({ data: sabotageMsg(enc.data) }).then(
+                    function (){},
+                    function (error){
+                        expect(error).to.be.an('error');
+                        expect(error.code).to.equal('GNUPG_ERROR');
+                        done();
+                    });
+            });
+    }).timeout(5000);
+
+
+    it('decrypt/verify operations return proper information', function (done){
+        const data = inputvalues.encryptSignedMessage;
+        context.decrypt({ data: data }).then(function (result){
+            expect(result).to.be.an('object');
+            expect(result.signatures).to.be.an('object');
+            expect(result.signatures.all_valid).to.be.true;
+            expect(result.signatures.count).to.equal(1);
+            expect(result.signatures.signatures.good).to.be.an('array');
+            expect(
+                result.signatures.signatures.good[0].fingerprint).to.equal(
+                good_fpr);
+            done();
+        });
+    });
+
+    it('decrypt of a png, result as base64', function (done){
+        const data = binaryData.encryptedArmored;
+        context.decrypt({ data: data, expect: 'base64' })
+            .then(function (result){
+                expect(result.data).to.be.a('String');
+                expect(result.data).to.equal(binaryData.base64);
+                expect(result.format).to.equal('base64');
+                done();
+            });
+    });
+
+    it('decrypt of a png, result as Uint8Array', function (done){
+        const data = binaryData.encryptedArmored;
+        context.decrypt({ data: data, expect: 'uint8' })
+            .then(function (result){
+                expect(result.data).to.be.an('Uint8Array');
+                expect(result.format).to.equal('uint8');
+                done();
+            });
+    });
+
+    for (let i=0; i < filename_files.length; i++) {
+        it (
+            'decrypted file_names keep correct encoding (' + i + ')',
+            function (done){
+                context.decrypt({ data:filename_files[i].data })
+                    .then(function (answer){
+                        expect(answer.file_name).to.equal(
+                            filename_files[i].name);
+                        done();
+                    });
+            });
+    }
+
+});
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js b/lang/js/BrowserTestExtension/tests/encryptDecryptTest.js
new file mode 100644 (file)
index 0000000..b4e4f3f
--- /dev/null
@@ -0,0 +1,200 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, expect, before, Gpgmejs */
+/* global inputvalues, encryptedData, bigString, bigBoringString */
+
+describe('Encryption and Decryption', function (){
+    let context = null;
+    let good_fpr = inputvalues.encrypt.good.fingerprint;
+
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+
+    it('Successful encrypt and decrypt simple string', function (done) {
+        let data = inputvalues.encrypt.good.data;
+        context.encrypt({ data: data, publicKeys: good_fpr }).then(
+            function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include('BEGIN PGP MESSAGE');
+                expect(answer.data).to.include('END PGP MESSAGE');
+                context.decrypt({ data: answer.data }).then(function (result) {
+                    expect(result).to.not.be.empty;
+                    expect(result.data).to.be.a('string');
+                    expect(result.data).to.equal(
+                        inputvalues.encrypt.good.data);
+                    done();
+                });
+            });
+    });
+
+    it('Decrypt simple non-ascii', function (done) {
+        let data = encryptedData;
+        context.decrypt({ data: data }).then(function (result) {
+            expect(result).to.not.be.empty;
+            expect(result.data).to.be.a('string');
+            expect(result.data).to.equal(
+                '¡Äußerste µ€ før ñoquis@hóme! Добрый день\n');
+            done();
+        });
+    }).timeout(3000);
+
+    it('Trailing whitespace and different line endings', function (done) {
+        const data = 'Keks. \rKeks \n Keks \r\n';
+        context.encrypt({ data: data, publicKeys: good_fpr }).then(
+            function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include('BEGIN PGP MESSAGE');
+                expect(answer.data).to.include('END PGP MESSAGE');
+
+                context.decrypt({ data: answer.data }).then(function (result) {
+                    expect(result).to.not.be.empty;
+                    expect(result.data).to.be.a('string');
+                    expect(result.data).to.equal(data);
+                    done();
+                });
+            });
+    }).timeout(5000);
+
+    it('Random data, as string', function (done) {
+        let data = bigString(1000);
+        context.encrypt({ data:data, publicKeys: good_fpr }).then(
+            function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include(
+                    'BEGIN PGP MESSAGE');
+                expect(answer.data).to.include(
+                    'END PGP MESSAGE');
+                context.decrypt({ data: answer.data }).then(function (result) {
+                    expect(result).to.not.be.empty;
+                    expect(result.data).to.be.a('string');
+                    expect(result.data).to.equal(data);
+                    done();
+                });
+            });
+    }).timeout(3000);
+
+    it('Data, input as base64', function (done) {
+        let data = inputvalues.encrypt.good.data;
+        let b64data = btoa(data);
+        context.encrypt({ data: b64data, publicKeys: good_fpr, base64: true })
+            .then(function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include(
+                    'BEGIN PGP MESSAGE');
+                expect(answer.data).to.include(
+                    'END PGP MESSAGE');
+                context.decrypt({ data: answer.data }).then(function (result) {
+                    expect(result).to.not.be.empty;
+                    expect(result.data).to.be.a('string');
+                    expect(result.data).to.equal(data);
+                    done();
+                });
+            });
+    }).timeout(3000);
+
+    it('Random data, input as base64', function (done) {
+        let data = bigBoringString(0.001);
+        let b64data = btoa(data);
+        context.encrypt(
+            { data: b64data, publicKeys: good_fpr, base64: true })
+            .then(function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include(
+                    'BEGIN PGP MESSAGE');
+                expect(answer.data).to.include(
+                    'END PGP MESSAGE');
+                context.decrypt({ data:answer.data }).then(
+                    function (result) {
+                        expect(result).to.not.be.empty;
+                        expect(result.data).to.be.a('string');
+                        expect(result.data).to.equal(data);
+                        done();
+                    });
+            });
+    }).timeout(3000);
+
+    it('Random data, original data is and should stay base64 encoded',
+        function (done) {
+            let data = bigBoringString(0.001);
+            let b64data = btoa(data);
+            context.encrypt(
+                { data: b64data, publicKeys: good_fpr })
+                .then(function (answer) {
+                    expect(answer).to.not.be.empty;
+                    expect(answer.data).to.be.a('string');
+                    expect(answer.data).to.include(
+                        'BEGIN PGP MESSAGE');
+                    expect(answer.data).to.include(
+                        'END PGP MESSAGE');
+                    context.decrypt({
+                        data:answer.data, expect: 'base64' })
+                        .then(function (result) {
+                            expect(result).to.not.be.empty;
+                            expect(result.data).to.be.a('string');
+                            expect(result.data).to.equal(b64data);
+                            done();
+                        });
+                });
+    }).timeout(3000);
+
+    for (let j = 0; j < inputvalues.encrypt.good.data_nonascii_32.length; j++){
+        it('Roundtrip with >1MB non-ascii input meeting default chunksize (' +
+            (j + 1) + '/'
+            + inputvalues.encrypt.good.data_nonascii_32.length + ')',
+        function (done) {
+            let input = inputvalues.encrypt.good.data_nonascii_32[j];
+            expect(input).to.have.length(32);
+            let data = '';
+            for (let i=0; i < 34 * 1024; i++){
+                data += input;
+            }
+            context.encrypt({ data: data, publicKeys: good_fpr })
+                .then(function (answer) {
+                    expect(answer).to.not.be.empty;
+                    expect(answer.data).to.be.a('string');
+                    expect(answer.data).to.include(
+                        'BEGIN PGP MESSAGE');
+                    expect(answer.data).to.include(
+                        'END PGP MESSAGE');
+                    context.decrypt({ data: answer.data })
+                        .then(function (result) {
+                            expect(result).to.not.be.empty;
+                            expect(result.data).to.be.a('string');
+                            expect(result.data).to.equal(data);
+                            done();
+                        });
+                });
+        }).timeout(5000);
+    }
+});
diff --git a/lang/js/BrowserTestExtension/tests/encryptTest.js b/lang/js/BrowserTestExtension/tests/encryptTest.js
new file mode 100644 (file)
index 0000000..1017d71
--- /dev/null
@@ -0,0 +1,159 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, expect, before, Gpgmejs */
+/* global inputvalues, fixedLengthString, bigString */
+
+describe('Encryption', function () {
+    let context = null;
+    const good_fpr = inputvalues.encrypt.good.fingerprint;
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+
+    it('Successful encrypt', function (done) {
+        const data = inputvalues.encrypt.good.data;
+        context.encrypt({ data: data, publicKeys: good_fpr })
+            .then(function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include('BEGIN PGP MESSAGE');
+                expect(answer.data).to.include('END PGP MESSAGE');
+                done();
+            });
+    });
+
+
+    it( 'encrypt with \'armor\': true should returned an armored block',
+        function (done){
+            const data = bigString(1000);
+            context.encrypt({ data: data, publicKeys: good_fpr, armor: true })
+                .then(function (answer){
+                    expect(answer).to.not.be.empty;
+                    expect(answer.data).to.be.a('string');
+                    expect(answer.data).to.include('BEGIN PGP MESSAGE');
+                    expect(answer.data).to.include('END PGP MESSAGE');
+                    expect(answer.format).to.equal('ascii');
+                    done();
+                });
+        });
+
+    it( 'encrypt with \'armor\': false and \'expected\': \'uint8\' returns ' +
+    'an Uint8Array', function (done) {
+        const data = bigString(1000);
+        context.encrypt({
+            data: data, publicKeys: good_fpr, armor: false, expect: 'uint8'
+        }).then(function (answer){
+            expect(answer).to.not.be.empty;
+            expect(answer.data).to.be.a('Uint8Array');
+            expect(answer.format).to.equal('uint8');
+            done();
+        });
+    });
+
+    it( 'encrypt with \'armor\': false and \'expected\': \'base64\' returns ' +
+        'a base64 string', function (done) {
+        const data = bigString(1000);
+        context.encrypt({
+            data: data, publicKeys: good_fpr, armor: false, expect: 'base64'
+        }).then(function (answer){
+            expect(answer).to.not.be.empty;
+            expect(answer.data).to.be.a('string');
+            expect(answer.format).to.equal('base64');
+            done();
+        });
+    });
+
+    const sizes = [5,20,50];
+    for (let i=0; i < sizes.length; i++) {
+        it('Successful encrypt a ' + sizes[i] + 'MB message', function (done) {
+            const data = fixedLengthString(sizes[i]);
+            context.encrypt({ data: data, publicKeys: good_fpr })
+                .then(function (answer) {
+                    expect(answer).to.not.be.empty;
+                    expect(answer.data).to.be.a('string');
+                    expect(answer.data).to.include('BEGIN PGP MESSAGE');
+                    expect(answer.data).to.include('END PGP MESSAGE');
+                    done();
+                });
+        }).timeout(20000);
+    }
+
+    it('Sending encryption without keys fails', function (done) {
+        const data = inputvalues.encrypt.good.data;
+        context.encrypt({ data: data }).then(function (answer) {
+            expect(answer).to.be.undefined;
+        }, function (error){
+            expect(error).to.be.an('Error');
+            expect(error.code).to.equal('MSG_INCOMPLETE');
+            done();
+        });
+    });
+
+    it('Sending encryption without data fails', function (done) {
+        context.encrypt({ data: null, publicKeys: good_fpr })
+            .then(function (answer) {
+                expect(answer).to.be.undefined;
+            }, function (error) {
+                expect(error).to.be.an.instanceof(Error);
+                expect(error.code).to.equal('MSG_INCOMPLETE');
+                done();
+            });
+    });
+
+    it('Sending encryption with non existing keys fails', function (done) {
+        const data = inputvalues.encrypt.good.data;
+        const bad_fpr = inputvalues.encrypt.bad.fingerprint;
+        context.encrypt({ data:data, publicKeys: bad_fpr })
+            .then(function (answer) {
+                expect(answer).to.be.undefined;
+            }, function (error){
+                expect(error).to.be.an('Error');
+                expect(error.code).to.not.be.undefined;
+                expect(error.code).to.equal('GNUPG_ERROR');
+                done();
+            });
+    }).timeout(5000);
+
+    it('Overly large message ( > 64MB) is rejected', function (done) {
+        const data = fixedLengthString(65);
+        context.encrypt({ data: data, publicKeys: good_fpr })
+            .then(function (answer) {
+                expect(answer).to.be.undefined;
+            }, function (error){
+                expect(error).to.be.an.instanceof(Error);
+                // TODO: there is a 64 MB hard limit at least in chrome at:
+                // chromium//extensions/renderer/messaging_util.cc:
+                // kMaxMessageLength
+                // The error will be a browser error, not from gnupg or from
+                // this library
+                done();
+            });
+    }).timeout(8000);
+
+
+});
diff --git a/lang/js/BrowserTestExtension/tests/inputvalues.js b/lang/js/BrowserTestExtension/tests/inputvalues.js
new file mode 100644 (file)
index 0000000..730e48a
--- /dev/null
@@ -0,0 +1,453 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+const inputvalues = {// eslint-disable-line no-unused-vars
+    encrypt: {
+        good:{
+            data : 'Hello World.',
+            // Fingerprint of a key that has been imported to gnupg
+            // (i.e. see testkey.pub; testkey.sec)
+            fingerprint : 'D41735B91236FDB882048C5A2301635EEFF0CB05',
+            fingerprint_mixedcase: 'D41735B91236fdb882048C5A2301635eeFF0Cb05',
+            data_nonascii: '¡Äußerste µ€ før ñoquis@hóme! Добрый день',
+
+            // used for checking encoding consistency in > 2MB messages.
+            data_nonascii_32: [
+                'K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€K€',
+                'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€',
+                '€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€',
+                '²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³²³',
+                'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€A€µ€µ€µ€µ€',
+                'µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µ€µAµ€µ€µ€µ€',
+                'üüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü',
+                'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€',
+                'µAAAAµAAAAAAAAAAAAAAAAAAAAAAAAA€',
+                'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAµ€',
+                'µAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA°',
+                '€AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA€',
+                'µ||||||||||||||||||||||||||||||€',
+                'æſæſ³¼„¬“³³¬“¬½”æſæſ³¼„¬“³³¬“¬½”'
+            ]
+        },
+        bad: {
+            // valid Hex value, but not usable (not imported to gnupg, or
+            // bogus fingerprint)
+            fingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A'
+        }
+    },
+
+    signedMessage: {
+        good: '-----BEGIN PGP SIGNED MESSAGE-----\n' +
+        'Hash: SHA256\n' +
+        '\n' +
+        'Matschige Münsteraner Marshmallows\n' +
+        '-----BEGIN PGP SIGNATURE-----\n' +
+        '\n' +
+        'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' +
+        'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' +
+        'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' +
+        'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' +
+        'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' +
+        'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' +
+        'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' +
+        '=ioB6\n' +
+        '-----END PGP SIGNATURE-----\n',
+        bad: '-----BEGIN PGP SIGNED MESSAGE-----\n' +
+        'Hash: SHA256\n' +
+        '\n' +
+        'Matschige Münchener Marshmallows\n' +
+        '-----BEGIN PGP SIGNATURE-----\n' +
+        '\n' +
+        'iQEzBAEBCAAdFiEE1Bc1uRI2/biCBIxaIwFjXu/wywUFAltRoiMACgkQIwFjXu/w\n' +
+        'ywUvagf6ApQbZbTPOROqfTfxAPdtzJsSDKHla6D0G5wom2gJbAVb0B2YS1c3Gjpq\n' +
+        'I4kTKT1W1RRkne0mK9cexf4sjb5DQcV8PLhfmmAJEpljDFei6i/E309BvW4CZ4rG\n' +
+        'jiurf8CkaNkrwn2fXJDaT4taVCX3V5FQAlgLxgOrm1zjiGA4mz98gi5zL4hvZXF9\n' +
+        'dHY0jLwtQMVUO99q+5XC1TJfPsnteWL9m4e/YYPfYJMZZso+/0ib/yX5vHCk7RXH\n' +
+        'CfhY40nMXSYdfl8mDOhvnKcCvy8qxetFv9uCX06OqepAamu/bvxslrzocRyJ/eq0\n' +
+        'T2JfzEN+E7Y3PB8UwLgp/ZRmG8zRrQ==\n' +
+        '=ioB6\n' +
+        '-----END PGP SIGNATURE-----\n'
+    },
+    encryptSignedMessage: '-----BEGIN PGP MESSAGE-----\n'+
+        '\n'+
+        'hQEMA6B8jfIUScGEAQf/bmQ+xNMGTjPvQCktkxR4Svt2dVNVdSzKsCmvSv24QOQF\n'+
+        'yBMK5w51S/6DTdiZI12IYD7hjvkr9NqxXXupjrVKwqEVpg4Pkwckac0OcElJIBsL\n'+
+        '3htr4iYsr8dhSgSS4BO0azcu4wZQTXy5v2P7yYPECMEagNEXnW+tE7sHLCq8Ysqz\n'+
+        'LVxG0R0IUijKeEd3xQC2Tt20e1Z1j5tnqaPhE/9Smqf5OjSUDqpXxvRnSNRk/zEs\n'+
+        'cGVgCF+cv68nUJM9lwEAbBQChplwL6ebnhunC6DsRCxnjLHVyKm127hmhSiMGC0e\n'+
+        'Ns31mGeP1dxpDv6Gi2/oKmq67vG3i4fKeckj7bt30tLA1wH0Qn5Mn6Tzxzve0W0q\n'+
+        'Ghqn9PY9qNK8EkrkzqaFk9dzu5tfSbaJBLS/uIhX2Wj70EMEBbFSkN0qlgOfLgGw\n'+
+        '5mwRvCgj4nvV1ByFhnx7uwgQixvOwLH4JLKvwCQpJm+O2R0eC7M6CzR/b9iL/oaO\n'+
+        'JTkoD9hcLhxF7j+3ZYg7rbNwofuHST097vFjzItsucb0jHOzjlkCqbhdczICILTa\n'+
+        'H76Q6YGdMLyG9a3s4yZUMruaeQyWGeXlryzLDvdEoSgoD5YrolsFOM+Z2apbzVs2\n'+
+        'k5CltwtanjjWGnpAqSyr49C6CSU8G1QHpNygx5frtAS8bojR2ovB9OJp2wUklDvC\n'+
+        'LtU7dLpTY/BIvfB1vzwcW/aNgmPadNHX8mAzlqTQJjeLoo69Wp804t+u36sgfd/J\n'+
+        'ser7vdJJUm+86Q9csefItvFmHhqjMg5XXHoa8WZWJOHIQMxZkaIwKAzcEt/oEOdJ\n'+
+        'rbVNVabhTdbmS5I1ok16wg5jMF07ZDM7nXWMcQNjwT646XKP+pp2N6YQROVidNXj\n'+
+        'COyRyiXE/csr\n'+
+        '=Ik7G\n'+
+        '-----END PGP MESSAGE-----\n',
+    someInputParameter: 'bad string',
+
+    publicKeyNonAscii: {
+        userid: 'Müller €uro',
+        key: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + '\n' +
+          'mQENBFt2/VIBCADIWBIMxExZlHda4XIVnM9nsIfUYLebJSC/krEriyWgzytU8/fQ\n' +
+          'S05cfnYx7RXvOOq4k8aa7mu80ovg3q77idXauLreAUwng4Njw0nMxWq/vtoMiZ60\n' +
+          '9f8EmfthZophhkQF2HIPHyqXMDZzMLWv4oTr2UJ9BKudL1XtbK51y9TbiyfQygBl\n' +
+          '8bl+zrOo70/dN6aunvuo6Hlu5cEzkj2QrzZlqTdfG5qv6KVEMut1eAbxZAmvSnna\n' +
+          'R4wqiRCT3/eRXGJbDL/8GaCEYkwi9FBrimjOTV0MpcLNwAU4aGfDxMUsxML9xJ+/\n' +
+          '/6GFxzYf7Lmk5UhvoewR58uQkHkTVPjZ9hXZABEBAAG0KE3DvGxsZXIg4oKsdXJv\n' +
+          'IDxtdWVsbGVyZXVyb0BleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQQVNixp3XT/DuGT\n' +
+          'F4MFmkL4L5UZdAUCW3b9UgIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIX\n' +
+          'gAAKCRAFmkL4L5UZdAhiCACowW1aC8DYGtJyAaBO2MqWhyw1wVCbQN9uFsQZPydY\n' +
+          'v3BEbCDrRc0HyfV1PVoRQsgkiNMes1S2tz2IMJoEOTMaz3WjPM8yK0dDbo5sfx/o\n' +
+          '/XaXeKhyYNqRkz2dPzorg1sHyHe0ki/HoQiANEJ8mByMtlwnPWlhnINAX+27eL17\n' +
+          'JC8juhBYUchqoIBAl+ajYKSThdLzrUkcL7QfJjZb3pPytJSTTdFc0rD6ERDbfXXc\n' +
+          '/vnE2SDYme+XXn7H5tNe67tPM8M96vbp+uM+n2t/z96C+Pqb6GJFMBa35PM+/qQO\n' +
+          'yr0I2oaQnTecx2AfBXGZvd81wMYikAJ9rAOWyMQZHJWouQENBFt2/VIBCADXCvKD\n' +
+          '3wRWCOzRWtLTs7hpAjCDxp6niPkwxKuUf9r/sUPmn0pWdZHYlbPDev9psN9bnJ+C\n' +
+          '+wzzPZ1zgSYKIAN0IMoh0L7BRAoau7VWQ3Q7hP6HIbdzOTEGyklSoh9pIh6IlwZZ\n' +
+          'XfPlFlnn7FeH1UeA711E174SUpDRKYSfT+mFObQUuQewGi9QC3gBsz5MPLQQLzML\n' +
+          'yimIOT+8i64fHHSKChw5ZDckBffej31/YHPQ7+JsWFV+G/6xDfbwnaFZFAUwo+1L\n' +
+          '4w9UiMyCNkIWCkulzJ2Hbz66xzFMi/8zMYxr08Af+PpsXaWTQHAa5V4GNJSInDEB\n' +
+          '7gy/CGLcY90EozoDABEBAAGJATwEGAEIACYWIQQVNixp3XT/DuGTF4MFmkL4L5UZ\n' +
+          'dAUCW3b9UgIbDAUJA8JnAAAKCRAFmkL4L5UZdPqoB/9kpqxqa82k7JMcq7UiwQY7\n' +
+          'CdqCUPKF88ciOWKBpZmpl8V7zgM7kEXwmM6ocHcznXi8xM7eOfDIJcBeqFVIE4OT\n' +
+          '63OCMuvZICM9Kiu48wLNAw5W/YGAOBH7ySQzZM2XrtvwfFtJ3lR00t5f4FVtriA5\n' +
+          '47BjYYG5tTdJc8HwEHs045S99xKCWqwuDgO9qskIi6iPePUkuhpaVBLuEj2Goku6\n' +
+          'i8aql/vKYQS67L7UHJiEbjLe+wP9k3FvWUFTx39lAubsDzb4Abhe+qRqs2TKD7Go\n' +
+          'k35ZriRIYllmx4c9KyWL7Mvzcp+84Sq9LeMfsN4JstBDJ7jn6g19SjO5dmtxSuP0\n' +
+          '=zZSJ\n' +
+          '-----END PGP PUBLIC KEY BLOCK-----\n'
+    }
+};
+
+// (Pseudo-)Random String covering all of utf8.
+function bigString (length){// eslint-disable-line no-unused-vars
+    let arr = [];
+    for (let i= 0; i < length; i++){
+        arr.push(String.fromCharCode(
+            Math.floor(Math.random() * 10174) + 1)
+        );
+    }
+    return arr.join('');
+}
+
+function fixedLengthString (megabytes){// eslint-disable-line no-unused-vars
+    let maxlength = 1024 * 1024 * megabytes / 2;
+    let uint = new Uint8Array(maxlength);
+    for (let i = 0; i < maxlength; i++){
+        uint[i] = Math.floor(Math.random()* 256);
+    }
+    let td = new TextDecoder('ascii');
+    let result = td.decode(uint);
+    return result;
+}
+
+// (Pseudo-)Random Uint8Array, given size in Megabytes
+function bigUint8 (megabytes){// eslint-disable-line no-unused-vars
+    let maxlength = 1024 * 1024 * megabytes;
+    let uint = new Uint8Array(maxlength);
+    for (let i= 0; i < maxlength; i++){
+        uint[i] = Math.floor(Math.random() * 256);
+    }
+    return uint;
+}
+
+// (Pseudo-)Random string with very limited charset
+// (ascii only, no control chars)
+function bigBoringString (megabytes){// eslint-disable-line no-unused-vars
+    let maxlength = 1024 * 1024 * megabytes;
+    let string = [];
+    let chars =
+        ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    for (let i= 0; i < maxlength; i++){
+        string.push(chars[Math.floor(Math.random() * chars.length)]);
+    }
+    return string.join('');
+}
+
+// Some String with simple chars, with different characteristics, but still
+// expected to occur in an averag message
+// eslint-disable-next-line no-unused-vars
+function slightlyLessBoringString (megabytes, set){
+    let maxlength = 1024 * 1024 * megabytes;
+    let string = [];
+    let chars = '';
+    if (set ===1 ) {
+        chars = '\n"\r \'';
+    } else if (set === 2 ) {
+        chars = '()=?`#+-{}[]';
+    } else if (set === 3){
+        chars = '^°/';
+    } else if (set ===4) {
+        chars = 'äüßµüþÖ~ɁÑ||@';
+    } else {
+        chars = '*<>\n"\r§$%&/()=?`#+-{}[] \'';
+    }
+    for (let i= 0; i < maxlength; i++){
+        string.push(chars[Math.floor(Math.random() * chars.length)]);
+    }
+    return string.join('');
+}
+
+// Data encrypted with testKey
+const encryptedData =// eslint-disable-line no-unused-vars
+    '-----BEGIN PGP MESSAGE-----\n' +
+    '\n' +
+    'hQEMA6B8jfIUScGEAQgAlANd3uyhmhYLzVcfz4LEqA8tgUC3n719YH0iuKEzG/dv\n' +
+    'B8fsIK2HoeQh2T3/Cc2LBMjgn4K33ksG3k2MqrbIvxWGUQlOAuggc259hquWtX9B\n' +
+    'EcEoOAeh5DuZT/b8CM5seJKNEpPzNxbEDiGikp9DV9gfIQTTUnrDjAu5YtgCN9vA\n' +
+    '3PJxihioH8ODoQw2jlYSkqgXpBVP2Fbx7qgTuxGNu5w36E0/P93//4hDXcKou7ez\n' +
+    'o0+NEGSkbaY+OPk1k7k9n+vBSC3F440dxsTNs5WmRvx9XZEotJkUBweE+8XaoLCn\n' +
+    '3RrtyD/lj63qi3dbyI5XFLuPU1baFskJ4UAmI4wNhdJ+ASailpnFBnNgiFBh3ZfB\n' +
+    'G5Rmd3ocSL7l6lq1bVK9advXb7vcne502W1ldAfHgTdQgc2CueIDFUYAaXP2OvhP\n' +
+    'isGL7jOlDCBKwep67ted0cTRPLWkk3NSuLIlvD5xs6L4z3rPu92gXYgbZoMMdP0N\n' +
+    'kSAQYOHplfA7YJWkrlRm\n' +
+    '=zap6\n' +
+    '-----END PGP MESSAGE-----\n';
+
+const ImportablePublicKey = {// eslint-disable-line no-unused-vars
+    fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A',
+    key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
+    '\n' +
+    'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' +
+    'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' +
+    'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' +
+    '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' +
+    'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' +
+    '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' +
+    'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' +
+    'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' +
+    'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' +
+    'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' +
+    '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' +
+    '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' +
+    'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' +
+    'uzEXPGW+sq0WRp3hynn7kVP6QQYvuQENBFsPvK0BCACwvBcmbnGJk8XhEBRu2QN3\n' +
+    'jKgVs3CG5nE2Xh20JipZwAuGHugDLv6/jlizzz5jtj3SAHVtJB8lJW8I0cNSEIX8\n' +
+    'bRYH4C7lP2DTb9CgMcGErQIyK480+HIsbsZhJSNHdjUUl6IPEEVfSQzWaufmuswe\n' +
+    'e+giqHiTsaiW20ytXilwVGpjlHBaxn/bpskZ0YRasgnPqKgJD3d5kunNqWoyCpMc\n' +
+    'FYgDERvPbhhceFbvFE9G/u3gbcuV15mx53dDX0ImvPcvJnDOyJS9yr7ApdOV312p\n' +
+    'A1MLbxfPnbnVu+dGXn7D/VCDd5aBYVPm+5ANrk6z9lYKH9aO5wgXpLAdJvutCOL5\n' +
+    'ABEBAAGJATwEGAEIACYWIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUCWw+8rQIbDAUJ\n' +
+    'A8JnAAAKCRD2N5DBHmAnigMVB/484G2+3R0cAaj3V/z4gW3MRSMhcYqEMyJ/ACdo\n' +
+    '7y8eoreYW843JWWVDRY6/YcYYGuBBP47WO4JuP2wIlVn17XOCSgnNjmmjsIYiAzk\n' +
+    'op772TB27o0VeiFX5iWcawy0EI7JCb23xpI+QP31ksL2yyRYFXCtXSUfcOrLpCY8\n' +
+    'aEQMQbAGtkag1wHTo/Tf/Vip8q0ZEQ4xOKTR2/ll6+inP8kzGyzadElUnH1Q1OUX\n' +
+    'd2Lj/7BpBHE2++hAjBQRgnyaONF7mpUNEuw64iBNs0Ce6Ki4RV2+EBLnFubnFNRx\n' +
+    'fFJcYXcijhuf3YCdWzqYmPpU/CtF4TgDlfSsdxHxVOmnZkY3\n' +
+    '=qP6s\n' +
+    '-----END PGP PUBLIC KEY BLOCK-----\n',
+
+    keyChangedUserId: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
+    '\n' +
+    'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' +
+    'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' +
+    'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' +
+    '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' +
+    'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' +
+    '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' +
+    'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' +
+    'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' +
+    'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' +
+    'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' +
+    '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' +
+    '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' +
+    'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' +
+    'uzEXPGW+sq0WRp3hynn7kVP6QQYvtCZTb21lb25lIEVsc2UgPHNvbWVvbmVlbHNl\n' +
+    'QGV4YW1wbGUub3JnPokBVAQTAQgAPhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJb\n' +
+    'D705AhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEPY3kMEeYCeK\n' +
+    'aIUH/2o+Ra+GzxgZrVexXLL+FCSmcu0cxeWfMhL8jd96c6uXIT21qQMRU2jgvnUp\n' +
+    'Wdi/BeLKp5lYwywm04PFhmRVxWXLuLArCsDu+CFys+aPeybnjikPBZov6P8/cZV3\n' +
+    'cd6zxFvqB9J15HjDMcl/r5v6d4CgSLKlFebrO5WKxHa6zGK9TRMQrqTu1heKHRf6\n' +
+    '4+Wj+MZmYnPzEQePjiBw/VkJ1Nm37Dd24gKdcN/qJFwEOqvbI5RIjB7xqoDslZk9\n' +
+    'sAivBXwF0E9HKqvh4WZZeA7uaWNdGo/cQkD5rab5SdHGNPHLbzoRWScsM8WYtsME\n' +
+    'dEMp5iPuG9M63+TD7losAkJ/TlS5AQ0EWw+8rQEIALC8FyZucYmTxeEQFG7ZA3eM\n' +
+    'qBWzcIbmcTZeHbQmKlnAC4Ye6AMu/r+OWLPPPmO2PdIAdW0kHyUlbwjRw1IQhfxt\n' +
+    'FgfgLuU/YNNv0KAxwYStAjIrjzT4cixuxmElI0d2NRSXog8QRV9JDNZq5+a6zB57\n' +
+    '6CKoeJOxqJbbTK1eKXBUamOUcFrGf9umyRnRhFqyCc+oqAkPd3mS6c2pajIKkxwV\n' +
+    'iAMRG89uGFx4Vu8UT0b+7eBty5XXmbHnd0NfQia89y8mcM7IlL3KvsCl05XfXakD\n' +
+    'UwtvF8+dudW750ZefsP9UIN3loFhU+b7kA2uTrP2Vgof1o7nCBeksB0m+60I4vkA\n' +
+    'EQEAAYkBPAQYAQgAJhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJbD7ytAhsMBQkD\n' +
+    'wmcAAAoJEPY3kMEeYCeKAxUH/jzgbb7dHRwBqPdX/PiBbcxFIyFxioQzIn8AJ2jv\n' +
+    'Lx6it5hbzjclZZUNFjr9hxhga4EE/jtY7gm4/bAiVWfXtc4JKCc2OaaOwhiIDOSi\n' +
+    'nvvZMHbujRV6IVfmJZxrDLQQjskJvbfGkj5A/fWSwvbLJFgVcK1dJR9w6sukJjxo\n' +
+    'RAxBsAa2RqDXAdOj9N/9WKnyrRkRDjE4pNHb+WXr6Kc/yTMbLNp0SVScfVDU5Rd3\n' +
+    'YuP/sGkEcTb76ECMFBGCfJo40XualQ0S7DriIE2zQJ7oqLhFXb4QEucW5ucU1HF8\n' +
+    'UlxhdyKOG5/dgJ1bOpiY+lT8K0XhOAOV9Kx3EfFU6admRjc=\n' +
+    '=9WZ7\n' +
+    '-----END PGP PUBLIC KEY BLOCK-----\n'
+};
+
+/**
+ * Changes base64 encoded gpg messages
+ * @param {String} msg input message
+ * @param {Number} rate of changes as percentage of message length.
+ * @param {[Number, Number]} p begin and end of the message left untouched (to
+ * preserve) header/footer
+ */
+// eslint-disable-next-line no-unused-vars
+function sabotageMsg (msg, rate = 0.01, p= [35,35]){
+    const iterations = Math.floor(Math.random() * msg.length * rate) + 1;
+    const base64_set =
+        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/';
+    for (let i=0; i < iterations; i++){
+        let str0, str1, str2;
+        const chosePosition = function (){
+            let position =
+                Math.floor( Math.random() * (msg.length - p[0] + p[1]))
+                + p[0];
+            str1 = msg.substring(position,position+1);
+            if (str1 === '\n'){
+                chosePosition();
+            } else {
+                str0 = msg.substring(0,position);
+                str2 = msg.substring(position +1);
+            }
+        };
+        chosePosition();
+        let new1 = function (){
+            let n = base64_set[Math.floor(Math.random() * 64)];
+            return (n === str1) ? new1() : n;
+        };
+        msg = str0.concat(new1()).concat(str2);
+    }
+    return msg;
+}
+
+// a simple png
+// eslint-disable-next-line no-unused-vars
+const binaryData = {
+    base64:
+        'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAEwElEQVQ4y62R228b1xn' +
+        'E51z27I271K64okTqRkuOLRuNHcNBCqTwS//qvvYCtAmQNrETO7akyJJsipRIUeSSez' +
+        'vnfH1IkBboW5F5mpf5ATPDiAi/hTh+I8n/I/NrCcbY/4DI/GKY+E/CGLIWROCccQ7Or' +
+        'bVVXWmttTHKUY6UjuP8jJMggilhVjAluILwIUMwRlpTUVBVQWsoxT1PS3E7v7u6Ho0m' +
+        '48boOIz63V6WdsIgZIxJmBXyMyx/QjWEbCPYReuQRNsWhbm5tqMRFQVPU6x3JlZ/e/r' +
+        'm76+/OR6elbrKovT5/ae/f/L5YGcQeL5EOcHsa8z/hOY1eIbVF6b5o/YeNrnWFxf2+5' +
+        'fs5kY9PDKHdlgu/vrPv/357F95UxLo7G44nk+EEO2orbKuRHOL1feo/wJ8hFEmn5dNv' +
+        'JB8MRP1xYV79j68HrEss1ub15Ph8dXpoil+3lqTPZ+PXp7+8On9x51kXcLUsAtgARBR' +
+        'ZfRpnn93voouxsJM5ptJsp1lfn+HwtDOpAX77/sM2bvVfFUW2hgJJ4bTQ9UH5YA1psz' +
+        'zyYery69ezaihzx883BgM2GDgxFGqq92N3YvltDA1CGDwJU8j3/UYZ1rCTRE+QfMBpQ' +
+        'BdgRzwAFwyKSAV1jvU38HGptvy+4J/8ej5sih+uHq31HUg+ePexvNHg27mcGch4aZY+' +
+        '4wsB23Z4j1XCNeOtr0dG6Eyst3tFV47ZwpQcZQcDR6YUnfD9UWxSBL/06Peo0+67WRC' +
+        'cinBBKmMnE8s98gcQjlh1Nn2sqArxiusyP1Yu8WdyUzVYhVpkiQ2/PX7W4PdQXdvP1x' +
+        'Lx0x9Z8BkXVVU1rQgGjZ2VPNOy9nrxPGmjdRkUs8XuqhEAcOIrCzG1zdv3/x4fn7+2b' +
+        'OngTMIPZfL0mJIsHJyfc3LUlyN5Jt37MfX4uixs96TmcO5MpxqggWKhlaNiUBE1pC2M' +
+        'BaWOOPM40gY7hEq+fLVKx/olOVa07hEgjEwzhnzlYg9GXs2r21L8dgXLddGaWtwtBv1' +
+        '/LgbWE9rzn2khK7FWPT7/Xy5bIVBO03U5qbc2+fdDZkkJBUDlGCh4h2fd0LpKlOw6VQ' +
+        'MF+FQxwvlyziMAyXBJxbH8vjkhKw92N8peplyOcWRE7mMk6t4J1KRJ1arQsIwW1qqKs' +
+        'ymwekV+wcTTKLeNt0OdRwowJEvXrxoJ+1wM8kTNncWQpVtIVLWapEbKIebmvQyv72pl' +
+        '3da2VJMSzOu1RWAnG7KpjC1VOhK/lR++YcvucPIa6biw6V+zSy7J5/6uh3IlrEMpilm' +
+        'N9fvXs0uT4KtnuoHvfBBg4Y46ztHbRvLleSrFpQrDw4OGqpv9Xi0qqf1R1hs6oPaVNp' +
+        'oznlT1+VycXd5cvv2a6JnycaTA/93mRqAsYytp0UoRzO7mLPQlVEUNaY2dZ3RVoVnAN' +
+        'ZExhpRmrJmDYyRXtDePgDQ3r63nuwEaUaKM8CtyZlN6fhEv33DuxtScAHmhIh62I+cx' +
+        'BjDaye/XU3zO85E6HvK9bODo7X+nhdGYdrx/VgoF9ZCL20zqW9v6f17ayEBCCYDGTpc' +
+        'Rc5aXdez5d3Hy/PT41MAh4eH+7s7ydY2Z8xRSjiOVC5jDLDkuBRFYm8fAEvSfwPxgHl' +
+        'kzr8e4QAAAABJRU5ErkJggg==',
+    encryptedArmored: '-----BEGIN PGP MESSAGE-----\n' +
+        '\n' +
+        'hQEMA6B8jfIUScGEAQgA3m9gtJswzXITlX3yJslszQSBBtHb3jTquF6ZoB5NPxMC\n' +
+        '5sX1WkjemyjYOy/pscLb8JRedj+owfaHYAGed1h+SM8iVHrHSVImbq+okzKbkYTB\n' +
+        'lYoBK63y2WHjMdMEjHHWl5CPfyzmAutwix0sWGcDLwNdO31eUdKHtjPi69k+IEw2\n' +
+        'hJ0+hdulttJk5jvNtW0mmuWjzKPgekZX7AFZnO+cJEgdDXEZ9oOD3/HxM8Rw/Kba\n' +
+        't7LWW/h3WxTzUKWrCO7SvcX27MGt94I1ff/2IAPb3/ZiskGrEwN6TYv8x0OaC6Xy\n' +
+        'TFxVCSGbXo+dSBFUfMMnPCiY6W8DOrchwDK2sBoGO9LqAUkpzm0tYSYt5v8/vXvO\n' +
+        'uXG+u8lG9I/TqnVDdZXXedBdDVFxv03K35FQU48K+AuBJLakdMfCfK3AvYRBgffu\n' +
+        'NbdnQUTs8AUM3qNgN95JRM/edhir0Tfz981R8dTYbxRwuosR//yNxCXA1UUg0UeD\n' +
+        'feC+AB6lRNt/P2qpt4pio4fflcnTqL5lFpAcktdvX6scKxe/GFR+ylafZMykm2A/\n' +
+        '+UMfTTjZKm6MqsTnk2fOaHM4AIZBebx7j4cYqV+vyaxla4drZ+7w4P4XveCIS8eD\n' +
+        'VkIGRhJuRtXMFTWrV4z8BIDhUw68/h0rClC5mucdEJMUPaEK65mECxMTEggomz5/\n' +
+        'BaSVDmNUoLHwqPV1fCUG+FtDhNHtqLDszlhEGNDa2NU4paGRaxJzS3IZExlhbxFX\n' +
+        'pNOWS1W/gEblkzslWbOq3Gg95/MjJL8tEhlAuGnlFQqa2ZZ9/QgAujSaSnY5bQFe\n' +
+        '+riqxkex8YA+2v5yfhwVBF1W4TxvkP/9tL4xw/tepibwZrAEJLih+Cus6X+OC/Qw\n' +
+        'qUe2ROmHwoM83iJdsrnoGaenVCBWBzSsTjVauRWD7lPT1Hgwavgz9J35mKjfyayx\n' +
+        'XJxbFnHgvsRPOE5usYEsWQYrPQoDin9lpaXq5d+D3QFi/weQSVvKcC6a7jjNxeYS\n' +
+        'KCc9lac+qBCLXg8F9Ff2Szkr7XuamMvHbd2FAQFiTQ5zFcrvOL8V8VuhyUERFhJE\n' +
+        '4xCFq/vwhC3v7+aRWhpBfRvb5IE45fHTCZsvXt8U4YdzaL/OiDzv+/S0xHda6cJx\n' +
+        '3ZWn7A5KQBUDvbqd1FNtjMj7yf6SIcM0OMLRulJ1Qkd7OH+9JluTu0FLw0P7AupF\n' +
+        'BW2O4UUZY4K56W/wK/Je29RSd4/EmnFRBBYj6VvqY2izxCWEiwvKz0BA/+zabUol\n' +
+        'eBnHXP3ATKFthBRGoN9kkCkSpoz4t+QTlUazGqJrTX57vjA+Gxdjc9Vhn4Q/Ra2f\n' +
+        'c4a01h8fRP+IDVLFzh+AfcQ0Q6Fr/3+D9KUj/poS2O3G4ACfIRm8L2zaVGnfEmiT\n' +
+        '7T/8ZJBQrHkncXHCbbocB1g0PSFoDrXLafNKaCS2EItk+FBUF3EQKfc/FxUwFXG6\n' +
+        'WhPptorUXx+TCcVuoR0ifKEnLEBnhhlwFM09gHRLxPDenSj0WIv/Nm+HP1Nd2410\n' +
+        'KvcEVLo/HyJDg7GEIi6Q+EZPasCvI7vxKLBBavrvBAZwRjA2tYVYYadUnlpuMdB3\n' +
+        '97iY+tPZ31OjBLl7Ho3BlA7W3yd8ptuqAcvhgBpBGBDc4rW02Ohb9DcTpzZioQIl\n' +
+        'Ih6p2vgIOZKz2cnJ+0sXf8xiRyPfkJE71eRkpQC9bdnddENEROXzAx80wP7kkajE\n' +
+        'W8CD9LLMZC65+X4sg+0g+RDnCqgYk2XoKnBaJC1qdaMQ3OrdGoPQsjk1Bq+qyk9Q\n' +
+        '7CqjzK897IdV5g+OBRbHi78gvF04Ruqgnq9lPz0OfjAxlDBoGYfAUsbRJKIXbGPq\n' +
+        'e57SbUkrsQXYLlbsj0vFb5z/MTveFAarbJ1/TPUYuvf9Z9w7S3qz/H8fc72fDuYM\n' +
+        'oI36H4NIou/7Jh+92CA27i+eamIZ8p5Ql28rEHpNB1qfIFoO0x1u7/1P2Mq7CbfF\n' +
+        'H0bg2KrSb5VkDnfHAnAF/hkt4K1yD0RcSD1abkC07cEzRmIQ95mtuX08sia3Yn0C\n' +
+        'dwc4gOeR+oiHxAsyV3wvrm8/w4AAqSbBqtxafAJ44dXJsyoRSRt1vkPta1IUUFZ6\n' +
+        'I+jv5nMv16jaJq6IpsI5ujxl/tKbniWC0Jjw5LqoT3beWaZ91iU=\n' +
+        '=AkaP\n' +
+        '-----END PGP MESSAGE-----\n'
+};
+
+// eslint-disable-next-line no-unused-vars
+const filename_files = [{
+    name: 'Example-1234.txt',
+    data: '-----BEGIN PGP MESSAGE-----\n' +
+        '\n' +
+        'hQEMA6B8jfIUScGEAQf/Ylt9GDcv/PGjX8v8CBWIeetzD7DpB8c5dZu57rPOhF7a\n' +
+        'gZ5wUCNwuZ5jSnPh/MAH1amr9AEHhW28JlHq+Lpoohl50iNFQy01M+Kxh1LmSKup\n' +
+        'hFQl3Lu+NewdShq/RwNc9+qdTAnCdwjGJ+SxODfo73cflLl9SSPJ7k29bdUUL1mp\n' +
+        'aGlYdecTB6lcz4pCNOyyGryDBJQcS5ObulpN4zvhSfFzT27GQFmQPElm7CTdGOf0\n' +
+        '5VUxFe0TqRmdJ9LzVuOVZB7x8E0BpuQYpPd88emS+KOozx4KWu0IakdQ4QBY0av5\n' +
+        'ZID2rgM640Z4T8kXgGZq2qFN1Ap5X3iwfjkEHaJIP9JXAb86F8IP7nLrxzN2V0eM\n' +
+        '3v0+1o0HJd/E4LPeXHXCaNDaJOr8rviOCLwoFvCJ9E10ZASLyqOXzhlW9Tkvxrjl\n' +
+        'ldeXQI8Fp6oWPfvW8qGQ917mzxuoQYGn\n' +
+        '=993W\n' +
+        '-----END PGP MESSAGE-----\n'
+}, {
+    name: 'Example-@€µ2äüß.txt',
+    data: '-----BEGIN PGP MESSAGE-----\n'+
+        '\n'+
+        'hQEMA6B8jfIUScGEAQgAiX5vBNJGPYvljleo/7nkee4mGsFL1ROXLOs7sUlBImFm\n'+
+        'axQ0PAtVsX9NvDY70Tj5EIaGmgQWr/WAnH5fuV+ctsZtPm/UsL2BhYgKz3cDcS2P\n'+
+        '1tni3WhHXVr8ldC3PePuEn0Wfy/wOS+y2FbkJOD9EqXeui06phB8ScGdF6se3AcA\n'+
+        'lNo6bFeURgK6NhIYgibKbybAr1+D/zUvksn5xnLztBarVeJFOwAj8I+lthLpoyj2\n'+
+        'vUFu2qOlSOW/98Z0ZYDvRqnB5Mqmqsgf0cWl4Lwt0+GrdfzuB+479+ouIJCFUaIA\n'+
+        'JDoU8Ct0UwgAoYZmDkxBtjZALmf3dGqH1gjSe0UbDdJhAZ9h5rlC525JNOse0v21\n'+
+        'LdrDtwtiETFZ9ras8RelYeyYyE7PfhBxtmP5EBZUk7Be6JbD2vn5s2pgsbmBTzGJ\n'+
+        'AcxxSN6MbTvInIvC3GhSTs0mLiC4sToVoPp/F8tfQIGZWg==\n'+
+        '=V6wP\n'+
+        '-----END PGP MESSAGE-----\n'
+}, {
+    name: 'Example- äüüß.txt',
+    data: '-----BEGIN PGP MESSAGE-----\n' +
+        '\n' +
+        'hQEMA6B8jfIUScGEAQf9H7CbkI952WbUqkuYIlgKri+Tr+G+9m1GN/mKh82GnwfZ\n' +
+        '8JekOOzdZ6BdCfyJohOSan959r1pOHJzj2sh+LitBbD02MDPg8BL14lUXfbUju7s\n' +
+        'eT5HuVDfnFWV2ThfEyVUNmAEaE57FwTzdO7vN1VYkkBNFC8pjCONQ6/iRWnDgUyB\n' +
+        'fJJSLkdFMDBgHSrEeSCyDP4P5rJyd/1JhqXXECLIMzIKWCUbvWNvKLfA71fhPbi3\n' +
+        'XzXLWhNKQWoMZsl2oEHJuPY7ez/KePJ07Km0gxcbBJhUGTRRNrHSjOxiaV7/TLp2\n' +
+        'O3U/GuPQ/eY4Xl3rE/cDaCjy2sdR4VyuxlbLeUVIvtJbAUzNkaibs9ydZshBj9UD\n' +
+        'x2JWCwkBa7Q1Mah9nciT8S2Co71dsVMdIc3VtsXUtlhomL1bHd8ipRhFSiqiyZM3\n' +
+        'Pih6tFUOcXuSaf0lv6FENXP+IThHiaujtjAbkA==\n' +
+        '=UxvV\n' +
+        '-----END PGP MESSAGE-----\n'
+}];
diff --git a/lang/js/BrowserTestExtension/tests/longRunningTests.js b/lang/js/BrowserTestExtension/tests/longRunningTests.js
new file mode 100644 (file)
index 0000000..534a95a
--- /dev/null
@@ -0,0 +1,58 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+/* global describe, it, before, expect, Gpgmejs */
+/* global bigString, inputvalues */
+
+describe('Long running Encryption/Decryption', function () {
+    let context = null;
+    const good_fpr = inputvalues.encrypt.good.fingerprint;
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+
+    for (let i=1; i < 101; i++) {
+        it('Successful encrypt/decrypt completely random data '
+            + (i) + '/100', function (done) {
+            const data = bigString(2*1024*1024);
+            context.encrypt({ data: data, publicKeys: good_fpr })
+                .then(function (answer){
+                    expect(answer).to.not.be.empty;
+                    expect(answer.data).to.be.a('string');
+                    expect(answer.data).to.include('BEGIN PGP MESSAGE');
+                    expect(answer.data).to.include('END PGP MESSAGE');
+                    context.decrypt({ data: answer.data })
+                        .then(function (result){
+                            expect(result).to.not.be.empty;
+                            expect(result.data).to.be.a('string');
+                            expect(result.data).to.equal(data);
+                            done();
+                        });
+                });
+        }).timeout(15000);
+    }
+
+});
diff --git a/lang/js/BrowserTestExtension/tests/signTest.js b/lang/js/BrowserTestExtension/tests/signTest.js
new file mode 100644 (file)
index 0000000..1e269e6
--- /dev/null
@@ -0,0 +1,64 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, expect, before, Gpgmejs */
+/* global bigString, inputvalues */
+
+describe('Signing', function () {
+    let context = null;
+    const good_fpr = inputvalues.encrypt.good.fingerprint;
+
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+
+    it('Sign a message', function (done) {
+        const data = bigString(100);
+        context.sign({ data: data, keys: good_fpr }).then(function (answer) {
+            expect(answer).to.not.be.empty;
+            expect(answer.data).to.be.a('string');
+            expect(answer.data).to.include('BEGIN PGP SIGNATURE');
+            expect(answer.data).to.include('END PGP SIGNATURE');
+            expect(answer.data).to.include(data);
+            done();
+        });
+    });
+
+    it('Detached sign a message', function (done) {
+        const data = bigString(100);
+        context.sign({ data: data, keys: good_fpr, mode: 'detached' })
+            .then(function (answer) {
+                expect(answer).to.not.be.empty;
+                expect(answer.data).to.be.a('string');
+                expect(answer.data).to.include(data);
+                expect(answer.signature).to.be.a('string');
+                expect(answer.signature).to.be.a('string');
+                done();
+            });
+    });
+
+});
diff --git a/lang/js/BrowserTestExtension/tests/startup.js b/lang/js/BrowserTestExtension/tests/startup.js
new file mode 100644 (file)
index 0000000..e7c7478
--- /dev/null
@@ -0,0 +1,47 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, expect, Gpgmejs, inputvalues */
+
+describe('GPGME context', function (){
+    it('Starting a GpgME instance', function (done){
+        let prm = Gpgmejs.init({ timeout: 2000 });
+        const input = inputvalues.someInputParameter;
+        prm.then(
+            function (context){
+                expect(context).to.be.an('object');
+                expect(context.encrypt).to.be.a('function');
+                expect(context.decrypt).to.be.a('function');
+                expect(context.sign).to.be.a('function');
+                expect(context.verify).to.be.a('function');
+                context.Keyring = input;
+                expect(context.Keyring).to.be.an('object');
+                expect(context.Keyring).to.not.equal(input);
+                expect(context.Keyring.getKeys).to.be.a('function');
+                expect(context.Keyring.getDefaultKey).to.be.a('function');
+                expect(context.Keyring.importKey).to.be.a('function');
+                expect(context.Keyring.generateKey).to.be.a('function');
+                done();
+            });
+    });
+});
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/tests/verifyTest.js b/lang/js/BrowserTestExtension/tests/verifyTest.js
new file mode 100644 (file)
index 0000000..c63f684
--- /dev/null
@@ -0,0 +1,90 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global describe, it, expect, before, bigString, inputvalues, Gpgmejs */
+
+
+
+describe('Verifying data', function () {
+    let context = null;
+    before(function (done){
+        const prm = Gpgmejs.init({ timeout: 2000 });
+        prm.then(function (gpgmejs){
+            context = gpgmejs;
+            done();
+        });
+    });
+    it('Successful verify message', function (done) {
+        const message = inputvalues.signedMessage.good;
+        context.verify({ data: message }).then(function (result){
+            expect(result.data).to.be.a('string');
+            expect(result.signatures.all_valid).to.be.true;
+            expect(result.signatures.count).to.equal(1);
+            expect(result.signatures.signatures.good).to.be.an('array');
+            expect(result.signatures.signatures.good.length).to.equal(1);
+            expect(result.signatures.signatures.good[0].fingerprint).to.be.a('string');
+            expect(result.signatures.signatures.good[0].valid).to.be.true;
+            done();
+        });
+    });
+
+    it('Successfully recognize changed cleartext', function (done) {
+        const message = inputvalues.signedMessage.bad;
+        context.verify({ data: message }).then(function (result){
+            expect(result.data).to.be.a('string');
+            expect(result.signatures.all_valid).to.be.false;
+            expect(result.signatures.count).to.equal(1);
+            expect(result.signatures.signatures.bad).to.be.an('array');
+            expect(result.signatures.signatures.bad.length).to.equal(1);
+            expect(result.signatures.signatures.bad[0].fingerprint)
+                .to.be.a('string');
+            expect(result.signatures.signatures.bad[0].valid)
+                .to.be.false;
+            done();
+        });
+    });
+
+    it('Encrypt-Sign-Verify random message', function (done) {
+        const message = bigString(2000);
+        let fpr = inputvalues.encrypt.good.fingerprint;
+        context.encrypt({ data: message, publicKeys: fpr })
+            .then(function (message_enc){
+                context.sign({ data: message_enc.data, keys: fpr })
+                    .then(function (message_encsign){
+                        context.verify({ data: message_encsign.data })
+                            .then(function (result){
+                                expect(result.data).to.equal(message_enc.data);
+                                expect(result.data).to.be.a('string');
+                                expect(result.signatures.all_valid).to.be.true;
+                                expect(result.signatures.count).to.equal(1);
+                                const arr = result.signatures.signatures.good;
+                                expect(arr).to.be.an('array');
+                                expect(arr.length).to.equal(1);
+                                expect(arr[0].fingerprint).to.equal(fpr);
+                                expect(arr[0].valid).to.be.true;
+                                done();
+                            });
+                    });
+            });
+    });
+});
\ No newline at end of file
diff --git a/lang/js/BrowserTestExtension/unittests.html b/lang/js/BrowserTestExtension/unittests.html
new file mode 100644 (file)
index 0000000..6f7da3f
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link href="libs/mocha.css" rel="stylesheet" />
+
+    </head>
+<body>
+    <h3>Unit tests</h3>
+    <div id="mocha"></div>
+    <script src="libs/mocha.js"></script>
+    <script src="libs/chai.js"></script>
+    <script src="setup_testing.js"></script>
+    <script src="libs/gpgmejs_unittests.bundle.js"></script>
+    <script src="rununittests.js"></script>
+    </body>
+</html>
diff --git a/lang/js/DemoExtension/Makefile.am b/lang/js/DemoExtension/Makefile.am
new file mode 100644 (file)
index 0000000..d6e87fd
--- /dev/null
@@ -0,0 +1,27 @@
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Intevation GmbH
+#
+# This file is part of gpgme.js.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+
+EXTRA_DIST = manifest.json \
+             popup.html \
+             entry.js \
+             maindemo.js \
+             mainui.html \
+             testicon.png \
+             ui.css
diff --git a/lang/js/DemoExtension/Makefile.in b/lang/js/DemoExtension/Makefile.in
new file mode 100644 (file)
index 0000000..1a379fd
--- /dev/null
@@ -0,0 +1,537 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Intevation GmbH
+#
+# This file is part of gpgme.js.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \  ]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs  ]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lang/js/DemoExtension
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+       $(top_srcdir)/build-aux/mkinstalldirs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+       $(top_srcdir)/m4/ax_pkg_swig.m4 \
+       $(top_srcdir)/m4/ax_python_devel.m4 \
+       $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \
+       $(top_srcdir)/m4/gnupg-ttyname.m4 \
+       $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \
+       $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+       $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+       $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \
+       $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \
+       $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/conf/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_FILEVERSION = @BUILD_FILEVERSION@
+BUILD_REVISION = @BUILD_REVISION@
+BUILD_TIMESTAMP = @BUILD_TIMESTAMP@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENABLED_LANGUAGES = @ENABLED_LANGUAGES@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@
+GLIBC21 = @GLIBC21@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@
+GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@
+GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@
+GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@
+GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@
+GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@
+GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@
+GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@
+GPGME_QT_LIBS = @GPGME_QT_LIBS@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@
+GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@
+GRAPHVIZ = @GRAPHVIZ@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@
+LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@
+LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@
+LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@
+LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@
+LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@
+LIBOBJS = @LIBOBJS@
+LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@
+LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@
+LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MOC = @MOC@
+MOC2 = @MOC2@
+NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PYTHON = @PYTHON@
+PYTHONS = @PYTHONS@
+PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@
+PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@
+PYTHON_LDFLAGS = @PYTHON_LDFLAGS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_SITE_PKG = @PYTHON_SITE_PKG@
+PYTHON_VERSION = @PYTHON_VERSION@
+QTCHOOSER = @QTCHOOSER@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SWIG = @SWIG@
+SWIG_LIB = @SWIG_LIB@
+SYSROOT = @SYSROOT@
+VERSION = @VERSION@
+VERSION_MAJOR = @VERSION_MAJOR@
+VERSION_MICRO = @VERSION_MICRO@
+VERSION_MINOR = @VERSION_MINOR@
+VERSION_NUMBER = @VERSION_NUMBER@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+emacs_local_vars_begin = @emacs_local_vars_begin@
+emacs_local_vars_end = @emacs_local_vars_end@
+emacs_local_vars_read_only = @emacs_local_vars_read_only@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = manifest.json \
+             popup.html \
+             entry.js \
+             maindemo.js \
+             mainui.html \
+             testicon.png \
+             ui.css
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+               && { if test -f $@; then exit 0; else break; fi; }; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/js/DemoExtension/Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --gnu lang/js/DemoExtension/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(DISTFILES)
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+       -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+       cscopelist-am ctags-am distclean distclean-generic \
+       distclean-libtool distdir dvi dvi-am html html-am info info-am \
+       install install-am install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-ps install-ps-am \
+       install-strip installcheck installcheck-am installdirs \
+       maintainer-clean maintainer-clean-generic mostlyclean \
+       mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+       tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lang/js/DemoExtension/entry.js b/lang/js/DemoExtension/entry.js
new file mode 100644 (file)
index 0000000..fd261a0
--- /dev/null
@@ -0,0 +1,30 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global chrome */
+
+document.addEventListener('DOMContentLoaded', function () {
+    chrome.tabs.create({
+        url: './mainui.html'
+    });
+});
diff --git a/lang/js/DemoExtension/maindemo.js b/lang/js/DemoExtension/maindemo.js
new file mode 100644 (file)
index 0000000..c992e7e
--- /dev/null
@@ -0,0 +1,123 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global document, Gpgmejs */
+
+document.addEventListener('DOMContentLoaded', function () {
+    Gpgmejs.init().then(function (gpgmejs){
+        document.getElementById('buttonencrypt').addEventListener('click',
+            function (){
+                let data = document.getElementById('inputtext').value;
+                let keyId = document.getElementById('pubkey').value;
+                gpgmejs.encrypt({ data: data, publicKeys: keyId, armor: true })
+                    .then(function (answer){
+                        if (answer.data){
+                            document.getElementById(
+                                'answer').value = answer.data;
+                        }
+                    }, function (errormsg){
+                        alert( errormsg.message);
+                    });
+            });
+
+        document.getElementById('buttondecrypt').addEventListener('click',
+            function (){
+                let data = document.getElementById('inputtext').value;
+                gpgmejs.decrypt({ data: data }).then(
+                    function (answer){
+                        if (answer.data){
+                            document.getElementById(
+                                'answer').value = answer.data;
+                        }
+                    }, function (errormsg){
+                        alert(errormsg.message);
+                    });
+            });
+
+        document.getElementById('getdefaultkey').addEventListener('click',
+            function (){
+                gpgmejs.Keyring.getDefaultKey().then(function (answer){
+                    document.getElementById('pubkey').value =
+                        answer.fingerprint;
+                }, function (errormsg){
+                    alert(errormsg.message);
+                });
+            });
+
+        document.getElementById('signtext').addEventListener('click',
+            function (){
+                let data = document.getElementById('inputtext').value;
+                let keyId = document.getElementById('pubkey').value;
+                gpgmejs.sign({ data: data, keys: keyId }).then(
+                    function (answer){
+                        if (answer.data){
+                            document.getElementById(
+                                'answer').value = answer.data;
+                        }
+                    }, function (errormsg){
+                        alert( errormsg.message);
+                    });
+            });
+
+        document.getElementById('verifytext').addEventListener('click',
+            function (){
+                let data = document.getElementById('inputtext').value;
+                gpgmejs.verify({ data: data }).then(
+                    function (answer){
+                        let vals = '';
+                        if (answer.all_valid === true){
+                            vals = 'Success! ';
+                        } else {
+                            vals = 'Failure! ';
+                        }
+                        vals = vals + (answer.count - answer.failures) + 'of '
+                            + answer.count + ' signature(s) were successfully '
+                            + 'verified.\n\n' + answer.data;
+                        document.getElementById('answer').value = vals;
+                    }, function (errormsg){
+                        alert( errormsg.message);
+                    });
+            });
+        document.getElementById('searchkey').addEventListener('click',
+            function (){
+                let data = document.getElementById('inputtext').value;
+                gpgmejs.Keyring.getKeys({
+                    pattern: data,
+                    prepare_sync: true,
+                    search: true }
+                ).then(function (keys){
+                    if (keys.length === 1){
+                        document.getElementById(
+                            'pubkey').value = keys[0].fingerprint;
+                    } else if (keys.length > 1) {
+                        alert('The pattern was not unambigious enough for a Key. '
+                        + keys.length + ' Keys were found');
+                    } else {
+                        alert('No keys found');
+                    }
+                }, function (errormsg){
+                    alert( errormsg.message);
+                });
+            });
+    });
+});
diff --git a/lang/js/DemoExtension/mainui.html b/lang/js/DemoExtension/mainui.html
new file mode 100644 (file)
index 0000000..c773c9b
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link rel="stylesheet" href="ui.css"/>
+        <script src="libs/gpgmejs.bundle.js"></script>
+        <script src="maindemo.js"></script>
+    </head>
+    <body>
+    <div>
+
+        <div class="left">
+            <ul>
+                <li>
+                    <span class="label">Input</span>
+                    <textarea rows="5" cols="65" id="inputtext" wrap="hard"></textarea>
+                </li>
+                <li>
+                    <span class="label">Fingerprint of Key to use: </span>
+                    <input type="text" id="pubkey" value="" />
+                        <button id="getdefaultkey">
+                            Set to default signing key
+                        </button>&nbsp;
+                        <button id="searchkey">
+                            Look up Key
+                        </button>
+                </li>
+            </ul>
+        </div>
+        <div class="right">
+            <ul>
+                    <li>
+                        <span class="label">Result</span>
+                        <textarea id="answer" rows="5" cols="65" wrap="hard"></textarea>
+                    </li>
+                </ul>
+        </div>
+    </div>
+    <div class="center">
+            <button id="buttonencrypt">Encrypt input text</button><br>
+            <button id="buttondecrypt">Decrypt input text</button><br>
+            <button id="signtext">Sign input text</button> <br>
+            <button id="verifytext">Verify input text</button><br>
+
+        </div>
+</body>
+</html>
diff --git a/lang/js/DemoExtension/manifest.json b/lang/js/DemoExtension/manifest.json
new file mode 100644 (file)
index 0000000..9e057b3
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "manifest_version": 2,
+
+  "name": "gpgme-json with native Messaging",
+  "description": "A simple demo application",
+  "version": "0.1",
+  "content_security_policy": "default-src 'self' filesystem:",
+  "browser_action": {
+    "default_icon": "testicon.png",
+    "default_title": "gpgme.js",
+    "default_popup": "popup.html"
+  },
+  "permissions": ["nativeMessaging", "activeTab"]
+}
diff --git a/lang/js/DemoExtension/popup.html b/lang/js/DemoExtension/popup.html
new file mode 100644 (file)
index 0000000..5007031
--- /dev/null
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src="entry.js"></script>
+    </head>
+    <body>
+    </body>
+</html>
\ No newline at end of file
diff --git a/lang/js/DemoExtension/testicon.png b/lang/js/DemoExtension/testicon.png
new file mode 100644 (file)
index 0000000..84284e0
Binary files /dev/null and b/lang/js/DemoExtension/testicon.png differ
diff --git a/lang/js/DemoExtension/ui.css b/lang/js/DemoExtension/ui.css
new file mode 100644 (file)
index 0000000..16dfb5a
--- /dev/null
@@ -0,0 +1,33 @@
+ul {
+    list-style-type: none;
+    padding-left: 0px;
+}
+
+ul li span {
+    float: left;
+    width: 120px;
+    margin-top: 6px;
+}
+
+div .left {
+    float: left;
+    align-items: stretch;
+    width: 40%;
+}
+div .center {
+    width: 50%;
+    align-content: space-between;
+}
+
+div .center button {
+    align-self: stretch;
+}
+div .right {
+    float: right;
+    align-items: stretch;
+    width: 40%;
+}
+
+div .bottom {
+    clear:both;
+}
\ No newline at end of file
diff --git a/lang/js/Makefile.am b/lang/js/Makefile.am
new file mode 100644 (file)
index 0000000..63cc41b
--- /dev/null
@@ -0,0 +1,32 @@
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+#
+# This file is part of gpgme.js.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+
+SUBDIRS = src BrowserTestExtension DemoExtension
+
+EXTRA_DIST = build_extensions.sh \
+             jsdoc.conf \
+             jsdoc_index.md \
+             .eslintrc.json \
+             package.json \
+             README \
+             unittest_inputvalues.js \
+             unittests.js \
+             webpack.conf.js \
+             webpack.conf_unittests.js
diff --git a/lang/js/Makefile.in b/lang/js/Makefile.in
new file mode 100644 (file)
index 0000000..0dbc0bc
--- /dev/null
@@ -0,0 +1,721 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+#
+# This file is part of gpgme.js.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \  ]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs  ]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lang/js
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+       $(top_srcdir)/build-aux/mkinstalldirs README
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+       $(top_srcdir)/m4/ax_pkg_swig.m4 \
+       $(top_srcdir)/m4/ax_python_devel.m4 \
+       $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \
+       $(top_srcdir)/m4/gnupg-ttyname.m4 \
+       $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \
+       $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+       $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+       $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \
+       $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \
+       $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/conf/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+       ctags-recursive dvi-recursive html-recursive info-recursive \
+       install-data-recursive install-dvi-recursive \
+       install-exec-recursive install-html-recursive \
+       install-info-recursive install-pdf-recursive \
+       install-ps-recursive install-recursive installcheck-recursive \
+       installdirs-recursive pdf-recursive ps-recursive \
+       tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive        \
+  distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+  $(RECURSIVE_TARGETS) \
+  $(RECURSIVE_CLEAN_TARGETS) \
+  $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+       distdir
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_FILEVERSION = @BUILD_FILEVERSION@
+BUILD_REVISION = @BUILD_REVISION@
+BUILD_TIMESTAMP = @BUILD_TIMESTAMP@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENABLED_LANGUAGES = @ENABLED_LANGUAGES@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@
+GLIBC21 = @GLIBC21@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@
+GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@
+GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@
+GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@
+GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@
+GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@
+GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@
+GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@
+GPGME_QT_LIBS = @GPGME_QT_LIBS@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@
+GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@
+GRAPHVIZ = @GRAPHVIZ@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@
+LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@
+LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@
+LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@
+LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@
+LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@
+LIBOBJS = @LIBOBJS@
+LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@
+LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@
+LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MOC = @MOC@
+MOC2 = @MOC2@
+NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PYTHON = @PYTHON@
+PYTHONS = @PYTHONS@
+PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@
+PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@
+PYTHON_LDFLAGS = @PYTHON_LDFLAGS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_SITE_PKG = @PYTHON_SITE_PKG@
+PYTHON_VERSION = @PYTHON_VERSION@
+QTCHOOSER = @QTCHOOSER@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SWIG = @SWIG@
+SWIG_LIB = @SWIG_LIB@
+SYSROOT = @SYSROOT@
+VERSION = @VERSION@
+VERSION_MAJOR = @VERSION_MAJOR@
+VERSION_MICRO = @VERSION_MICRO@
+VERSION_MINOR = @VERSION_MINOR@
+VERSION_NUMBER = @VERSION_NUMBER@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+emacs_local_vars_begin = @emacs_local_vars_begin@
+emacs_local_vars_end = @emacs_local_vars_end@
+emacs_local_vars_read_only = @emacs_local_vars_read_only@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = src BrowserTestExtension DemoExtension
+EXTRA_DIST = build_extensions.sh \
+             jsdoc.conf \
+             jsdoc_index.md \
+             .eslintrc.json \
+             package.json \
+             README \
+             unittest_inputvalues.js \
+             unittests.js \
+             webpack.conf.js \
+             webpack.conf_unittests.js
+
+all: all-recursive
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+               && { if test -f $@; then exit 0; else break; fi; }; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/js/Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --gnu lang/js/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+#     (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+       @fail=; \
+       if $(am__make_keepgoing); then \
+         failcom='fail=yes'; \
+       else \
+         failcom='exit 1'; \
+       fi; \
+       dot_seen=no; \
+       target=`echo $@ | sed s/-recursive//`; \
+       case "$@" in \
+         distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+         *) list='$(SUBDIRS)' ;; \
+       esac; \
+       for subdir in $$list; do \
+         echo "Making $$target in $$subdir"; \
+         if test "$$subdir" = "."; then \
+           dot_seen=yes; \
+           local_target="$$target-am"; \
+         else \
+           local_target="$$target"; \
+         fi; \
+         ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+         || eval $$failcom; \
+       done; \
+       if test "$$dot_seen" = "no"; then \
+         $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+       fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+       $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+       set x; \
+       here=`pwd`; \
+       if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+         include_option=--etags-include; \
+         empty_fix=.; \
+       else \
+         include_option=--include; \
+         empty_fix=; \
+       fi; \
+       list='$(SUBDIRS)'; for subdir in $$list; do \
+         if test "$$subdir" = .; then :; else \
+           test ! -f $$subdir/TAGS || \
+             set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+         fi; \
+       done; \
+       $(am__define_uniq_tagged_files); \
+       shift; \
+       if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+         test -n "$$unique" || unique=$$empty_fix; \
+         if test $$# -gt 0; then \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             "$$@" $$unique; \
+         else \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             $$unique; \
+         fi; \
+       fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+       $(am__define_uniq_tagged_files); \
+       test -z "$(CTAGS_ARGS)$$unique" \
+         || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+            $$unique
+
+GTAGS:
+       here=`$(am__cd) $(top_builddir) && pwd` \
+         && $(am__cd) $(top_srcdir) \
+         && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+       list='$(am__tagged_files)'; \
+       case "$(srcdir)" in \
+         [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+         *) sdir=$(subdir)/$(srcdir) ;; \
+       esac; \
+       for i in $$list; do \
+         if test -f "$$i"; then \
+           echo "$(subdir)/$$i"; \
+         else \
+           echo "$$sdir/$$i"; \
+         fi; \
+       done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+       -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+       @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+         if test "$$subdir" = .; then :; else \
+           $(am__make_dryrun) \
+             || test -d "$(distdir)/$$subdir" \
+             || $(MKDIR_P) "$(distdir)/$$subdir" \
+             || exit 1; \
+           dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+           $(am__relativize); \
+           new_distdir=$$reldir; \
+           dir1=$$subdir; dir2="$(top_distdir)"; \
+           $(am__relativize); \
+           new_top_distdir=$$reldir; \
+           echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+           echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+           ($(am__cd) $$subdir && \
+             $(MAKE) $(AM_MAKEFLAGS) \
+               top_distdir="$$new_top_distdir" \
+               distdir="$$new_distdir" \
+               am__remove_distdir=: \
+               am__skip_length_check=: \
+               am__skip_mode_fix=: \
+               distdir) \
+             || exit 1; \
+         fi; \
+       done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+       -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+       check-am clean clean-generic clean-libtool cscopelist-am ctags \
+       ctags-am distclean distclean-generic distclean-libtool \
+       distclean-tags distdir dvi dvi-am html html-am info info-am \
+       install install-am install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-ps install-ps-am \
+       install-strip installcheck installcheck-am installdirs \
+       installdirs-am maintainer-clean maintainer-clean-generic \
+       mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \
+       ps ps-am tags tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lang/js/README b/lang/js/README
new file mode 100644 (file)
index 0000000..fd95cc4
--- /dev/null
@@ -0,0 +1,116 @@
+gpgme.js - JavaScript for GPGME
+-------------------------------
+Initially developed for integration with the Mailvelope Web Extension.
+
+Overview
+--------
+
+gpgme.js is a javascript library for direct use of GnuPG in browsers.
+It interacts with GPGME through nativeMessaging and gpgme-json.
+
+It is meant to be distributed directly by its downstream users in
+their extension package. As such it is not integrated in the
+autotools build system. See build instructions below.
+
+
+gpgme-json
+----------
+
+gpgme-json (see core src/gpgme-json.c) the json to GPGME bridge is
+required as native messaging backend for gpgme.js to work.
+It needs to be installed and registered as native messaging
+backend with the browser.
+
+See gpgme-mozilla.json and gpgme-chrome.json examples in
+the top level doc/examples as example manifests.
+
+Any web extension using gpgme.js will need to be whitelisted in the manifest
+file by its id.
+
+Distributors are encouraged to create manifest packages for their
+distributions.
+
+
+Building gpgme.js
+-----------------
+
+gpgme.js uses webpack, and thus depends on Node.js for building.
+All dependencies will be installed (in a local subdirectory) with the command
+`npm install`.
+
+To create a current version of the package, the command is
+`npx webpack --config webpack.conf.js`.
+If you want a more debuggable (i.e. not minified) build, just change the mode
+in webpack.conf.js.
+
+
+Demo and Test WebExtension:
+---------------------------
+
+The Demo Extension shows simple examples of the usage of gpgme.js.
+
+The BrowsertestExtension runs more intensive tests (using the mocha and chai
+frameworks). Tests from BrowserTestExtension/tests will be run against the
+gpgmejs.bundle.js itself. They aim to test the outward facing functionality
+and API.
+
+Unittests as defined in ./unittests.js will be bundled in
+gpgmejs_unittests.bundle.js, and test the separate components of gpgme.js,
+which mostly are not exported.
+
+The file `build_extension.sh` may serve as a pointer on how to build and
+assemble these two Extensions and their dependencies. It can directly
+be used in most linux systems.
+
+The resulting folders can just be included in the extensions tab of the browser
+in questions (extension debug mode needs to be active). For chrome, selecting
+the folder is sufficient, for firefox, the manifest.json needs to be selected.
+Please note that it is just for demonstration/debug purposes!
+
+For the Extensions to successfully communicate with gpgme-json, a manifest file
+is needed.
+
+- `~/.config/chromium/NativeMessagingHosts/gpgmejson.json`
+
+In the browsers' nativeMessaging configuration folder a file 'gpgmejs.json'
+is needed, with the following content:
+
+- For Chrome/Chromium:
+  ```
+  {
+    "name": "gpgmejson",
+    "description": "This is a test application for gpgme.js",
+    "path": "/usr/bin/gpgme-json",
+    "type": "stdio",
+    "allowed_origins": ["chrome-extension://ExtensionIdentifier/"]
+  }
+  ```
+  The usual path for Linux is similar to:
+  `~/.config/chromium/NativeMessagingHosts/gpgmejson.json` for
+  For Windows, the path to the manifest needs to be placed in
+  `HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\gpgmejson`
+
+  - For firefox:
+  ```
+  {
+    "name": "gpgmejson",
+    "description": "This is a test application for gpgme.js",
+    "path": "/usr/bin/gpgme-json",
+    "type": "stdio",
+    "allowed_extensions": ["ExtensionIdentifier@temporary-addon"]
+  }
+  ```
+
+  The ExtensionIdentifier can be seen as Extension ID on the about:addons page
+  if addon-debugging is active. In firefox, the temporary addon is removed once
+  firefox exits, and the identifier will need to be changed more often.
+
+  The manifest for linux is usually placed at:
+    `~/.mozilla/native-messaging-hosts/gpgmejson.json`
+
+
+Documentation
+-------------
+
+The documentation can be built by jsdoc. It currently uses the command
+`./node_modules/.bin/jsdoc -c jsdoc.conf`.
diff --git a/lang/js/build_extensions.sh b/lang/js/build_extensions.sh
new file mode 100755 (executable)
index 0000000..91d5479
--- /dev/null
@@ -0,0 +1,17 @@
+#/!bin/bash
+
+npx webpack --config webpack.conf.js
+npx webpack --config webpack.conf_unittests.js
+mkdir -p BrowserTestExtension/libs
+cp node_modules/chai/chai.js \
+    node_modules/mocha/mocha.css \
+    node_modules/mocha/mocha.js \
+    build/gpgmejs.bundle.js \
+    build/gpgmejs_unittests.bundle.js BrowserTestExtension/libs
+rm -rf build/extensions
+mkdir -p build/extensions
+zip -r build/extensions/browsertest.zip BrowserTestExtension
+
+mkdir -p DemoExtension/libs
+cp build/gpgmejs.bundle.js DemoExtension/libs
+zip -r build/extensions/demoextension.zip DemoExtension
diff --git a/lang/js/jsdoc.conf b/lang/js/jsdoc.conf
new file mode 100644 (file)
index 0000000..976f4e4
--- /dev/null
@@ -0,0 +1,24 @@
+{
+    "tags": {
+        "allowUnknownTags": false,
+        "dictionaries": ["jsdoc"]
+    },
+    "source": {
+        "include": ["jsdoc_index.md", "./src"],
+        "includePattern": ".+\\.js(doc|x)?$",
+        "excludePattern": "(^|\\/|\\\\)_"
+    },
+    "opts":{
+        "destination": "./doc/",
+        "recurse": true
+    },
+    "sourceType": "module",
+    "plugins": [],
+    "templates": {
+        "cleverLinks": false,
+        "monospaceLinks": false,
+        "default": {
+            "outputSourceFiles": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/lang/js/jsdoc_index.md b/lang/js/jsdoc_index.md
new file mode 100644 (file)
index 0000000..b7371ad
--- /dev/null
@@ -0,0 +1,50 @@
+Using gpgme.js
+---------------
+At first, make sure that the environment you want to use gpgme.js in has access
+and permissions for nativeMessaging, and gpgme-json installed. For details,
+see the README.
+
+The library itself is started via the {@link init} method. This will test the
+nativeMessaging connection, and then resolve into an Object offering
+the top level API:
+
+* [encrypt]{@link GpgME#encrypt}
+* [decrypt]{@link GpgME#decrypt}
+* [sign]{@link GpgME#sign}
+* [verify]{@link GpgME#verify}
+* [Keyring]{@link GPGME_Keyring}
+
+```
+gpgmejs.init()
+    .then(function(GPGME) {
+        // using GPGME
+    }, function(error){
+        // error handling;
+    })
+```
+
+All methods that require communication with nativeMessaging are asynchronous,
+using Promises. Rejections will be instances of {@link GPGME_Error}.
+
+An exaeption are Keys, which can be initialized in a 'sync' mode, allowing them
+to be cached and used synchronously until manually refreshed.
+
+Keyring and Keys
+----------------
+The gnupg keys can be accessed via the [Keyring]{@link GPGME_Keyring}.
+
+The Keyring offers the methods for accessing information on all Keys known to
+gnupg.
+
+**Due to security constraints, the javascript-binding currently only offers
+limited support for secret-Key interaction.**
+
+The existance of secret Keys is not secret, and those secret Keys can be used
+for signing, but Operations that may expose, modify or delete secret Keys are
+not supported.
+
+* [getKeysArmored]{@link GPGME_Keyring#getKeysArmored}
+* [getKeys]{@link GPGME_Keyring#getKeys}
+* [getDefaultKey]{@link GPGME_Keyring#getDefaultKey}
+* [generateKey]{@link GPGME_Keyring#generateKey}
+* [deleteKey]{@link GPGME_Keyring#deleteKey}
diff --git a/lang/js/package.json b/lang/js/package.json
new file mode 100644 (file)
index 0000000..54af298
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "name": "gpgmejs",
+  "version": "0.0.1-dev",
+  "description": "Javascript part of the GPGME nativeMessaging integration",
+  "main": "src/index.js",
+  "private": true,
+  "keywords": [],
+  "author": "",
+  "license": "LGPL-2.1+",
+  "devDependencies": {
+    "webpack": "^4.5.0",
+    "webpack-cli": "^3.0.8",
+    "chai": "^4.1.2",
+    "mocha": "^5.1.1",
+    "jsdoc": "^3.5.5"
+  }
+}
diff --git a/lang/js/src/Connection.js b/lang/js/src/Connection.js
new file mode 100644 (file)
index 0000000..d43d55f
--- /dev/null
@@ -0,0 +1,320 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/* global chrome */
+
+import { permittedOperations } from './permittedOperations';
+import { gpgme_error } from './Errors';
+import { GPGME_Message, createMessage } from './Message';
+import { decode, atobArray, Utf8ArrayToStr } from './Helpers';
+
+/**
+ * A Connection handles the nativeMessaging interaction via a port. As the
+ * protocol only allows up to 1MB of message sent from the nativeApp to the
+ * browser, the connection will stay open until all parts of a communication
+ * are finished. For a new request, a new port will open, to avoid mixing
+ * contexts.
+ * @class
+ * @private
+ */
+export class Connection{
+
+    constructor (){
+        this._connection = chrome.runtime.connectNative('gpgmejson');
+    }
+
+    /**
+     * Immediately closes an open port.
+     */
+    disconnect () {
+        if (this._connection){
+            this._connection.disconnect();
+            this._connection = null;
+        }
+    }
+
+
+    /**
+    * @typedef {Object} backEndDetails
+    * @property {String} gpgme Version number of gpgme
+    * @property {Array<Object>} info Further information about the backend
+    * and the used applications (Example:
+    * <pre>
+    * {
+    *          "protocol":     "OpenPGP",
+    *          "fname":        "/usr/bin/gpg",
+    *          "version":      "2.2.6",
+    *          "req_version":  "1.4.0",
+    *          "homedir":      "default"
+    * }
+    * </pre>
+    */
+
+    /**
+     * Retrieves the information about the backend.
+     * @param {Boolean} details (optional) If set to false, the promise will
+     *  just return if a connection was successful.
+     * @param {Number} timeout (optional)
+     * @returns {Promise<backEndDetails>|Promise<Boolean>} Details from the
+     * backend
+     * @async
+     */
+    checkConnection (details = true, timeout = 1000){
+        if (typeof timeout !== 'number' && timeout <= 0) {
+            timeout = 1000;
+        }
+        const msg = createMessage('version');
+        if (details === true) {
+            return this.post(msg);
+        } else {
+            let me = this;
+            return new Promise(function (resolve) {
+                Promise.race([
+                    me.post(msg),
+                    new Promise(function (resolve, reject){
+                        setTimeout(function (){
+                            reject(gpgme_error('CONN_TIMEOUT'));
+                        }, timeout);
+                    })
+                ]).then(function (){ // success
+                    resolve(true);
+                }, function (){ // failure
+                    resolve(false);
+                });
+            });
+        }
+    }
+
+    /**
+     * Sends a {@link GPGME_Message} via the nativeMessaging port. It
+     * resolves with the completed answer after all parts have been
+     * received and reassembled, or rejects with an {@link GPGME_Error}.
+     *
+     * @param {GPGME_Message} message
+     * @returns {Promise<*>} The collected answer, depending on the messages'
+     * operation
+     * @private
+     * @async
+     */
+    post (message){
+        if (!message || !(message instanceof GPGME_Message)){
+            this.disconnect();
+            return Promise.reject(gpgme_error(
+                'PARAM_WRONG', 'Connection.post'));
+        }
+        if (message.isComplete() !== true){
+            this.disconnect();
+            return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
+        }
+        let chunksize = message.chunksize;
+        const me = this;
+        return new Promise(function (resolve, reject){
+            let answer = new Answer(message);
+            let listener = function (msg) {
+                if (!msg){
+                    me._connection.onMessage.removeListener(listener);
+                    me._connection.disconnect();
+                    reject(gpgme_error('CONN_EMPTY_GPG_ANSWER'));
+                } else {
+                    let answer_result = answer.collect(msg);
+                    if (answer_result !== true){
+                        me._connection.onMessage.removeListener(listener);
+                        me._connection.disconnect();
+                        reject(answer_result);
+                    } else {
+                        if (msg.more === true){
+                            me._connection.postMessage({
+                                'op': 'getmore',
+                                'chunksize': chunksize
+                            });
+                        } else {
+                            me._connection.onMessage.removeListener(listener);
+                            me._connection.disconnect();
+                            const message = answer.getMessage();
+                            if (message instanceof Error){
+                                reject(message);
+                            } else {
+                                resolve(message);
+                            }
+                        }
+                    }
+                }
+            };
+            me._connection.onMessage.addListener(listener);
+            if (permittedOperations[message.operation].pinentry){
+                return me._connection.postMessage(message.message);
+            } else {
+                return Promise.race([
+                    me._connection.postMessage(message.message),
+                    function (resolve, reject){
+                        setTimeout(function (){
+                            me._connection.disconnect();
+                            reject(gpgme_error('CONN_TIMEOUT'));
+                        }, 5000);
+                    }
+                ]).then(function (result){
+                    return result;
+                }, function (reject){
+                    if (!(reject instanceof Error)) {
+                        me._connection.disconnect();
+                        return gpgme_error('GNUPG_ERROR', reject);
+                    } else {
+                        return reject;
+                    }
+                });
+            }
+        });
+    }
+}
+
+
+/**
+ * A class for answer objects, checking and processing the return messages of
+ * the nativeMessaging communication.
+ * @private
+ */
+class Answer{
+
+    /**
+     * @param {GPGME_Message} message
+     */
+    constructor (message){
+        this._operation = message.operation;
+        this._expected = message.expected;
+        this._response_b64 = null;
+    }
+
+    get operation (){
+        return this._operation;
+    }
+
+    get expected (){
+        return this._expected;
+    }
+
+    /**
+     * Adds incoming base64 encoded data to the existing response
+     * @param {*} msg base64 encoded data.
+     * @returns {Boolean}
+     *
+     * @private
+     */
+    collect (msg){
+        if (typeof (msg) !== 'object' || !msg.hasOwnProperty('response')) {
+            return gpgme_error('CONN_UNEXPECTED_ANSWER');
+        }
+        if (!this._response_b64){
+            this._response_b64 = msg.response;
+            return true;
+        } else {
+            this._response_b64 += msg.response;
+            return true;
+        }
+    }
+    /**
+     * Decodes and verifies the base64 encoded answer data. Verified against
+     * {@link permittedOperations}.
+     * @returns {Object} The readable gpnupg answer
+     */
+    getMessage (){
+        if (this._response_b64 === null){
+            return gpgme_error('CONN_UNEXPECTED_ANSWER');
+        }
+        let _decodedResponse = JSON.parse(atob(this._response_b64));
+        let _response = {
+            format: 'ascii'
+        };
+        let messageKeys = Object.keys(_decodedResponse);
+        let poa = permittedOperations[this.operation].answer;
+        if (messageKeys.length === 0){
+            return gpgme_error('CONN_UNEXPECTED_ANSWER');
+        }
+        for (let i= 0; i < messageKeys.length; i++){
+            let key = messageKeys[i];
+            switch (key) {
+            case 'type': {
+                if (_decodedResponse.type === 'error'){
+                    return (gpgme_error('GNUPG_ERROR',
+                        decode(_decodedResponse.msg)));
+                } else if (poa.type.indexOf(_decodedResponse.type) < 0){
+                    return gpgme_error('CONN_UNEXPECTED_ANSWER');
+                }
+                break;
+            }
+            case 'base64': {
+                break;
+            }
+            case 'msg': {
+                if (_decodedResponse.type === 'error'){
+                    return (gpgme_error('GNUPG_ERROR', _decodedResponse.msg));
+                }
+                break;
+            }
+            default: {
+                let answerType = null;
+                if (poa.payload && poa.payload.hasOwnProperty(key)){
+                    answerType = 'p';
+                } else if (poa.info && poa.info.hasOwnProperty(key)){
+                    answerType = 'i';
+                }
+                if (answerType !== 'p' && answerType !== 'i'){
+                    return gpgme_error('CONN_UNEXPECTED_ANSWER');
+                }
+
+                if (answerType === 'i') {
+                    if ( typeof (_decodedResponse[key]) !== poa.info[key] ){
+                        return gpgme_error('CONN_UNEXPECTED_ANSWER');
+                    }
+                    _response[key] = decode(_decodedResponse[key]);
+
+                } else if (answerType === 'p') {
+                    if (_decodedResponse.base64 === true
+                        && poa.payload[key] === 'string'
+                    ) {
+                        if (this.expected === 'uint8'){
+                            _response[key] = atobArray(_decodedResponse[key]);
+                            _response.format = 'uint8';
+
+                        } else if (this.expected === 'base64'){
+                            _response[key] = _decodedResponse[key];
+                            _response.format = 'base64';
+
+                        } else { // no 'expected'
+                            _response[key] = Utf8ArrayToStr(
+                                atobArray(_decodedResponse[key]));
+                            _response.format = 'string';
+                        }
+                    } else if (poa.payload[key] === 'string') {
+                        _response[key] = _decodedResponse[key];
+                    } else {
+                        // fallthrough, should not be reached
+                        // (payload is always string)
+                        return gpgme_error('CONN_UNEXPECTED_ANSWER');
+                    }
+                }
+                break;
+            } }
+        }
+        return _response;
+    }
+}
diff --git a/lang/js/src/Errors.js b/lang/js/src/Errors.js
new file mode 100644 (file)
index 0000000..2f66c83
--- /dev/null
@@ -0,0 +1,177 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/**
+ * Listing of all possible error codes and messages of a {@link GPGME_Error}.
+ */
+export const err_list = {
+    // Connection
+    'CONN_NO_CONNECT': {
+        msg:'Connection with the nativeMessaging host could not be'
+            + ' established.',
+        type: 'error'
+    },
+    'CONN_EMPTY_GPG_ANSWER':{
+        msg: 'The nativeMessaging answer was empty.',
+        type: 'error'
+    },
+    'CONN_TIMEOUT': {
+        msg: 'A connection timeout was exceeded.',
+        type: 'error'
+    },
+    'CONN_UNEXPECTED_ANSWER': {
+        msg: 'The answer from gnupg was not as expected.',
+        type: 'error'
+    },
+    'CONN_ALREADY_CONNECTED':{
+        msg: 'A connection was already established.',
+        type: 'warning'
+    },
+    // Message/Data
+    'MSG_INCOMPLETE': {
+        msg: 'The Message did not match the minimum requirements for'
+            + ' the interaction.',
+        type: 'error'
+    },
+    'MSG_EMPTY' : {
+        msg: 'The Message is empty.',
+        type: 'error'
+    },
+    'MSG_WRONG_OP': {
+        msg: 'The operation requested could not be found',
+        type: 'error'
+    },
+    'MSG_NO_KEYS' : {
+        msg: 'There were no valid keys provided.',
+        type: 'warning'
+    },
+    'MSG_NOT_A_FPR': {
+        msg: 'The String is not an accepted fingerprint',
+        type: 'warning'
+    },
+    'KEY_INVALID': {
+        msg:'Key object is invalid',
+        type: 'error'
+    },
+    'KEY_NOKEY': {
+        msg:'This key does not exist in GPG',
+        type: 'error'
+    },
+    'KEY_NO_INIT': {
+        msg:'This property has not been retrieved yet from GPG',
+        type: 'error'
+    },
+    'KEY_ASYNC_ONLY': {
+        msg: 'This property cannot be used in synchronous calls',
+        type: 'error'
+    },
+    'KEY_NO_DEFAULT': {
+        msg:'A default key could not be established. Please check yout gpg ' +
+            'configuration',
+        type: 'error'
+    },
+    'SIG_WRONG': {
+        msg:'A malformed signature was created',
+        type: 'error'
+    },
+    'SIG_NO_SIGS': {
+        msg:'There were no signatures found',
+        type: 'error'
+    },
+    // generic
+    'PARAM_WRONG':{
+        msg: 'Invalid parameter was found',
+        type: 'error'
+    },
+    'DECODE_FAIL': {
+        msg: 'Decoding failed due to unexpected data',
+        type: 'error'
+    },
+    'PARAM_IGNORED': {
+        msg: 'An parameter was set that has no effect in gpgmejs',
+        type: 'warning'
+    },
+    'GENERIC_ERROR': {
+        msg: 'Unspecified error',
+        type: 'error'
+    }
+};
+
+/**
+ * Checks the given error code and returns an {@link GPGME_Error} error object
+ * with some information about meaning and origin
+ * @param {String} code Error code as defined in {@link err_list}.
+ * @param {String} info Possible additional error message to pass through.
+ * Currently used for errors sent as answer by gnupg via a native Message port
+ * @returns {GPGME_Error}
+ */
+export function gpgme_error (code = 'GENERIC_ERROR', info){
+    if (err_list.hasOwnProperty(code)){
+        if (err_list[code].type === 'error'){
+            return new GPGME_Error(code);
+        }
+        if (err_list[code].type === 'warning'){
+            // eslint-disable-next-line no-console
+            // console.warn(code + ': ' + err_list[code].msg);
+        }
+        return null;
+    } else if (code === 'GNUPG_ERROR'){
+        return new GPGME_Error(code, info);
+    }
+    else {
+        return new GPGME_Error('GENERIC_ERROR');
+    }
+}
+
+/**
+ * An error class with additional info about the origin of the error, as string
+ * It is created by {@link gpgme_error}, and its' codes are defined in
+ * {@link err_list}.
+ *
+ * @property {String} code Short description of origin and type of the error
+ * @property {String} msg Additional info
+ * @protected
+ * @class
+ * @extends Error
+ */
+class GPGME_Error extends Error{
+    constructor (code = 'GENERIC_ERROR', msg=''){
+
+        if (code === 'GNUPG_ERROR' && typeof (msg) === 'string'){
+            super(msg);
+        } else if (err_list.hasOwnProperty(code)){
+            if (msg){
+                super(err_list[code].msg + '--' + msg);
+            } else {
+                super(err_list[code].msg);
+            }
+        } else {
+            super(err_list['GENERIC_ERROR'].msg);
+        }
+        this._code = code;
+    }
+
+    get code (){
+        return this._code;
+    }
+}
\ No newline at end of file
diff --git a/lang/js/src/Helpers.js b/lang/js/src/Helpers.js
new file mode 100644 (file)
index 0000000..0b41852
--- /dev/null
@@ -0,0 +1,219 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+import { gpgme_error } from './Errors';
+
+/**
+ * Helper function that tries to return an array of fingerprints, either from
+ * input fingerprints or from Key objects (openpgp Keys or GPGME_Keys are both
+ * accepted).
+ *
+ * @param {Object | Object[] | String | String[] } input
+ * @returns {String[]} Array of fingerprints, or an empty array
+ */
+export function toKeyIdArray (input){
+    if (!input){
+        return [];
+    }
+    if (!Array.isArray(input)){
+        input = [input];
+    }
+    let result = [];
+    for (let i=0; i < input.length; i++){
+        if (typeof (input[i]) === 'string'){
+            if (isFingerprint(input[i]) === true){
+                result.push(input[i]);
+            } else {
+                // MSG_NOT_A_FPR is just a console warning if warning enabled
+                // in src/Errors.js
+                gpgme_error('MSG_NOT_A_FPR');
+            }
+        } else if (typeof (input[i]) === 'object'){
+            let fpr = '';
+            if (input[i].fingerprint !== undefined){
+                fpr = input[i].fingerprint;
+            } else if (input[i].hasOwnProperty('primaryKey') &&
+                input[i].primaryKey.hasOwnProperty('getFingerprint')){
+                fpr = input[i].primaryKey.getFingerprint();
+            }
+            if (isFingerprint(fpr) === true){
+                result.push(fpr);
+            } else {
+                gpgme_error('MSG_NOT_A_FPR');
+            }
+        } else {
+            return gpgme_error('PARAM_WRONG');
+        }
+    }
+    if (result.length === 0){
+        return [];
+    } else {
+        return result;
+    }
+}
+
+/**
+ * Check if values are valid hexadecimal values of a specified length
+ * @param {String} key input value.
+ * @param {int} len the expected length of the value
+ * @returns {Boolean} true if value passes test
+ * @private
+ */
+function hextest (key, len){
+    if (!key || typeof (key) !== 'string'){
+        return false;
+    }
+    if (key.length !== len){
+        return false;
+    }
+    let regexp= /^[0-9a-fA-F]*$/i;
+    return regexp.test(key);
+}
+
+/**
+ * Checks if the input is a valid Fingerprint
+ *      (Hex string with a length of 40 characters)
+ * @param {String} value to check
+ * @returns {Boolean} true if value passes test
+ */
+export function isFingerprint (value){
+    return hextest(value, 40);
+}
+
+/**
+ * check if the input is a valid gnupg long ID (Hex string with a length of 16
+ * characters)
+ * @param {String} value to check
+ * @returns {Boolean} true if value passes test
+ */
+export function isLongId (value){
+    return hextest(value, 16);
+}
+
+/**
+ * Recursively decodes input (utf8) to output (utf-16; javascript) strings.
+ * @param {Object | Array | String} property
+ * @private
+ */
+export function decode (property){
+    if (typeof property === 'string'){
+        try {
+            return decodeURIComponent(escape(unescape(property)));
+        }
+        catch (error){
+            if (error instanceof URIError) {
+                return property;
+            }
+        }
+    } else if (Array.isArray(property)){
+        let res = [];
+        for (let arr=0; arr < property.length; arr++){
+            res.push(decode(property[arr]));
+        }
+        return res;
+    } else if (typeof property === 'object'){
+        const keys = Object.keys(property);
+        if (keys.length){
+            let res = {};
+            for (let k=0; k < keys.length; k++ ){
+                res[keys[k]] = decode(property[keys[k]]);
+            }
+            return res;
+        }
+        return property;
+    }
+    return property;
+}
+
+/**
+ * Turns a base64 encoded string into an uint8 array
+ * adapted from https://gist.github.com/borismus/1032746
+ * @param {String} base64 encoded String
+ * @returns {Uint8Array}
+ * @private
+ */
+export function atobArray (base64) {
+    if (typeof (base64) !== 'string'){
+        throw gpgme_error('DECODE_FAIL');
+    }
+    const raw = window.atob(base64);
+    const rawLength = raw.length;
+    let array = new Uint8Array(new ArrayBuffer(rawLength));
+    for (let i = 0; i < rawLength; i++) {
+        array[i] = raw.charCodeAt(i);
+    }
+    return array;
+}
+
+/**
+ * Turns a Uint8Array into an utf8-String
+ * <pre>
+ * Taken and slightly adapted from
+ *  http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
+ * (original header:
+ *   utf.js - UTF-8 <=> UTF-16 convertion
+ *
+ *   Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
+ *   Version: 1.0
+ *   LastModified: Dec 25 1999
+ *   This library is free.  You can redistribute it and/or modify it.
+ *  )
+ * </pre>
+ * @param {*} array Uint8Array
+ * @returns {String}
+ * @private
+ */
+export function Utf8ArrayToStr (array) {
+    let out, i, len, c, char2, char3;
+    out = '';
+    len = array.length;
+    i = 0;
+    if (array instanceof Uint8Array === false){
+        throw gpgme_error('DECODE_FAIL');
+    }
+    while (i < len) {
+        c = array[i++];
+        switch (c >> 4) {
+        case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
+            // 0xxxxxxx
+            out += String.fromCharCode(c);
+            break;
+        case 12: case 13:
+            // 110x xxxx   10xx xxxx
+            char2 = array[i++];
+            out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
+            break;
+        case 14:
+            // 1110 xxxx  10xx xxxx  10xx xxxx
+            char2 = array[i++];
+            char3 = array[i++];
+            out += String.fromCharCode(((c & 0x0F) << 12) |
+                            ((char2 & 0x3F) << 6) |
+                            ((char3 & 0x3F) << 0));
+            break;
+        default:
+            break;
+        }
+    }
+    return out;
+}
diff --git a/lang/js/src/Key.js b/lang/js/src/Key.js
new file mode 100644 (file)
index 0000000..7f0554c
--- /dev/null
@@ -0,0 +1,711 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+
+import { isFingerprint, isLongId } from './Helpers';
+import { gpgme_error } from './Errors';
+import { createMessage } from './Message';
+
+/**
+ * Validates the given fingerprint and creates a new {@link GPGME_Key}
+ * @param {String} fingerprint
+ * @param {Boolean} async If True, Key properties (except fingerprint) will be
+ * queried from gnupg on each call, making the operation up-to-date, the
+ * answers will be Promises, and the performance will likely suffer
+ * @param {Object} data additional initial properties this Key will have. Needs
+ * a full object as delivered by gpgme-json
+ * @returns {Object} The verified and updated data
+ */
+export function createKey (fingerprint, async = false, data){
+    if (!isFingerprint(fingerprint) || typeof (async) !== 'boolean'){
+        throw gpgme_error('PARAM_WRONG');
+    }
+    if (data !== undefined){
+        data = validateKeyData(fingerprint, data);
+    }
+    if (data instanceof Error){
+        throw gpgme_error('KEY_INVALID');
+    } else {
+        return new GPGME_Key(fingerprint, async, data);
+    }
+}
+
+/**
+ * Represents the Keys as stored in the gnupg backend. A key is defined by a
+ * fingerprint.
+ * A key cannot be directly created via the new operator, please use
+ * {@link createKey} instead.
+ * A GPGME_Key object allows to query almost all information defined in gpgme
+ * Keys. It offers two modes, async: true/false. In async mode, Key properties
+ * with the exception of the fingerprint will be queried from gnupg on each
+ * call, making the operation up-to-date, the answers will be Promises, and
+ * the performance will likely suffer. In Sync modes, all information except
+ * for the armored Key export will be cached and can be refreshed by
+ * [refreshKey]{@link GPGME_Key#refreshKey}.
+ *
+ * <pre>
+ * see also:
+ *      {@link GPGME_UserId} user Id objects
+ *      {@link GPGME_Subkey} subKey objects
+ * </pre>
+ * For other Key properteis, refer to {@link validKeyProperties},
+ * and to the [gpgme documentation]{@link https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html}
+ * for meanings and further details.
+ *
+ * @class
+ */
+class GPGME_Key {
+
+    constructor (fingerprint, async, data){
+
+        /**
+         * @property {Boolean} _async If true, the Key was initialized without
+         * cached data
+         */
+        this._async = async;
+
+        this._data = { fingerprint: fingerprint.toUpperCase() };
+        if (data !== undefined
+            && data.fingerprint.toUpperCase() === this._data.fingerprint
+        ) {
+            this._data = data;
+        }
+    }
+
+    /**
+     * Query any property of the Key listed in {@link validKeyProperties}
+     * @param {String} property property to be retreived
+     * @returns {Boolean| String | Date | Array | Object}
+     * @returns {Promise<Boolean| String | Date | Array | Object>} (if in async
+     * mode)
+     * <pre>
+     * Returns the value of the property requested. If the Key is set to async,
+     * the value will be fetched from gnupg and resolved as a Promise. If Key
+     * is not  async, the armored property is not available (it can still be
+     * retrieved asynchronously by [getArmor]{@link GPGME_Key#getArmor})
+     */
+    get (property) {
+        if (this._async === true) {
+            switch (property){
+            case 'armored':
+                return this.getArmor();
+            case 'hasSecret':
+                return this.getGnupgSecretState();
+            default:
+                return getGnupgState(this.fingerprint, property);
+            }
+        } else {
+            if (property === 'armored') {
+                throw gpgme_error('KEY_ASYNC_ONLY');
+            }
+            // eslint-disable-next-line no-use-before-define
+            if (!validKeyProperties.hasOwnProperty(property)){
+                throw gpgme_error('PARAM_WRONG');
+            } else {
+                return (this._data[property]);
+            }
+        }
+    }
+
+    /**
+     * Reloads the Key information from gnupg. This is only useful if the Key
+     * use the GPGME_Keys cached. Note that this is a performance hungry
+     * operation. If you desire more than a few refreshs, it may be
+     * advisable to run [Keyring.getKeys]{@link Keyring#getKeys} instead.
+     * @returns {Promise<GPGME_Key>}
+     * @async
+     */
+    refreshKey () {
+        let me = this;
+        return new Promise(function (resolve, reject) {
+            if (!me._data.fingerprint){
+                reject(gpgme_error('KEY_INVALID'));
+            }
+            let msg = createMessage('keylist');
+            msg.setParameter('sigs', true);
+            msg.setParameter('keys', me._data.fingerprint);
+            msg.post().then(function (result){
+                if (result.keys.length === 1){
+                    const newdata = validateKeyData(
+                        me._data.fingerprint, result.keys[0]);
+                    if (newdata instanceof Error){
+                        reject(gpgme_error('KEY_INVALID'));
+                    } else {
+                        me._data = newdata;
+                        me.getGnupgSecretState().then(function (){
+                            me.getArmor().then(function (){
+                                resolve(me);
+                            }, function (error){
+                                reject(error);
+                            });
+                        }, function (error){
+                            reject(error);
+                        });
+                    }
+                } else {
+                    reject(gpgme_error('KEY_NOKEY'));
+                }
+            }, function (error) {
+                reject(gpgme_error('GNUPG_ERROR'), error);
+            });
+        });
+    }
+
+    /**
+     * Query the armored block of the Key directly from gnupg. Please note
+     * that this will not get you any export of the secret/private parts of
+     * a Key
+     * @returns {Promise<String>}
+     * @async
+     */
+    getArmor () {
+        const me = this;
+        return new Promise(function (resolve, reject) {
+            if (!me._data.fingerprint){
+                reject(gpgme_error('KEY_INVALID'));
+            }
+            let msg = createMessage('export');
+            msg.setParameter('armor', true);
+            msg.setParameter('keys', me._data.fingerprint);
+            msg.post().then(function (result){
+                resolve(result.data);
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+
+    /**
+     * Find out if the Key is part of a Key pair including public and
+     * private key(s). If you want this information about more than a few
+     * Keys in synchronous mode, it may be advisable to run
+     * [Keyring.getKeys]{@link Keyring#getKeys} instead, as it performs faster
+     * in bulk querying.
+     * @returns {Promise<Boolean>} True if a private Key is available in the
+     * gnupg Keyring.
+     * @async
+     */
+    getGnupgSecretState (){
+        const me = this;
+        return new Promise(function (resolve, reject) {
+            if (!me._data.fingerprint){
+                reject(gpgme_error('KEY_INVALID'));
+            } else {
+                let msg = createMessage('keylist');
+                msg.setParameter('keys', me._data.fingerprint);
+                msg.setParameter('secret', true);
+                msg.post().then(function (result){
+                    me._data.hasSecret = null;
+                    if (
+                        result.keys &&
+                        result.keys.length === 1 &&
+                        result.keys[0].secret === true
+                    ) {
+                        me._data.hasSecret = true;
+                        resolve(true);
+                    } else {
+                        me._data.hasSecret = false;
+                        resolve(false);
+                    }
+                }, function (error){
+                    reject(error);
+                });
+            }
+        });
+    }
+
+    /**
+     * Deletes the (public) Key from the GPG Keyring. Note that a deletion
+     * of a secret key is not supported by the native backend, and gnupg will
+     * refuse to delete a Key if there is still a secret/private Key present
+     * to that public Key
+     * @returns {Promise<Boolean>} Success if key was deleted.
+     */
+    delete (){
+        const me = this;
+        return new Promise(function (resolve, reject){
+            if (!me._data.fingerprint){
+                reject(gpgme_error('KEY_INVALID'));
+            }
+            let msg = createMessage('delete');
+            msg.setParameter('key', me._data.fingerprint);
+            msg.post().then(function (result){
+                resolve(result.success);
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+
+    /**
+     * @returns {String} The fingerprint defining this Key. Convenience getter
+     */
+    get fingerprint (){
+        return this._data.fingerprint;
+    }
+}
+
+/**
+ * Representing a subkey of a Key. See {@link validSubKeyProperties} for
+ * possible properties.
+ * @class
+ * @protected
+ */
+class GPGME_Subkey {
+
+    /**
+     * Initializes with the json data sent by gpgme-json
+     * @param {Object} data
+     * @private
+     */
+    constructor (data){
+        this._data = {};
+        let keys = Object.keys(data);
+        const me = this;
+
+        /**
+         * Validates a subkey property against {@link validSubKeyProperties} and
+         * sets it if validation is successful
+         * @param {String} property
+         * @param {*} value
+         * @param private
+         */
+        const setProperty = function (property, value){
+            // eslint-disable-next-line no-use-before-define
+            if (validSubKeyProperties.hasOwnProperty(property)){
+                // eslint-disable-next-line no-use-before-define
+                if (validSubKeyProperties[property](value) === true) {
+                    if (property === 'timestamp' || property === 'expires'){
+                        me._data[property] = new Date(value * 1000);
+                    } else {
+                        me._data[property] = value;
+                    }
+                }
+            }
+        };
+        for (let i=0; i< keys.length; i++) {
+            setProperty(keys[i], data[keys[i]]);
+        }
+    }
+
+    /**
+     * Fetches any information about this subkey
+     * @param {String} property Information to request
+     * @returns {String | Number | Date}
+     */
+    get (property) {
+        if (this._data.hasOwnProperty(property)){
+            return (this._data[property]);
+        }
+    }
+
+}
+
+/**
+ * Representing user attributes associated with a Key or subkey. See
+ * {@link validUserIdProperties} for possible properties.
+ * @class
+ * @protected
+ */
+class GPGME_UserId {
+
+    /**
+     * Initializes with the json data sent by gpgme-json
+     * @param {Object} data
+     * @private
+     */
+    constructor (data){
+        this._data = {};
+        const me = this;
+        let keys = Object.keys(data);
+        const setProperty = function (property, value){
+            // eslint-disable-next-line no-use-before-define
+            if (validUserIdProperties.hasOwnProperty(property)){
+                // eslint-disable-next-line no-use-before-define
+                if (validUserIdProperties[property](value) === true) {
+                    if (property === 'last_update'){
+                        me._data[property] = new Date(value*1000);
+                    } else {
+                        me._data[property] = value;
+                    }
+                }
+            }
+        };
+        for (let i=0; i< keys.length; i++) {
+            setProperty(keys[i], data[keys[i]]);
+        }
+    }
+
+    /**
+     * Fetches information about the user
+     * @param {String} property Information to request
+     * @returns {String | Number}
+     */
+    get (property) {
+        if (this._data.hasOwnProperty(property)){
+            return (this._data[property]);
+        }
+    }
+
+}
+
+/**
+ * Validation definition for userIds. Each valid userId property is represented
+ * as a key- Value pair, with their value being a validation function to check
+ * against
+ * @protected
+ * @const
+ */
+const validUserIdProperties = {
+    'revoked': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'invalid':  function (value){
+        return typeof (value) === 'boolean';
+    },
+    'uid': function (value){
+        if (typeof (value) === 'string' || value === ''){
+            return true;
+        }
+        return false;
+    },
+    'validity': function (value){
+        if (typeof (value) === 'string'){
+            return true;
+        }
+        return false;
+    },
+    'name': function (value){
+        if (typeof (value) === 'string' || value === ''){
+            return true;
+        }
+        return false;
+    },
+    'email': function (value){
+        if (typeof (value) === 'string' || value === ''){
+            return true;
+        }
+        return false;
+    },
+    'address': function (value){
+        if (typeof (value) === 'string' || value === ''){
+            return true;
+        }
+        return false;
+    },
+    'comment': function (value){
+        if (typeof (value) === 'string' || value === ''){
+            return true;
+        }
+        return false;
+    },
+    'origin':  function (value){
+        return Number.isInteger(value);
+    },
+    'last_update':  function (value){
+        return Number.isInteger(value);
+    }
+};
+
+/**
+ * Validation definition for subKeys. Each valid userId property is represented
+ * as a key-value pair, with the value being a validation function
+ * @protected
+ * @const
+ */
+const validSubKeyProperties = {
+    'invalid': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_encrypt': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_sign': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_certify':  function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_authenticate':  function (value){
+        return typeof (value) === 'boolean';
+    },
+    'secret': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'is_qualified': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'is_cardkey':  function (value){
+        return typeof (value) === 'boolean';
+    },
+    'is_de_vs':  function (value){
+        return typeof (value) === 'boolean';
+    },
+    'pubkey_algo_name': function (value){
+        return typeof (value) === 'string';
+        // TODO: check against list of known?['']
+    },
+    'pubkey_algo_string': function (value){
+        return typeof (value) === 'string';
+        // TODO: check against list of known?['']
+    },
+    'keyid': function (value){
+        return isLongId(value);
+    },
+    'pubkey_algo': function (value) {
+        return (Number.isInteger(value) && value >= 0);
+    },
+    'length': function (value){
+        return (Number.isInteger(value) && value > 0);
+    },
+    'timestamp': function (value){
+        return (Number.isInteger(value) && value > 0);
+    },
+    'expires': function (value){
+        return (Number.isInteger(value) && value > 0);
+    }
+};
+
+/**
+ * Validation definition for Keys. Each valid Key property is represented
+ * as a key-value pair, with their value being a validation function. For
+ * details on the meanings, please refer to the gpgme documentation
+ * https://www.gnupg.org/documentation/manuals/gpgme/Key-objects.html#Key-objects
+ * @param {String} fingerprint
+ * @param {Boolean} revoked
+ * @param {Boolean} expired
+ * @param {Boolean} disabled
+ * @param {Boolean} invalid
+ * @param {Boolean} can_encrypt
+ * @param {Boolean} can_sign
+ * @param {Boolean} can_certify
+ * @param {Boolean} can_authenticate
+ * @param {Boolean} secret
+ * @param {Boolean}is_qualified
+ * @param {String} protocol
+ * @param {String} issuer_serial
+ * @param {String} issuer_name
+ * @param {Boolean} chain_id
+ * @param {String} owner_trust
+ * @param {Date} last_update
+ * @param {String} origin
+ * @param {Array<GPGME_Subkey>} subkeys
+ * @param {Array<GPGME_UserId>} userids
+ * @param {Array<String>} tofu
+ * @param {Boolean} hasSecret
+ * @protected
+ * @const
+ */
+const validKeyProperties = {
+    'fingerprint': function (value){
+        return isFingerprint(value);
+    },
+    'revoked': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'expired': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'disabled': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'invalid': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_encrypt': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_sign': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_certify': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'can_authenticate': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'secret': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'is_qualified': function (value){
+        return typeof (value) === 'boolean';
+    },
+    'protocol': function (value){
+        return typeof (value) === 'string';
+        // TODO check for implemented ones
+    },
+    'issuer_serial': function (value){
+        return typeof (value) === 'string';
+    },
+    'issuer_name': function (value){
+        return typeof (value) === 'string';
+    },
+    'chain_id': function (value){
+        return typeof (value) === 'string';
+    },
+    'owner_trust': function (value){
+        return typeof (value) === 'string';
+    },
+    'last_update': function (value){
+        return (Number.isInteger(value));
+        // TODO undefined/null possible?
+    },
+    'origin': function (value){
+        return (Number.isInteger(value));
+    },
+    'subkeys': function (value){
+        return (Array.isArray(value));
+    },
+    'userids': function (value){
+        return (Array.isArray(value));
+    },
+    'tofu': function (value){
+        return (Array.isArray(value));
+    },
+    'hasSecret': function (value){
+        return typeof (value) === 'boolean';
+    }
+
+};
+
+/**
+* sets the Key data in bulk. It can only be used from inside a Key, either
+* during construction or on a refresh callback.
+* @param {Object} key the original internal key data.
+* @param {Object} data Bulk set the data for this key, with an Object structure
+* as sent by gpgme-json.
+* @returns {Object|GPGME_Error} the changed data after values have been set,
+* an error if something went wrong.
+* @private
+*/
+function validateKeyData (fingerprint, data){
+    const key = {};
+    if (!fingerprint || typeof (data) !== 'object' || !data.fingerprint
+     || fingerprint !== data.fingerprint.toUpperCase()
+    ){
+        return gpgme_error('KEY_INVALID');
+    }
+    let props = Object.keys(data);
+    for (let i=0; i< props.length; i++){
+        if (!validKeyProperties.hasOwnProperty(props[i])){
+            return gpgme_error('KEY_INVALID');
+        }
+        // running the defined validation function
+        if (validKeyProperties[props[i]](data[props[i]]) !== true ){
+            return gpgme_error('KEY_INVALID');
+        }
+        switch (props[i]){
+        case 'subkeys':
+            key.subkeys = [];
+            for (let i=0; i< data.subkeys.length; i++) {
+                key.subkeys.push(
+                    new GPGME_Subkey(data.subkeys[i]));
+            }
+            break;
+        case 'userids':
+            key.userids = [];
+            for (let i=0; i< data.userids.length; i++) {
+                key.userids.push(
+                    new GPGME_UserId(data.userids[i]));
+            }
+            break;
+        case 'last_update':
+            key[props[i]] = new Date( data[props[i]] * 1000 );
+            break;
+        default:
+            key[props[i]] = data[props[i]];
+        }
+    }
+    return key;
+}
+
+/**
+ * Fetches and sets properties from gnupg
+ * @param {String} fingerprint
+ * @param {String} property to search for.
+ * @private
+ * @async
+ */
+function getGnupgState (fingerprint, property){
+    return new Promise(function (resolve, reject) {
+        if (!isFingerprint(fingerprint)) {
+            reject(gpgme_error('KEY_INVALID'));
+        } else {
+            let msg = createMessage('keylist');
+            msg.setParameter('keys', fingerprint);
+            msg.post().then(function (res){
+                if (!res.keys || res.keys.length !== 1){
+                    reject(gpgme_error('KEY_INVALID'));
+                } else {
+                    const key = res.keys[0];
+                    let result;
+                    switch (property){
+                    case 'subkeys':
+                        result = [];
+                        if (key.subkeys.length){
+                            for (let i=0; i < key.subkeys.length; i++) {
+                                result.push(
+                                    new GPGME_Subkey(key.subkeys[i]));
+                            }
+                        }
+                        resolve(result);
+                        break;
+                    case 'userids':
+                        result = [];
+                        if (key.userids.length){
+                            for (let i=0; i< key.userids.length; i++) {
+                                result.push(
+                                    new GPGME_UserId(key.userids[i]));
+                            }
+                        }
+                        resolve(result);
+                        break;
+                    case 'last_update':
+                        if (key.last_update === undefined){
+                            reject(gpgme_error('CONN_UNEXPECTED_ANSWER'));
+                        } else if (key.last_update !== null){
+                            resolve(new Date( key.last_update * 1000));
+                        } else {
+                            resolve(null);
+                        }
+                        break;
+                    default:
+                        if (!validKeyProperties.hasOwnProperty(property)){
+                            reject(gpgme_error('PARAM_WRONG'));
+                        } else {
+                            if (key.hasOwnProperty(property)){
+                                resolve(key[property]);
+                            } else {
+                                reject(gpgme_error(
+                                    'CONN_UNEXPECTED_ANSWER'));
+                            }
+                        }
+                        break;
+                    }
+                }
+            }, function (error){
+                reject(gpgme_error(error));
+            });
+        }
+    });
+}
\ No newline at end of file
diff --git a/lang/js/src/Keyring.js b/lang/js/src/Keyring.js
new file mode 100644 (file)
index 0000000..2071c6d
--- /dev/null
@@ -0,0 +1,439 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+
+import { createMessage } from './Message';
+import { createKey } from './Key';
+import { isFingerprint } from './Helpers';
+import { gpgme_error } from './Errors';
+
+/**
+ * This class offers access to the gnupg keyring
+ */
+export class GPGME_Keyring {
+
+    /**
+     * Queries Keys (all Keys or a subset) from gnupg.
+     *
+     * @param {Object} options:
+     * @param {String | Array<String>} options.pattern (optional) A pattern to
+     * search for in userIds or KeyIds.
+     * @param {Boolean} options.prepare_sync (optional) if set to true, most
+     * data (with the exception of armored Key blocks) will be cached for the
+     * Keys. This enables direct, synchronous use of these properties for
+     * all keys. It does not check for changes on the backend. The cached
+     * information can be updated with the {@link Key.refresh} method.
+     * @param {Boolean} options.search (optional) retrieve Keys from external
+     * servers with the method(s) defined in gnupg (e.g. WKD/HKP lookup)
+     * @returns {Promise<GPGME_Key[]>}
+     * @static
+     * @async
+     */
+    getKeys ({ pattern, prepare_sync = false, search = false } = {}){
+        if (typeof arguments[0] !== 'object') {
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        if (arguments.length && typeof arguments[0] !== 'object') {
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        return new Promise(function (resolve, reject) {
+            let msg = createMessage('keylist');
+            if (pattern) {
+                msg.setParameter('keys', pattern);
+            }
+            msg.setParameter('sigs', true);
+            if (search === true){
+                msg.setParameter('locate', true);
+            }
+            msg.post().then(function (result){
+                let resultset = [];
+                if (result.keys.length === 0){
+                    resolve([]);
+                } else {
+                    let secondrequest;
+                    if (prepare_sync === true) {
+                        secondrequest = function () {
+                            let msg2 = createMessage('keylist');
+                            if (pattern){
+                                msg2.setParameter('keys', pattern);
+                            }
+                            msg2.setParameter('secret', true);
+                            return msg2.post();
+                        };
+                    } else {
+                        secondrequest = function () {
+                            return Promise.resolve(true);
+                        };
+                    }
+                    secondrequest().then(function (answer) {
+                        for (let i=0; i < result.keys.length; i++){
+                            if (prepare_sync === true){
+                                if (answer && answer.keys) {
+                                    for (let j=0;
+                                        j < answer.keys.length; j++ ){
+                                        const a = answer.keys[j];
+                                        const b = result.keys[i];
+                                        if (
+                                            a.fingerprint === b.fingerprint
+                                        ) {
+                                            if (a.secret === true){
+                                                b.hasSecret = true;
+                                            } else {
+                                                b.hasSecret = false;
+                                            }
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                            let k = createKey(result.keys[i].fingerprint,
+                                !prepare_sync, result.keys[i]);
+                            resultset.push(k);
+                        }
+                        resolve(resultset);
+                    }, function (error){
+                        reject(error);
+                    });
+                }
+            });
+        });
+    }
+
+    /**
+     * @typedef {Object} exportResult The result of a getKeysArmored
+     * operation.
+     * @property {String} armored The public Key(s) as armored block. Note
+     * that the result is one armored block, and not a block per key.
+     * @property {Array<String>} secret_fprs (optional) list of
+     * fingerprints for those Keys that also have a secret Key available in
+     * gnupg. The secret key will not be exported, but the fingerprint can
+     * be used in operations needing a secret key.
+     */
+
+    /**
+     * Fetches the armored public Key blocks for all Keys matching the
+     * pattern (if no pattern is given, fetches all keys known to gnupg).
+     * @param {Object} options (optional)
+     * @param {String|Array<String>} options.pattern The Pattern to
+     * search for
+     * @param {Boolean} options.with_secret_fpr also return a list of
+     * fingerprints for the keys that have a secret key available
+     * @returns {Promise<exportResult>} Object containing the
+     * armored Key(s) and additional information.
+     * @static
+     * @async
+     */
+    getKeysArmored ({ pattern, with_secret_fpr }) {
+        return new Promise(function (resolve, reject) {
+            let msg = createMessage('export');
+            msg.setParameter('armor', true);
+            if (with_secret_fpr === true) {
+                msg.setParameter('with-sec-fprs', true);
+            }
+            if (pattern){
+                msg.setParameter('keys', pattern);
+            }
+            msg.post().then(function (answer){
+                const result = { armored: answer.data };
+                if (with_secret_fpr === true){
+                    if (answer.hasOwnProperty('sec-fprs')){
+                        result.secret_fprs = answer['sec-fprs'];
+                    } else {
+                        result.secret_fprs = [];
+                    }
+                }
+                resolve(result);
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+
+    /**
+     * Returns the Key used by default in gnupg.
+     * (a.k.a. 'primary Key or 'main key').
+     * It looks up the gpg configuration if set, or the first key that
+     * contains a secret key.
+     *
+     * @returns {Promise<GPGME_Key>}
+     * @async
+     * @static
+     */
+    getDefaultKey (prepare_sync = false) {
+        let me = this;
+        return new Promise(function (resolve, reject){
+            let msg = createMessage('config_opt');
+            msg.setParameter('component', 'gpg');
+            msg.setParameter('option', 'default-key');
+            msg.post().then(function (resp){
+                if (resp.option !== undefined
+                    && resp.option.hasOwnProperty('value')
+                    && resp.option.value.length === 1
+                    && resp.option.value[0].hasOwnProperty('string')
+                    && typeof (resp.option.value[0].string) === 'string'){
+                    me.getKeys({ pattern: resp.option.value[0].string,
+                        prepare_sync: true }).then(
+                        function (keys){
+                            if (keys.length === 1){
+                                resolve(keys[0]);
+                            } else {
+                                reject(gpgme_error('KEY_NO_DEFAULT'));
+                            }
+                        }, function (error){
+                            reject(error);
+                        });
+                } else {
+                    let msg = createMessage('keylist');
+                    msg.setParameter('secret', true);
+                    msg.post().then(function (result){
+                        if (result.keys.length === 0){
+                            reject(gpgme_error('KEY_NO_DEFAULT'));
+                        } else {
+                            for (let i=0; i< result.keys.length; i++ ) {
+                                if (
+                                    result.keys[i].invalid === false &&
+                                    result.keys[i].expired === false &&
+                                    result.keys[i].revoked === false &&
+                                    result.keys[i].can_sign === true
+                                ) {
+                                    let k = createKey(
+                                        result.keys[i].fingerprint,
+                                        !prepare_sync,
+                                        result.keys[i]);
+                                    resolve(k);
+                                    break;
+                                } else if (i === result.keys.length - 1){
+                                    reject(gpgme_error('KEY_NO_DEFAULT'));
+                                }
+                            }
+                        }
+                    }, function (error){
+                        reject(error);
+                    });
+                }
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+
+    /**
+     * @typedef {Object} importResult The result of a Key update
+     * @property {Object} summary Numerical summary of the result. See the
+     * feedbackValues variable for available Keys values and the gnupg
+     * documentation.
+     * https://www.gnupg.org/documentation/manuals/gpgme/Importing-Keys.html
+     * for details on their meaning.
+     * @property {Array<importedKeyResult>} Keys Array of Object containing
+     * GPGME_Keys with additional import information
+     *
+     */
+
+    /**
+     * @typedef {Object} importedKeyResult
+     * @property {GPGME_Key} key The resulting key
+     * @property {String} status:
+     *  'nochange' if the Key was not changed,
+     *  'newkey' if the Key was imported in gpg, and did not exist
+     *    previously,
+     *  'change' if the key existed, but details were updated. For details,
+     *    Key.changes is available.
+     * @property {Boolean} changes.userId Changes in userIds
+     * @property {Boolean} changes.signature Changes in signatures
+     * @property {Boolean} changes.subkey Changes in subkeys
+     */
+
+    /**
+     * Import an armored Key block into gnupg. Note that this currently
+     * will not succeed on private Key blocks.
+     * @param {String} armored Armored Key block of the Key(s) to be
+     * imported into gnupg
+     * @param {Boolean} prepare_sync prepare the keys for synched use
+     * (see {@link getKeys}).
+     * @returns {Promise<importResult>} A summary and Keys considered.
+     * @async
+     * @static
+     */
+    importKey (armored, prepare_sync) {
+        let feedbackValues = ['considered', 'no_user_id', 'imported',
+            'imported_rsa', 'unchanged', 'new_user_ids', 'new_sub_keys',
+            'new_signatures', 'new_revocations', 'secret_read',
+            'secret_imported', 'secret_unchanged', 'skipped_new_keys',
+            'not_imported', 'skipped_v3_keys'];
+        if (!armored || typeof (armored) !== 'string'){
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        let me = this;
+        return new Promise(function (resolve, reject){
+            let msg = createMessage('import');
+            msg.setParameter('data', armored);
+            msg.post().then(function (response){
+                let infos = {};
+                let fprs = [];
+                let summary = {};
+                for (let i=0; i < feedbackValues.length; i++ ){
+                    summary[feedbackValues[i]] =
+                        response.result[feedbackValues[i]];
+                }
+                if (!response.result.hasOwnProperty('imports') ||
+                    response.result.imports.length === 0
+                ){
+                    resolve({ Keys:[],summary: summary });
+                    return;
+                }
+                for (let res=0; res<response.result.imports.length; res++){
+                    let result = response.result.imports[res];
+                    let status = '';
+                    if (result.status === 0){
+                        status = 'nochange';
+                    } else if ((result.status & 1) === 1){
+                        status = 'newkey';
+                    } else {
+                        status = 'change';
+                    }
+                    let changes = {};
+                    changes.userId = (result.status & 2) === 2;
+                    changes.signature = (result.status & 4) === 4;
+                    changes.subkey = (result.status & 8) === 8;
+                    // 16 new secret key: not implemented
+                    fprs.push(result.fingerprint);
+                    infos[result.fingerprint] = {
+                        changes: changes,
+                        status: status
+                    };
+                }
+                let resultset = [];
+                if (prepare_sync === true){
+                    me.getKeys({ pattern: fprs, prepare_sync: true })
+                        .then(function (result){
+                            for (let i=0; i < result.length; i++) {
+                                resultset.push({
+                                    key: result[i],
+                                    changes:
+                                        infos[result[i].fingerprint].changes,
+                                    status: infos[result[i].fingerprint].status
+                                });
+                            }
+                            resolve({ Keys:resultset,summary: summary });
+                        }, function (error){
+                            reject(error);
+                        });
+                } else {
+                    for (let i=0; i < fprs.length; i++) {
+                        resultset.push({
+                            key: createKey(fprs[i]),
+                            changes: infos[fprs[i]].changes,
+                            status: infos[fprs[i]].status
+                        });
+                    }
+                    resolve({ Keys:resultset,summary:summary });
+                }
+
+            }, function (error){
+                reject(error);
+            });
+
+
+        });
+
+
+    }
+
+    /**
+     * Convenience function for deleting a Key. See {@link Key#delete} for
+     * further information about the return values.
+     * @param {String} fingerprint
+     * @returns {Promise<Boolean>}
+     * @async
+     * @static
+     */
+    deleteKey (fingerprint){
+        if (isFingerprint(fingerprint) === true) {
+            let key = createKey(fingerprint);
+            return key.delete();
+        } else {
+            return Promise.reject(gpgme_error('KEY_INVALID'));
+        }
+    }
+
+    /**
+     * Generates a new Key pair directly in gpg, and returns a GPGME_Key
+     * representing that Key. Please note that due to security concerns,
+     * secret Keys can not be deleted or exported from inside gpgme.js.
+     * @param {Object} options
+     * @param {String} option.userId The user Id, e.g. 'Foo Bar <foo@bar.baz>'
+     * @param {String} option.algo (optional) algorithm (and optionally key
+     * size) to be used. See {@link supportedKeyAlgos} below for supported
+     * values. If ommitted, 'default' is used.
+     * @param {Number} option.expires (optional) Expiration time in seconds
+     * from now. If not set or set to 0, expiration will be 'never'
+     *
+     * @return {Promise<Key|GPGME_Error>}
+     * @async
+     */
+    generateKey ({ userId, algo = 'default', expires= 0 } = {}){
+        if (typeof userId !== 'string'
+            // eslint-disable-next-line no-use-before-define
+            || (algo && supportedKeyAlgos.indexOf(algo) < 0 )
+            || (!Number.isInteger(expires) || expires < 0 )
+        ){
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        // eslint-disable-next-line no-use-before-define
+        let me = this;
+        return new Promise(function (resolve, reject){
+            let msg = createMessage('createkey');
+            msg.setParameter('userid', userId);
+            msg.setParameter('algo', algo);
+            msg.setParameter('expires', expires);
+            msg.post().then(function (response){
+                me.getKeys({
+                    pattern: response.fingerprint,
+                    prepare_sync: true
+                }).then(function (result){
+                    resolve(result);
+                }, function (error){
+                    reject(error);
+                });
+            }, function (error) {
+                reject(error);
+            });
+        });
+    }
+}
+
+
+/**
+ * List of algorithms supported for key generation. Please refer to the gnupg
+ * documentation for details
+ */
+const supportedKeyAlgos = [
+    'default', 'future-default',
+    'rsa', 'rsa2048', 'rsa3072', 'rsa4096',
+    'dsa', 'dsa2048', 'dsa3072', 'dsa4096',
+    'elg', 'elg2048', 'elg3072', 'elg4096',
+    'ed25519',
+    'cv25519',
+    'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1',
+    'NIST P-256', 'NIST P-384', 'NIST P-521'
+];
diff --git a/lang/js/src/Makefile.am b/lang/js/src/Makefile.am
new file mode 100644 (file)
index 0000000..dc58fd3
--- /dev/null
@@ -0,0 +1,30 @@
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Intevation GmbH
+#
+# This file is part of GPGME.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+
+EXTRA_DIST = Connection.js \
+             Errors.js \
+             gpgmejs.js \
+             Helpers.js \
+             index.js \
+             Key.js \
+             Keyring.js \
+             Message.js \
+             permittedOperations.js \
+             Signature.js
diff --git a/lang/js/src/Makefile.in b/lang/js/src/Makefile.in
new file mode 100644 (file)
index 0000000..fe5e274
--- /dev/null
@@ -0,0 +1,540 @@
+# Makefile.in generated by automake 1.14.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile.am for gpgme.js.
+# Copyright (C) 2018 Intevation GmbH
+#
+# This file is part of GPGME.
+#
+# gpgme.js 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.js 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+VPATH = @srcdir@
+am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \  ]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs  ]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = lang/js/src
+DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
+       $(top_srcdir)/build-aux/mkinstalldirs
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+       $(top_srcdir)/m4/ax_pkg_swig.m4 \
+       $(top_srcdir)/m4/ax_python_devel.m4 \
+       $(top_srcdir)/m4/glib-2.0.m4 $(top_srcdir)/m4/glibc21.m4 \
+       $(top_srcdir)/m4/gnupg-ttyname.m4 \
+       $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/libassuan.m4 \
+       $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+       $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+       $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/pkg.m4 \
+       $(top_srcdir)/m4/python.m4 $(top_srcdir)/m4/qt.m4 \
+       $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
+CONFIG_HEADER = $(top_builddir)/conf/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AS = @AS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_FILEVERSION = @BUILD_FILEVERSION@
+BUILD_REVISION = @BUILD_REVISION@
+BUILD_TIMESTAMP = @BUILD_TIMESTAMP@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENABLED_LANGUAGES = @ENABLED_LANGUAGES@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GITLOG_TO_CHANGELOG = @GITLOG_TO_CHANGELOG@
+GLIBC21 = @GLIBC21@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GPGME_CONFIG_API_VERSION = @GPGME_CONFIG_API_VERSION@
+GPGME_CONFIG_AVAIL_LANG = @GPGME_CONFIG_AVAIL_LANG@
+GPGME_CONFIG_CFLAGS = @GPGME_CONFIG_CFLAGS@
+GPGME_CONFIG_HOST = @GPGME_CONFIG_HOST@
+GPGME_CONFIG_LIBS = @GPGME_CONFIG_LIBS@
+GPGME_QTTEST_CFLAGS = @GPGME_QTTEST_CFLAGS@
+GPGME_QTTEST_LIBS = @GPGME_QTTEST_LIBS@
+GPGME_QT_CFLAGS = @GPGME_QT_CFLAGS@
+GPGME_QT_LIBS = @GPGME_QT_LIBS@
+GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@
+GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@
+GPG_ERROR_LIBS = @GPG_ERROR_LIBS@
+GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@
+GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@
+GRAPHVIZ = @GRAPHVIZ@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+HAVE_DOT = @HAVE_DOT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@
+LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@
+LIBASSUAN_LIBS = @LIBASSUAN_LIBS@
+LIBGPGMEPP_LT_AGE = @LIBGPGMEPP_LT_AGE@
+LIBGPGMEPP_LT_CURRENT = @LIBGPGMEPP_LT_CURRENT@
+LIBGPGMEPP_LT_REVISION = @LIBGPGMEPP_LT_REVISION@
+LIBGPGME_LT_AGE = @LIBGPGME_LT_AGE@
+LIBGPGME_LT_CURRENT = @LIBGPGME_LT_CURRENT@
+LIBGPGME_LT_REVISION = @LIBGPGME_LT_REVISION@
+LIBOBJS = @LIBOBJS@
+LIBQGPGME_LT_AGE = @LIBQGPGME_LT_AGE@
+LIBQGPGME_LT_CURRENT = @LIBQGPGME_LT_CURRENT@
+LIBQGPGME_LT_REVISION = @LIBQGPGME_LT_REVISION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MOC = @MOC@
+MOC2 = @MOC2@
+NEED__FILE_OFFSET_BITS = @NEED__FILE_OFFSET_BITS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PYTHON = @PYTHON@
+PYTHONS = @PYTHONS@
+PYTHON_CPPFLAGS = @PYTHON_CPPFLAGS@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_EXTRA_LDFLAGS = @PYTHON_EXTRA_LDFLAGS@
+PYTHON_EXTRA_LIBS = @PYTHON_EXTRA_LIBS@
+PYTHON_LDFLAGS = @PYTHON_LDFLAGS@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_SITE_PKG = @PYTHON_SITE_PKG@
+PYTHON_VERSION = @PYTHON_VERSION@
+QTCHOOSER = @QTCHOOSER@
+RANLIB = @RANLIB@
+RC = @RC@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SWIG = @SWIG@
+SWIG_LIB = @SWIG_LIB@
+SYSROOT = @SYSROOT@
+VERSION = @VERSION@
+VERSION_MAJOR = @VERSION_MAJOR@
+VERSION_MICRO = @VERSION_MICRO@
+VERSION_MINOR = @VERSION_MINOR@
+VERSION_NUMBER = @VERSION_NUMBER@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+emacs_local_vars_begin = @emacs_local_vars_begin@
+emacs_local_vars_end = @emacs_local_vars_end@
+emacs_local_vars_read_only = @emacs_local_vars_read_only@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = Connection.js \
+             Errors.js \
+             gpgmejs.js \
+             Helpers.js \
+             index.js \
+             Key.js \
+             Keyring.js \
+             Message.js \
+             permittedOperations.js \
+             Signature.js
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+               && { if test -f $@; then exit 0; else break; fi; }; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lang/js/src/Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --gnu lang/js/src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(DISTFILES)
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+       -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+       cscopelist-am ctags-am distclean distclean-generic \
+       distclean-libtool distdir dvi dvi-am html html-am info info-am \
+       install install-am install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-ps install-ps-am \
+       install-strip installcheck installcheck-am installdirs \
+       maintainer-clean maintainer-clean-generic mostlyclean \
+       mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+       tags-am uninstall uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/lang/js/src/Message.js b/lang/js/src/Message.js
new file mode 100644 (file)
index 0000000..9f6abb7
--- /dev/null
@@ -0,0 +1,243 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+import { permittedOperations } from './permittedOperations';
+import { gpgme_error } from './Errors';
+import { Connection } from './Connection';
+
+/**
+ * Initializes a message for gnupg, validating the message's purpose with
+ *   {@link permittedOperations} first
+ * @param {String} operation
+ * @returns {GPGME_Message} The Message object
+ */
+export function createMessage (operation){
+    if (typeof (operation) !== 'string'){
+        throw gpgme_error('PARAM_WRONG');
+    }
+    if (permittedOperations.hasOwnProperty(operation)){
+        return new GPGME_Message(operation);
+    } else {
+        throw gpgme_error('MSG_WRONG_OP');
+    }
+}
+
+/**
+ * A Message collects, validates and handles all information required to
+ * successfully establish a meaningful communication with gpgme-json via
+ * [Connection.post]{@link Connection#post}. The definition on which
+ * communication is available can be found in {@link permittedOperations}.
+ * @class
+ * @protected
+ */
+export class GPGME_Message {
+
+    constructor (operation){
+        this._msg = {
+            op: operation,
+            chunksize: 1023* 1024
+        };
+        this._expected = null;
+    }
+
+    get operation (){
+        return this._msg.op;
+    }
+
+    set expected (value){
+        if (value === 'uint8' || value === 'base64'){
+            this._expected = value;
+        }
+    }
+
+    get expected () {
+        return this._expected;
+    }
+    /**
+     * The maximum size of responses from gpgme in bytes. As of September 2018,
+     * most browsers will only accept answers up to 1 MB of size.
+     * Everything above that threshold will not pass through
+     * nativeMessaging; answers that are larger need to be sent in parts.
+     * The lower limit is set to 10 KB. Messages smaller than the threshold
+     * will not encounter problems, larger messages will be received in
+     * chunks. If the value is not explicitly specified, 1023 KB is used.
+     */
+    set chunksize (value){
+        if (
+            Number.isInteger(value) &&
+            value > 10 * 1024 &&
+            value <= 1024 * 1024
+        ){
+            this._msg.chunksize = value;
+        }
+    }
+
+    get chunksize (){
+        return this._msg.chunksize;
+    }
+
+    /**
+     * Returns the prepared message after their parameters and the completion
+     * of required parameters have been checked.
+     * @returns {Object|null} Object to be posted to gnupg, or null if
+     * incomplete
+     */
+    get message () {
+        if (this.isComplete() === true){
+            return this._msg;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sets a parameter for the message. It validates with
+     * {@link permittedOperations}
+     * @param {String} param Parameter to set
+     * @param {any} value Value to set
+     * @returns {Boolean} True if the parameter was set successfully.
+     * Throws errors if the parameters don't match the message operation
+     */
+    setParameter ( param,value ){
+        if (!param || typeof (param) !== 'string'){
+            throw gpgme_error('PARAM_WRONG');
+        }
+        let po = permittedOperations[this._msg.op];
+        if (!po){
+            throw gpgme_error('MSG_WRONG_OP');
+        }
+        let poparam = null;
+        if (po.required.hasOwnProperty(param)){
+            poparam = po.required[param];
+        } else if (po.optional.hasOwnProperty(param)){
+            poparam = po.optional[param];
+        } else {
+            throw gpgme_error('PARAM_WRONG');
+        }
+        // check incoming value for correctness
+        let checktype = function (val){
+            switch (typeof (val)){
+            case 'string':
+                if (poparam.allowed.indexOf(typeof (val)) >= 0
+                        && val.length > 0) {
+                    return true;
+                }
+                throw gpgme_error('PARAM_WRONG');
+            case 'number':
+                if (
+                    poparam.allowed.indexOf('number') >= 0
+                        && isNaN(value) === false){
+                    return true;
+                }
+                throw gpgme_error('PARAM_WRONG');
+
+            case 'boolean':
+                if (poparam.allowed.indexOf('boolean') >= 0){
+                    return true;
+                }
+                throw gpgme_error('PARAM_WRONG');
+            case 'object':
+                if (Array.isArray(val)){
+                    if (poparam.array_allowed !== true){
+                        throw gpgme_error('PARAM_WRONG');
+                    }
+                    for (let i=0; i < val.length; i++){
+                        let res = checktype(val[i]);
+                        if (res !== true){
+                            return res;
+                        }
+                    }
+                    if (val.length > 0) {
+                        return true;
+                    }
+                } else if (val instanceof Uint8Array){
+                    if (poparam.allowed.indexOf('Uint8Array') >= 0){
+                        return true;
+                    }
+                    throw gpgme_error('PARAM_WRONG');
+                } else {
+                    throw gpgme_error('PARAM_WRONG');
+                }
+                break;
+            default:
+                throw gpgme_error('PARAM_WRONG');
+            }
+        };
+        let typechecked = checktype(value);
+        if (typechecked !== true){
+            return typechecked;
+        }
+        if (poparam.hasOwnProperty('allowed_data')){
+            if (poparam.allowed_data.indexOf(value) < 0){
+                return gpgme_error('PARAM_WRONG');
+            }
+        }
+        this._msg[param] = value;
+        return true;
+    }
+
+
+    /**
+     * Check if the message has the minimum requirements to be sent, that is
+     * all 'required' parameters according to {@link permittedOperations}.
+     * @returns {Boolean} true if message is complete.
+     */
+    isComplete (){
+        if (!this._msg.op){
+            return false;
+        }
+        let reqParams = Object.keys(
+            permittedOperations[this._msg.op].required);
+        let msg_params = Object.keys(this._msg);
+        for (let i=0; i < reqParams.length; i++){
+            if (msg_params.indexOf(reqParams[i]) < 0){
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Sends the Message via nativeMessaging and resolves with the answer.
+     * @returns {Promise<Object>} GPGME response
+     * @async
+     */
+    post (){
+        let me = this;
+        return new Promise(function (resolve, reject) {
+            if (me.isComplete() === true) {
+
+                let conn  = new Connection;
+                conn.post(me).then(function (response) {
+                    resolve(response);
+                }, function (reason) {
+                    reject(reason);
+                });
+            }
+            else {
+                reject(gpgme_error('MSG_INCOMPLETE'));
+            }
+        });
+    }
+
+}
diff --git a/lang/js/src/Signature.js b/lang/js/src/Signature.js
new file mode 100644 (file)
index 0000000..f848e32
--- /dev/null
@@ -0,0 +1,206 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+import { gpgme_error } from './Errors';
+
+/**
+ * Validates an object containing a signature, as sent by the nativeMessaging
+ * interface
+ * @param {Object} sigObject Object as returned by gpgme-json. The definition
+ * of the expected values are to be found in {@link expKeys}, {@link expSum},
+ * {@link expNote}.
+ * @returns {GPGME_Signature|GPGME_Error} Signature Object
+ * @private
+ */
+export function createSignature (sigObject){
+    if (
+        typeof (sigObject) !=='object' ||
+        !sigObject.hasOwnProperty('summary') ||
+        !sigObject.hasOwnProperty('fingerprint') ||
+        !sigObject.hasOwnProperty('timestamp')
+        // TODO check if timestamp is mandatory in specification
+    ){
+        return gpgme_error('SIG_WRONG');
+    }
+    let keys = Object.keys(sigObject);
+    for (let i=0; i< keys.length; i++){
+        // eslint-disable-next-line no-use-before-define
+        if ( typeof (sigObject[keys[i]]) !== expKeys[keys[i]] ){
+            return gpgme_error('SIG_WRONG');
+        }
+    }
+    let sumkeys = Object.keys(sigObject.summary);
+    for (let i=0; i< sumkeys.length; i++){
+        // eslint-disable-next-line no-use-before-define
+        if ( typeof (sigObject.summary[sumkeys[i]]) !== expSum[sumkeys[i]] ){
+            return gpgme_error('SIG_WRONG');
+        }
+    }
+    if (sigObject.hasOwnProperty('notations')){
+        if (!Array.isArray(sigObject.notations)){
+            return gpgme_error('SIG_WRONG');
+        }
+        for (let i=0; i < sigObject.notations.length; i++){
+            let notation = sigObject.notations[i];
+            let notekeys = Object.keys(notation);
+            for (let j=0; j < notekeys.length; j++){
+                // eslint-disable-next-line no-use-before-define
+                if ( typeof (notation[notekeys[j]]) !== expNote[notekeys[j]] ){
+                    return gpgme_error('SIG_WRONG');
+                }
+            }
+        }
+    }
+    return new GPGME_Signature(sigObject);
+}
+
+
+/**
+ * Representing the details of a signature. The full details as given by
+ * gpgme-json can be read from the _rawSigObject.
+ *
+ * Note to reviewers: This class should be read only except via
+ * {@link createSignature}
+ * @protected
+ * @class
+ */
+class GPGME_Signature {
+
+    constructor (sigObject){
+        this._rawSigObject = sigObject;
+    }
+    /**
+     * @returns {String} the fingerprint of this signature
+     */
+    get fingerprint (){
+        if (!this._rawSigObject.fingerprint){
+            throw gpgme_error('SIG_WRONG');
+        } else {
+            return this._rawSigObject.fingerprint;
+        }
+    }
+
+    /**
+     * The expiration of this Signature as Javascript date, or null if
+     * signature does not expire
+     * @returns {Date | null}
+     */
+    get expiration (){
+        if (!this._rawSigObject.exp_timestamp){
+            return null;
+        }
+        return new Date(this._rawSigObject.exp_timestamp* 1000);
+    }
+
+    /**
+     * The creation date of this Signature in Javascript Date
+     * @returns {Date}
+     */
+    get timestamp (){
+        return new Date(this._rawSigObject.timestamp * 1000);
+    }
+
+    /**
+     * The overall validity of the key. If false, errorDetails may contain
+     * additional information.
+     */
+    get valid () {
+        if (this._rawSigObject.summary.valid === true){
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Object with boolean properties giving more information on non-valid
+     * signatures. Refer to the [gpgme docs]{@link https://www.gnupg.org/documentation/manuals/gpgme/Verify.html}
+     * for details on the values.
+     */
+    get errorDetails (){
+        let properties = ['revoked', 'key-expired', 'sig-expired',
+            'key-missing', 'crl-missing', 'crl-too-old', 'bad-policy',
+            'sys-error'];
+        let result = {};
+        for (let i=0; i< properties.length; i++){
+            if ( this._rawSigObject.summary.hasOwnProperty(properties[i]) ){
+                result[properties[i]] = this._rawSigObject.summary[properties[i]];
+            }
+        }
+        return result;
+    }
+}
+
+/**
+ * Expected keys and their value's type for the signature Object
+ * @private
+ */
+const expKeys = {
+    'wrong_key_usage': 'boolean',
+    'chain_model': 'boolean',
+    'summary': 'object',
+    'is_de_vs': 'boolean',
+    'status_string':'string',
+    'fingerprint':'string',
+    'validity_string': 'string',
+    'pubkey_algo_name':'string',
+    'hash_algo_name':'string',
+    'pka_address':'string',
+    'status_code':'number',
+    'timestamp':'number',
+    'exp_timestamp':'number',
+    'pka_trust':'number',
+    'validity':'number',
+    'validity_reason':'number',
+    'notations': 'object'
+};
+
+/**
+ * Keys and their value's type for the summary
+ * @private
+ */
+const expSum = {
+    'valid': 'boolean',
+    'green': 'boolean',
+    'red': 'boolean',
+    'revoked': 'boolean',
+    'key-expired': 'boolean',
+    'sig-expired': 'boolean',
+    'key-missing': 'boolean',
+    'crl-missing': 'boolean',
+    'crl-too-old': 'boolean',
+    'bad-policy': 'boolean',
+    'sys-error': 'boolean',
+    'sigsum': 'object'
+};
+
+/**
+ * Keys and their value's type for notations objects
+ * @private
+ */
+const expNote = {
+    'human_readable': 'boolean',
+    'critical':'boolean',
+    'name': 'string',
+    'value': 'string',
+    'flags': 'number'
+};
diff --git a/lang/js/src/gpgmejs.js b/lang/js/src/gpgmejs.js
new file mode 100644 (file)
index 0000000..9105724
--- /dev/null
@@ -0,0 +1,465 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+
+import { GPGME_Message, createMessage } from './Message';
+import { toKeyIdArray } from './Helpers';
+import { gpgme_error } from './Errors';
+import { GPGME_Keyring } from './Keyring';
+import { createSignature } from './Signature';
+
+/**
+ * @typedef {Object} decrypt_result
+ * @property {String|Uint8Array} data The decrypted data.
+ * @property {String} format Indicating how the data was converted after being
+ * received from gpgme:
+ * <pre>
+ *      'ascii': Data was ascii-encoded and no further processed
+ *      'string': Data was decoded into an utf-8 string,
+ *      'base64': Data was not processed and is a base64 string
+ *      'uint8': data was turned into a Uint8Array
+ * </pre>
+ * @property {Boolean} is_mime (optional) the data claims to be a MIME object.
+ * @property {String} file_name (optional) the original file name
+ * @property {signatureDetails} signatures Verification details for
+ * signatures
+ */
+
+/**
+ * @typedef {Object} signatureDetails
+ * @property {Boolean} all_valid Quick summary. True if all signatures are
+ * fully valid according to gnupg.
+ * @property {Number} count Number of signatures parsed.
+ * @property {Number} failures Number of signatures not passing as valid. This
+ * may imply bad signatures, or signatures with e.g. the public Key not being
+ * available.
+ * @property {GPGME_Signature[]} signatures.good Array of all signatures
+ * considered valid.
+ * @property {GPGME_Signature[]} signatures.bad All invalid signatures.
+ */
+
+/**
+ * @typedef {Object} encrypt_result The result of an encrypt operation,
+ * containing the encrypted data and some additional information.
+ * @property {String} data The encrypted message.
+ * @property {String} format Indicating how the data was converted after being
+ *  received from gpgme.
+ * <pre>
+ *      'ascii': Data was ascii-encoded and no further processed
+ *      'string': Data was decoded into an utf-8 string,
+ *      'base64': Data was not processed and is a base64 string
+ *      'uint8': Data was turned into a Uint8Array
+ * </pre>
+ */
+
+/**
+ * @typedef { GPGME_Key | String | Object } inputKeys
+ * Accepts different identifiers of a gnupg Key that can be parsed by
+ * {@link toKeyIdArray}. Expected inputs are: One or an array of
+ * GPGME_Keys; one or an array of fingerprint strings; one or an array of
+ * openpgpjs Key objects.
+ */
+
+/**
+ * @typedef {Object} signResult The result of a signing operation
+ * @property {String} data The resulting data. Includes the signature in
+ *  clearsign mode
+ * @property {String} signature The detached signature (only present in in
+ * detached mode)
+ */
+
+/** @typedef {Object} verifyResult The result of a verification
+ * @property {Boolean} data: The verified data
+ * @property {Boolean} is_mime (optional) the data claims to be a MIME
+ * object.
+ * @property {signatureDetails} signatures Verification details for
+ * signatures
+ */
+
+/**
+ * The main entry point for gpgme.js.
+ * @class
+ */
+export class GpgME {
+
+    constructor (){
+        this._Keyring = null;
+    }
+
+    set Keyring (keyring){
+        if (keyring && keyring instanceof GPGME_Keyring){
+            this._Keyring = keyring;
+        }
+    }
+
+    /**
+     * Accesses the {@link GPGME_Keyring}. From the Keyring, all Keys can be
+     * accessed.
+     */
+    get Keyring (){
+        if (!this._Keyring){
+            this._Keyring = new GPGME_Keyring;
+        }
+        return this._Keyring;
+    }
+
+    /**
+     * Encrypt data for the recipients specified in publicKeys. If privateKeys
+     * are submitted, the data will be signed by those Keys.
+     * @param {Object} options
+     * @param {String|Object} options.data text/data to be encrypted as String.
+     * Also accepts Objects with a getText method.
+     * @param {inputKeys} options.publicKeys
+     * Keys used to encrypt the message
+     * @param {inputKeys} options.secretKeys (optional) Keys used to sign the
+     * message. If Keys are present, the  operation requested is assumed
+     * to be 'encrypt and sign'
+     * @param {Boolean} options.base64 (optional, default: false) The data will
+     * be interpreted as base64 encoded data.
+     * @param {Boolean} options.armor (optional, default: true) Request the
+     * output as armored block.
+     * @param {Boolean} options.wildcard (optional, default: false) If true,
+     * recipient information will not be added to the message.
+     * @param {Boolean} options.always_trust (optional, default true) This
+     * assumes that used keys are fully trusted. If set to false, encryption to
+     * a key not fully trusted in gnupg will fail.
+     * @param {String} options.expect (default: 'base64') In case of
+     * armored:false, request how to return the binary result.
+     * Accepts 'base64' or 'uint8'
+     * @param {Object} options.additional use additional valid gpg options as
+     * defined in {@link permittedOperations}
+     * @returns {Promise<encrypt_result>} Object containing the encrypted
+     * message and additional info.
+     * @async
+     */
+    encrypt ({ data, publicKeys, secretKeys, base64 = false, armor = true,
+        wildcard, always_trust = true, expect = 'base64',
+        additional = {} } = {}){
+        if (typeof arguments[0] !== 'object') {
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        if (!data || !publicKeys){
+            return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
+        }
+        let msg = createMessage('encrypt');
+        if (msg instanceof Error){
+            return Promise.reject(msg);
+        }
+        if (armor === false){
+            msg.setParameter('armor', false);
+            if (expect === 'uint8' || expect === 'base64') {
+                msg.expected = expect;
+            } else {
+                return Promise.reject(gpgme_error('PARAM_WRONG'));
+            }
+        } else if (armor === true) {
+            msg.setParameter('armor', true);
+        }
+        if (base64 === true) {
+            msg.setParameter('base64', true);
+        }
+        if (always_trust === true) {
+            msg.setParameter('always-trust', true);
+        }
+        let pubkeys = toKeyIdArray(publicKeys);
+        if (!pubkeys.length) {
+            return Promise.reject(gpgme_error('MSG_NO_KEYS'));
+        }
+        msg.setParameter('keys', pubkeys);
+        let sigkeys = toKeyIdArray(secretKeys);
+        if (sigkeys.length > 0) {
+            msg.setParameter('signing_keys', sigkeys);
+        }
+        putData(msg, data);
+        if (wildcard === true){
+            msg.setParameter('throw-keyids', true);
+        }
+        if (additional){
+            let additional_Keys = Object.keys(additional);
+            for (let k = 0; k < additional_Keys.length; k++) {
+                try {
+                    msg.setParameter(additional_Keys[k],
+                        additional[additional_Keys[k]]);
+                }
+                catch (error){
+                    return Promise.reject(error);
+                }
+            }
+        }
+        if (msg.isComplete() === true){
+            return msg.post();
+        } else {
+            return Promise.reject(gpgme_error('MSG_INCOMPLETE'));
+        }
+    }
+
+    /**
+    * Decrypts (and verifies, if applicable) a message.
+    * @param {Object} options
+    * @param {String|Object} options.data text/data to be decrypted. Accepts
+    * Strings and Objects with a getText method.
+    * @param {Boolean} options.base64 (optional, default: false). Indicate that
+    * the input given is base64-encoded binary instead of an armored block in
+    * gpg armored form.
+    * @param {String} options.expect (optional). By default, the output is
+    * expected to be a string compatible with javascript. In cases of binary
+    * data the decryption may fail due to encoding problems. For data expected
+    * to return as binary data, the decroding after decryption can be bypassed:
+    * <pre>
+    *   'uint8': Return as Uint8Array
+    *   'base64': Return as unprocessed (base64 encoded) string.
+    * </pre>
+    * @returns {Promise<decrypt_result>} Decrypted Message and information
+    * @async
+    */
+    decrypt ({ data, base64, expect } = {}){
+        if (typeof arguments[0] !== 'object') {
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        if (!data){
+            return Promise.reject(gpgme_error('MSG_EMPTY'));
+        }
+        let msg = createMessage('decrypt');
+
+        if (msg instanceof Error){
+            return Promise.reject(msg);
+        }
+        if (base64 === true){
+            msg.setParameter('base64', true);
+        }
+        if (expect === 'base64' || expect === 'uint8'){
+            msg.expected = expect;
+        }
+        putData(msg, data);
+        return new Promise(function (resolve, reject){
+            msg.post().then(function (result){
+                let returnValue = { data: result.data };
+                returnValue.format = result.format ? result.format : null;
+                if (result.hasOwnProperty('dec_info')){
+                    returnValue.is_mime = result.dec_info.is_mime ? true: false;
+                    if (result.dec_info.file_name) {
+                        returnValue.file_name = result.dec_info.file_name;
+                    }
+                }
+                if (!returnValue.file_name) {
+                    returnValue.file_name = null;
+                }
+                if (result.hasOwnProperty('info')
+                    && result.info.hasOwnProperty('signatures')
+                    && Array.isArray(result.info.signatures)
+                ) {
+                    returnValue.signatures = collectSignatures(
+                        result.info.signatures);
+                }
+                if (returnValue.signatures instanceof Error){
+                    reject(returnValue.signatures);
+                } else {
+                    resolve(returnValue);
+                }
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+
+    /**
+     * Sign a Message.
+     * @param {Object} options Signing options
+     * @param {String|Object} options.data text/data to be signed. Accepts
+     * Strings and Objects with a getText method.
+     * @param {inputKeys} options.keys The key/keys to use for signing
+     * @param {String} options.mode The signing mode. Currently supported:
+     * <pre>
+     *      'clearsign':The Message is embedded into the signature;
+     *      'detached': The signature is stored separately
+     * </pre>
+     * @param {Boolean} options.base64 input is considered base64
+     * @returns {Promise<signResult>}
+     * @async
+     */
+    sign ({ data, keys, mode = 'clearsign', base64 } = {}){
+        if (typeof arguments[0] !== 'object') {
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        if (!data){
+            return Promise.reject(gpgme_error('MSG_EMPTY'));
+        }
+        let key_arr = toKeyIdArray(keys);
+        if (key_arr.length === 0){
+            return Promise.reject(gpgme_error('MSG_NO_KEYS'));
+        }
+
+        let msg = createMessage('sign');
+        msg.setParameter('keys', key_arr);
+        if (base64 === true){
+            msg.setParameter('base64', true);
+        }
+        msg.setParameter('mode', mode);
+        putData(msg, data);
+
+        return new Promise(function (resolve,reject) {
+            msg.post().then( function (message) {
+                if (mode === 'clearsign'){
+                    resolve({
+                        data: message.data }
+                    );
+                } else if (mode === 'detached') {
+                    resolve({
+                        data: data,
+                        signature: message.data
+                    });
+                }
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+
+    /**
+     * Verifies data.
+     * @param {Object} options
+     * @param {String|Object} options.data text/data to be verified. Accepts
+     * Strings and Objects with a getText method
+     * @param {String} options.signature A detached signature. If not present,
+     * opaque mode is assumed
+     * @param {Boolean} options.base64 Indicating that data and signature are
+     * base64 encoded
+     * @returns {Promise<verifyResult>}
+     *@async
+    */
+    verify ({ data, signature, base64 } = {}){
+        if (typeof arguments[0] !== 'object') {
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        if (!data){
+            return Promise.reject(gpgme_error('PARAM_WRONG'));
+        }
+        let msg = createMessage('verify');
+        let dt = putData(msg, data);
+        if (dt instanceof Error){
+            return Promise.reject(dt);
+        }
+        if (signature){
+            if (typeof signature !== 'string'){
+                return Promise.reject(gpgme_error('PARAM_WRONG'));
+            } else {
+                msg.setParameter('signature', signature);
+            }
+        }
+        if (base64 === true){
+            msg.setParameter('base64', true);
+        }
+        return new Promise(function (resolve, reject){
+            msg.post().then(function (message){
+                if (!message.info || !message.info.signatures){
+                    reject(gpgme_error('SIG_NO_SIGS'));
+                } else {
+                    let returnValue = {
+                        signatures: collectSignatures(message.info.signatures)
+                    };
+                    if (returnValue.signatures instanceof Error){
+                        reject(returnValue.signatures);
+                    } else {
+                        returnValue.is_mime = message.info.is_mime? true: false;
+                        if (message.info.filename){
+                            returnValue.file_name = message.info.filename;
+                        }
+                        returnValue.data = message.data;
+                        resolve(returnValue);
+                    }
+                }
+            }, function (error){
+                reject(error);
+            });
+        });
+    }
+}
+
+/**
+ * Sets the data of the message, setting flags according on the data type
+ * @param {GPGME_Message} message The message where this data will be set
+ * @param { String| Object } data The data to enter. Expects either a string of
+ * data, or an object with a getText method
+ * @returns {undefined| GPGME_Error} Error if not successful, nothing otherwise
+ * @private
+ */
+function putData (message, data){
+    if (!message || !(message instanceof GPGME_Message)) {
+        return gpgme_error('PARAM_WRONG');
+    }
+    if (!data){
+        return gpgme_error('PARAM_WRONG');
+    } else if (typeof data === 'string') {
+        message.setParameter('data', data);
+    } else if (
+        (typeof data === 'object') &&
+        (typeof data.getText === 'function')
+    ){
+        let txt = data.getText();
+        if (typeof txt === 'string'){
+            message.setParameter('data', txt);
+        } else {
+            return gpgme_error('PARAM_WRONG');
+        }
+
+    } else {
+        return gpgme_error('PARAM_WRONG');
+    }
+}
+
+/**
+ * Parses, validates and converts incoming objects into signatures.
+ * @param {Array<Object>} sigs
+ * @returns {signatureDetails} Details about the signatures
+ * @private
+ */
+function collectSignatures (sigs){
+    if (!Array.isArray(sigs)){
+        return gpgme_error('SIG_NO_SIGS');
+    }
+    let summary = {
+        all_valid: false,
+        count: sigs.length,
+        failures: 0,
+        signatures: {
+            good: [],
+            bad: [],
+        }
+    };
+    for (let i=0; i< sigs.length; i++){
+        let sigObj = createSignature(sigs[i]);
+        if (sigObj instanceof Error) {
+            return gpgme_error('SIG_WRONG');
+        }
+        if (sigObj.valid !== true){
+            summary.failures += 1;
+            summary.signatures.bad.push(sigObj);
+        } else {
+            summary.signatures.good.push(sigObj);
+        }
+    }
+    if (summary.failures === 0){
+        summary.all_valid = true;
+    }
+    return summary;
+}
\ No newline at end of file
diff --git a/lang/js/src/index.js b/lang/js/src/index.js
new file mode 100644 (file)
index 0000000..b8e4274
--- /dev/null
@@ -0,0 +1,58 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+
+import { GpgME } from './gpgmejs';
+import { gpgme_error } from './Errors';
+import { Connection } from './Connection';
+
+/**
+ * Main entry point for gpgme.js. It initializes by testing the nativeMessaging
+ * connection once, and then offers the available functions as method of the
+ * response object.
+ * An unsuccessful attempt will reject as a GPGME_Error.
+ * @param {Object} config (optional) configuration options
+ * @param {Number} config.timeout set the timeout for the initial connection
+ * check. On some machines and operating systems a default timeout of 500 ms is
+ * too low, so a higher number might be attempted.
+ * @returns {Promise<GpgME>}
+ * @async
+ */
+function init ({ timeout = 500 } = {}){
+    return new Promise(function (resolve, reject){
+        const connection = new Connection;
+        connection.checkConnection(false, timeout).then(
+            function (result){
+                if (result === true) {
+                    resolve(new GpgME());
+                } else {
+                    reject(gpgme_error('CONN_NO_CONNECT'));
+                }
+            }, function (){ // unspecific connection error. Should not happen
+                reject(gpgme_error('CONN_NO_CONNECT'));
+            });
+    });
+}
+
+const exportvalue = { init:init };
+export default exportvalue;
\ No newline at end of file
diff --git a/lang/js/src/permittedOperations.js b/lang/js/src/permittedOperations.js
new file mode 100644 (file)
index 0000000..09a1783
--- /dev/null
@@ -0,0 +1,413 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author(s):
+ *     Maximilian Krambach <mkrambach@intevation.de>
+ */
+
+/**
+ * @typedef {Object} messageProperty
+ * A message Property is defined by it's key.
+ * @property {Array<String>} allowed Array of allowed types.
+ * Currently accepted values are 'number', 'string', 'boolean'.
+ * @property {Boolean} array_allowed If the value can be an array of types
+ *      defined in allowed
+ * @property {Array<*>} allowed_data (optional) restricts to the given values
+  */
+
+/**
+ * Definition of the possible interactions with gpgme-json.
+ * @param {Object} operation Each operation is named by a key and contains
+ * the following properties:
+ * @property {messageProperty} required An object with all required parameters
+ * @property {messageProperty} optional An object with all optional parameters
+ * @property {Boolean} pinentry (optional) If true, a password dialog is
+ *      expected, thus a connection tuimeout is not advisable
+ * @property {Object} answer The definition on what to expect as answer, if the
+ *      answer is not an error
+ * @property {Array<String>} answer.type the type(s) as reported by gpgme-json.
+ * @property {Object} answer.payload key-value combinations of expected
+ * properties of an answer and their type ('boolean', 'string', object), which
+ * may need further decoding from base64
+ * @property {Object} answer.info key-value combinations of expected
+ * properties of an answer and their type ('boolean', 'string', object), which
+ * are meant to be data directly sent by gpgme (i.e. user ids)
+  @const
+*/
+export const permittedOperations = {
+    encrypt: {
+        pinentry: true, // TODO only with signing_keys
+        required: {
+            'keys': {
+                allowed: ['string'],
+                array_allowed: true
+            },
+            'data': {
+                allowed: ['string']
+            }
+        },
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'signing_keys': {
+                allowed: ['string'],
+                array_allowed: true
+            },
+            'base64': {
+                allowed: ['boolean']
+            },
+            'mime': {
+                allowed: ['boolean']
+            },
+            'armor': {
+                allowed: ['boolean']
+            },
+            'always-trust': {
+                allowed: ['boolean']
+            },
+            'no-encrypt-to': {
+                allowed: ['string'],
+                array_allowed: true
+            },
+            'no-compress': {
+                allowed: ['boolean']
+            },
+            'throw-keyids': {
+                allowed: ['boolean']
+            },
+            'want-address': {
+                allowed: ['boolean']
+            },
+            'wrap': {
+                allowed: ['boolean']
+            },
+            'sender': {
+                allowed: ['string']
+            },
+            'file_name': {
+                allowed: ['string']
+            }
+        },
+        answer: {
+            type: ['ciphertext'],
+            payload: {
+                'data': 'string'
+            },
+            info: {
+                'base64':'boolean'
+            }
+        }
+    },
+
+    decrypt: {
+        pinentry: true,
+        required: {
+            'data': {
+                allowed: ['string']
+            }
+        },
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'base64': {
+                allowed: ['boolean']
+            }
+        },
+        answer: {
+            type: ['plaintext'],
+            payload: {
+                'data': 'string',
+            },
+            info: {
+                'base64': 'boolean',
+                'mime': 'boolean',
+                'info': 'object',
+                'dec_info': 'object'
+            }
+        }
+    },
+
+    sign: {
+        pinentry: true,
+        required: {
+            'data': {
+                allowed: ['string'] },
+            'keys': {
+                allowed: ['string'],
+                array_allowed: true
+            }
+        },
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'sender': {
+                allowed: ['string'],
+            },
+            'mode': {
+                allowed: ['string'],
+                allowed_data: ['detached', 'clearsign']
+                // TODO 'opaque' is not used, but available on native app
+            },
+            'base64': {
+                allowed: ['boolean']
+            },
+            'armor': {
+                allowed: ['boolean']
+            },
+        },
+        answer: {
+            type: ['signature', 'ciphertext'],
+            payload: {
+                'data': 'string',
+            },
+            info: {
+                'base64':'boolean'
+            }
+        }
+    },
+
+    // note: For the meaning of the optional keylist flags, refer to
+    // https://www.gnupg.org/documentation/manuals/gpgme/Key-Listing-Mode.html
+    keylist:{
+        required: {},
+
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'secret': {
+                allowed: ['boolean']
+            },
+            'extern': {
+                allowed: ['boolean']
+            },
+            'local':{
+                allowed: ['boolean']
+            },
+            'locate': {
+                allowed: ['boolean']
+            },
+            'sigs':{
+                allowed: ['boolean']
+            },
+            'notations':{
+                allowed: ['boolean']
+            },
+            'tofu': {
+                allowed: ['boolean']
+            },
+            'ephemeral': {
+                allowed: ['boolean']
+            },
+            'validate': {
+                allowed: ['boolean']
+            },
+            'keys': {
+                allowed: ['string'],
+                array_allowed: true
+            }
+        },
+        answer: {
+            type: ['keys'],
+            info: {
+                'keys': 'object',
+                'base64': 'boolean',
+            }
+        }
+    },
+
+    export: {
+        required: {},
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'keys': {
+                allowed: ['string'],
+                array_allowed: true
+            },
+            'armor': {
+                allowed: ['boolean']
+            },
+            'extern': {
+                allowed: ['boolean']
+            },
+            'minimal': {
+                allowed: ['boolean']
+            },
+            'raw': {
+                allowed: ['boolean']
+            },
+            'pkcs12': {
+                allowed: ['boolean']
+            },
+            'with-sec-fprs': {
+                allowed: ['boolean']
+            }
+            // secret: not yet implemented
+        },
+        answer: {
+            type: ['keys'],
+            payload: {
+                'data': 'string',
+            },
+            info: {
+                'base64': 'boolean',
+                'sec-fprs': 'object'
+            }
+        }
+    },
+
+    import: {
+        required: {
+            'data': {
+                allowed: ['string']
+            }
+        },
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'base64': {
+                allowed: ['boolean']
+            },
+        },
+        answer: {
+            type: [],
+            info: {
+                'result': 'object'
+            }
+        }
+    },
+
+    delete: {
+        pinentry: true,
+        required:{
+            'key': {
+                allowed: ['string']
+            }
+        },
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+        },
+        answer: {
+            info: {
+                'success': 'boolean'
+            }
+        }
+    },
+
+    version: {
+        required: {},
+        optional: {},
+        answer: {
+            type:  [''],
+            info: {
+                'gpgme': 'string',
+                'info': 'object'
+            }
+        }
+    },
+
+    createkey: {
+        pinentry: true,
+        required: {
+            userid: {
+                allowed: ['string']
+            }
+        },
+        optional: {
+            algo: {
+                allowed: ['string']
+            },
+            expires: {
+                allowed: ['number'],
+            }
+        },
+        answer: {
+            type: [''],
+            info: { 'fingerprint': 'string' }
+        }
+    },
+
+    verify: {
+        required: {
+            data: {
+                allowed: ['string']
+            }
+        },
+        optional: {
+            'protocol': {
+                allowed: ['string'],
+                allowed_data: ['cms', 'openpgp']
+            },
+            'signature': {
+                allowed: ['string']
+            },
+            'base64':{
+                allowed: ['boolean']
+            }
+        },
+        answer: {
+            type: ['plaintext'],
+            payload:{
+                'data': 'string'
+            },
+            info: {
+                'base64':'boolean',
+                'info': 'object'
+                // info.file_name: Optional string of the plaintext file name.
+                // info.is_mime: Boolean if the messages claims it is MIME.
+                // info.signatures: Array of signatures
+            }
+        }
+    },
+
+    config_opt: {
+        required: {
+            'component':{
+                allowed: ['string'],
+                // allowed_data: ['gpg'] // TODO check all available
+            },
+            'option': {
+                allowed: ['string'],
+                // allowed_data: ['default-key'] // TODO check all available
+            }
+        },
+        optional: {},
+        answer: {
+            type: [],
+            info: {
+                'option': 'object'
+            }
+        }
+    }
+};
diff --git a/lang/js/unittest_inputvalues.js b/lang/js/unittest_inputvalues.js
new file mode 100644 (file)
index 0000000..659ef85
--- /dev/null
@@ -0,0 +1,123 @@
+import { createKey } from './src/Key';
+
+export const helper_params = {
+    validLongId: '0A0A0A0A0A0A0A0A',
+    validKeys: ['A1E3BC45BDC8E87B74F4392D53B151A1368E50F3',
+        createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'),
+        'EE17AEE730F88F1DE7713C54BBE0A4FF7851650A'],
+    validFingerprint: '9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A',
+    validFingerprints: ['9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A9A9A7A7A8A',
+        '9AAE7A338A9A9A7A7A8A9A9A7A7A8A9A9A7A7DDA'],
+    invalidLongId: '9A9A7A7A8A9A9A7A7A8A',
+    invalidFingerprints: [{ hello:'World' }, ['kekekeke'], new Uint32Array(40)],
+    invalidKeyArray: { curiosity:'uncat' },
+    invalidKeyArray_OneBad: [
+        createKey('D41735B91236FDB882048C5A2301635EEFF0CB05'),
+        'E1D18E6E994FA9FE9360Bx0E687B940FEFEB095A',
+        '3AEA7FE4F5F416ED18CEC63DD519450D9C0FAEE5'],
+    invalidErrorCode: 'Please type in all your passwords.',
+    validGPGME_Key: createKey('D41735B91236FDB882048C5A2301635EEFF0CB05', true),
+    valid_openpgplike: { primaryKey: {
+        getFingerprint: function (){
+            return '85DE2A8BA5A5AB3A8A7BE2000B8AED24D7534BC2';}
+    }
+    }
+};
+
+export const message_params = {
+    invalid_op_action : 'dance',
+    invalid_op_type : [234, 34, '<>'],
+    valid_encrypt_data: 'مرحبا بالعالم',
+    invalid_param_test: {
+        valid_op: 'encrypt',
+        invalid_param_names: [22,'dance', {}],
+        validparam_name_0: 'mime',
+        invalid_values_0: [2134, 'All your passwords',
+            createKey('12AE9F3E41B33BF77DF52B6BE8EE1992D7909B08'), null]
+    }
+};
+
+export const whatever_params = {
+    four_invalid_params: ['<(((-<', '>°;==;~~', '^^', '{{{{o}}}}'],
+};
+export const key_params = {
+// A Key you own (= having a secret Key) in GPG. See testkey.pub/testkey.sec
+    validKeyFingerprint: 'D41735B91236FDB882048C5A2301635EEFF0CB05',
+    // A Key you do not own (= having no secret Key) in GPG. See testkey2.pub
+    validFingerprintNoSecret: 'E059A1E0866D31AE131170884D9A2E13304153D1',
+    // A Key not in your Keyring. This is just a random hex string.
+    invalidKeyFingerprint: 'CDC3A2B2860625CCBFC5AAAAAC6D1B604967FC4A',
+    validKeyProperties: ['expired', 'disabled','invalid','can_encrypt',
+        'can_sign','can_certify','can_authenticate','secret','is_qualified']
+};
+export const armoredKey = {
+    fingerprint: '78034948BA7F5D0E9BDB67E4F63790C11E60278A',
+    key:'-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
+        '\n' +
+        'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' +
+        'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' +
+        'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' +
+        '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' +
+        'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' +
+        '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' +
+        'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' +
+        'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' +
+        'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' +
+        'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' +
+        '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' +
+        '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' +
+        'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' +
+        'uzEXPGW+sq0WRp3hynn7kVP6QQYvuQENBFsPvK0BCACwvBcmbnGJk8XhEBRu2QN3\n' +
+        'jKgVs3CG5nE2Xh20JipZwAuGHugDLv6/jlizzz5jtj3SAHVtJB8lJW8I0cNSEIX8\n' +
+        'bRYH4C7lP2DTb9CgMcGErQIyK480+HIsbsZhJSNHdjUUl6IPEEVfSQzWaufmuswe\n' +
+        'e+giqHiTsaiW20ytXilwVGpjlHBaxn/bpskZ0YRasgnPqKgJD3d5kunNqWoyCpMc\n' +
+        'FYgDERvPbhhceFbvFE9G/u3gbcuV15mx53dDX0ImvPcvJnDOyJS9yr7ApdOV312p\n' +
+        'A1MLbxfPnbnVu+dGXn7D/VCDd5aBYVPm+5ANrk6z9lYKH9aO5wgXpLAdJvutCOL5\n' +
+        'ABEBAAGJATwEGAEIACYWIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUCWw+8rQIbDAUJ\n' +
+        'A8JnAAAKCRD2N5DBHmAnigMVB/484G2+3R0cAaj3V/z4gW3MRSMhcYqEMyJ/ACdo\n' +
+        '7y8eoreYW843JWWVDRY6/YcYYGuBBP47WO4JuP2wIlVn17XOCSgnNjmmjsIYiAzk\n' +
+        'op772TB27o0VeiFX5iWcawy0EI7JCb23xpI+QP31ksL2yyRYFXCtXSUfcOrLpCY8\n' +
+        'aEQMQbAGtkag1wHTo/Tf/Vip8q0ZEQ4xOKTR2/ll6+inP8kzGyzadElUnH1Q1OUX\n' +
+        'd2Lj/7BpBHE2++hAjBQRgnyaONF7mpUNEuw64iBNs0Ce6Ki4RV2+EBLnFubnFNRx\n' +
+        'fFJcYXcijhuf3YCdWzqYmPpU/CtF4TgDlfSsdxHxVOmnZkY3\n' +
+        '=qP6s\n' +
+        '-----END PGP PUBLIC KEY BLOCK-----\n',
+    keyChangedUserId: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
+        '\n' +
+        'mQENBFsPvK0BCACaIgoIN+3g05mrTITULK/YDTrfg4W7RdzIZBxch5CM0zdu/dby\n' +
+        'esFwaJbVQIqu54CRz5xKAiWmRrQCaRvhvjY0na5r5UUIpbeQiOVrl65JtNbRmlik\n' +
+        'd9Prn1kZDUOZiCPIKn+/M2ecJ92YedM7I4/BbpiaFB11cVrPFg4thepn0LB3+Whp\n' +
+        '9HDm4orH9rjy6IUr6yjWNIr+LYRY6/Ip2vWcMVjleEpTFznXrm83hrJ0n0INtyox\n' +
+        'Nass4eDWkgo6ItxDFFLOORSmpfrToxZymSosWqgux/qG6sxHvLqlqy6Xe3ZYRFbG\n' +
+        '+JcA1oGdwOg/c0ndr6BYYiXTh8+uUJfEoZvzABEBAAG0HEJsYSBCbGEgPGJsYWJs\n' +
+        'YUBleGFtcGxlLm9yZz6JAVQEEwEIAD4WIQR4A0lIun9dDpvbZ+T2N5DBHmAnigUC\n' +
+        'Ww+8rQIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD2N5DBHmAn\n' +
+        'igwIB/9K3E3Yev9taZP4KnXPhk1oMQRW1MWAsFGUr+70N85VwedpUawymW4vXi1+\n' +
+        'hMeTc39QjmZ0+VqHkJttkqEN6bLcEvgmU/mOlOgKdzy6eUcasYAzgoAKUqSX1SPs\n' +
+        '0Imo7Tj04wnfnVwvKxaeadi0VmdqIYaW75UlrzIaltsBctyeYH8sBrvaTLscb4ON\n' +
+        '46OM3Yw2G9+dBF0P+4UYFHP3EYZMlzNxfwF+i2HsYcNDHlcLfjENr9GwKn5FJqpY\n' +
+        'Iq3qmI37w1hVasHDxXdz1X06dpsa6Im4ACk6LXa7xIQlXxTgPAQV0sz2yB5eY+Md\n' +
+        'uzEXPGW+sq0WRp3hynn7kVP6QQYvtCZTb21lb25lIEVsc2UgPHNvbWVvbmVlbHNl\n' +
+        'QGV4YW1wbGUub3JnPokBVAQTAQgAPhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJb\n' +
+        'D705AhsDBQkDwmcABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEPY3kMEeYCeK\n' +
+        'aIUH/2o+Ra+GzxgZrVexXLL+FCSmcu0cxeWfMhL8jd96c6uXIT21qQMRU2jgvnUp\n' +
+        'Wdi/BeLKp5lYwywm04PFhmRVxWXLuLArCsDu+CFys+aPeybnjikPBZov6P8/cZV3\n' +
+        'cd6zxFvqB9J15HjDMcl/r5v6d4CgSLKlFebrO5WKxHa6zGK9TRMQrqTu1heKHRf6\n' +
+        '4+Wj+MZmYnPzEQePjiBw/VkJ1Nm37Dd24gKdcN/qJFwEOqvbI5RIjB7xqoDslZk9\n' +
+        'sAivBXwF0E9HKqvh4WZZeA7uaWNdGo/cQkD5rab5SdHGNPHLbzoRWScsM8WYtsME\n' +
+        'dEMp5iPuG9M63+TD7losAkJ/TlS5AQ0EWw+8rQEIALC8FyZucYmTxeEQFG7ZA3eM\n' +
+        'qBWzcIbmcTZeHbQmKlnAC4Ye6AMu/r+OWLPPPmO2PdIAdW0kHyUlbwjRw1IQhfxt\n' +
+        'FgfgLuU/YNNv0KAxwYStAjIrjzT4cixuxmElI0d2NRSXog8QRV9JDNZq5+a6zB57\n' +
+        '6CKoeJOxqJbbTK1eKXBUamOUcFrGf9umyRnRhFqyCc+oqAkPd3mS6c2pajIKkxwV\n' +
+        'iAMRG89uGFx4Vu8UT0b+7eBty5XXmbHnd0NfQia89y8mcM7IlL3KvsCl05XfXakD\n' +
+        'UwtvF8+dudW750ZefsP9UIN3loFhU+b7kA2uTrP2Vgof1o7nCBeksB0m+60I4vkA\n' +
+        'EQEAAYkBPAQYAQgAJhYhBHgDSUi6f10Om9tn5PY3kMEeYCeKBQJbD7ytAhsMBQkD\n' +
+        'wmcAAAoJEPY3kMEeYCeKAxUH/jzgbb7dHRwBqPdX/PiBbcxFIyFxioQzIn8AJ2jv\n' +
+        'Lx6it5hbzjclZZUNFjr9hxhga4EE/jtY7gm4/bAiVWfXtc4JKCc2OaaOwhiIDOSi\n' +
+        'nvvZMHbujRV6IVfmJZxrDLQQjskJvbfGkj5A/fWSwvbLJFgVcK1dJR9w6sukJjxo\n' +
+        'RAxBsAa2RqDXAdOj9N/9WKnyrRkRDjE4pNHb+WXr6Kc/yTMbLNp0SVScfVDU5Rd3\n' +
+        'YuP/sGkEcTb76ECMFBGCfJo40XualQ0S7DriIE2zQJ7oqLhFXb4QEucW5ucU1HF8\n' +
+        'UlxhdyKOG5/dgJ1bOpiY+lT8K0XhOAOV9Kx3EfFU6admRjc=\n' +
+        '=9WZ7\n' +
+        '-----END PGP PUBLIC KEY BLOCK-----\n'
+};
\ No newline at end of file
diff --git a/lang/js/unittests.js b/lang/js/unittests.js
new file mode 100644 (file)
index 0000000..f28be76
--- /dev/null
@@ -0,0 +1,386 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+import './node_modules/mocha/mocha'; /* global mocha, it, describe*/
+import './node_modules/chai/chai';/* global chai*/
+import { helper_params as hp } from './unittest_inputvalues';
+import { message_params as mp } from './unittest_inputvalues';
+import { whatever_params as wp } from './unittest_inputvalues';
+import { key_params as kp } from './unittest_inputvalues';
+import { Connection } from './src/Connection';
+import { gpgme_error, err_list } from './src/Errors';
+import { toKeyIdArray , isFingerprint } from './src/Helpers';
+import { createKey } from './src/Key';
+import { GPGME_Keyring } from './src/Keyring';
+import { GPGME_Message, createMessage } from './src/Message';
+
+mocha.setup('bdd');
+const expect = chai.expect;
+chai.config.includeStack = true;
+const connectionTimeout = 2000;
+
+function unittests (){
+    describe('Connection testing', function (){
+
+        it('Connecting', function (done) {
+            let conn0 = new Connection;
+            conn0.checkConnection(true, connectionTimeout).then(
+                function (answer) {
+                    expect(answer).to.not.be.empty;
+                    expect(answer.gpgme).to.not.be.undefined;
+                    expect(answer.gpgme).to.be.a('string');
+                    expect(answer.info).to.be.an('Array');
+                    expect(conn0.disconnect).to.be.a('function');
+                    expect(conn0.post).to.be.a('function');
+                    done();
+                });
+
+        });
+
+        it('Disconnecting', function (done) {
+            let conn0 = new Connection;
+            conn0.checkConnection(false, connectionTimeout).then(
+                function (answer) {
+                    expect(answer).to.be.true;
+                    conn0.disconnect();
+                    conn0.checkConnection(false, connectionTimeout).then(
+                        function (result) {
+                            expect(result).to.be.false;
+                            done();
+                        });
+                });
+        });
+    });
+
+    describe('Error Object handling', function (){
+        // TODO: new GPGME_Error codes
+        it('check the Timeout error', function (){
+            let test0 = gpgme_error('CONN_TIMEOUT');
+
+            expect(test0).to.be.an.instanceof(Error);
+            expect(test0.code).to.equal('CONN_TIMEOUT');
+        });
+
+        it('Error Object returns generic code if code is not listed',
+            function (){
+                let test0 = gpgme_error(hp.invalidErrorCode);
+
+                expect(test0).to.be.an.instanceof(Error);
+                expect(test0.code).to.equal('GENERIC_ERROR');
+            }
+        );
+
+        it('Warnings like PARAM_IGNORED should not return errors', function (){
+            let test0 = gpgme_error('PARAM_IGNORED');
+
+            expect(test0).to.be.null;
+        });
+    });
+
+    describe('Fingerprint checking', function (){
+
+        it('isFingerprint(): valid Fingerprint', function (){
+            let test0  = isFingerprint(hp.validFingerprint);
+
+            expect(test0).to.be.true;
+        });
+
+        it('isFingerprint(): invalid Fingerprints', function (){
+            for (let i=0; i < hp.invalidFingerprints.length; i++){
+                let test0 = isFingerprint(hp.invalidFingerprints[i]);
+
+                expect(test0).to.be.false;
+            }
+        });
+    });
+
+    describe('toKeyIdArray() (converting input to fingerprint)', function (){
+
+        it('Correct fingerprint string', function (){
+            let test0 = toKeyIdArray(hp.validFingerprint);
+
+            expect(test0).to.be.an('array');
+            expect(test0).to.include(hp.validFingerprint);
+        });
+
+        it('openpgpjs-like object', function (){
+            let test0 = toKeyIdArray(hp.valid_openpgplike);
+
+            expect(test0).to.be.an('array').with.lengthOf(1);
+            expect(test0).to.include(
+                hp.valid_openpgplike.primaryKey.getFingerprint());
+        });
+
+        it('Array of valid inputs', function (){
+            let test0 = toKeyIdArray(hp.validKeys);
+            expect(test0).to.be.an('array');
+            expect(test0).to.have.lengthOf(hp.validKeys.length);
+        });
+
+        it('Incorrect inputs', function (){
+
+            it('valid Long ID', function (){
+                let test0 = toKeyIdArray(hp.validLongId);
+
+                expect(test0).to.be.empty;
+            });
+
+            it('invalidFingerprint', function (){
+                let test0 = toKeyIdArray(hp.invalidFingerprint);
+
+                expect(test0).to.be.empty;
+            });
+
+            it('invalidKeyArray', function (){
+                let test0 = toKeyIdArray(hp.invalidKeyArray);
+
+                expect(test0).to.be.empty;
+            });
+
+            it('Partially invalid array', function (){
+                let test0 = toKeyIdArray(hp.invalidKeyArray_OneBad);
+
+                expect(test0).to.be.an('array');
+                expect(test0).to.have.lengthOf(
+                    hp.invalidKeyArray_OneBad.length - 1);
+            });
+        });
+    });
+
+    describe('GPGME_Key', function (){
+        it('Key has data after a first refresh', function (done) {
+            let key = createKey(kp.validKeyFingerprint);
+            key.refreshKey().then(function (key2){
+                expect(key2.get).to.be.a('function');
+                for (let i=0; i < kp.validKeyProperties.length; i++) {
+                    let prop = key2.get(kp.validKeyProperties[i]);
+                    expect(prop).to.not.be.undefined;
+                    expect(prop).to.be.a('boolean');
+                }
+                expect(isFingerprint(key2.get('fingerprint'))).to.be.true;
+                expect(
+                    key2.get('fingerprint')).to.equal(kp.validKeyFingerprint);
+                expect(
+                    key2.get('fingerprint')).to.equal(key.fingerprint);
+                done();
+            });
+        });
+
+        it('Non-cached key async data retrieval', function (done){
+            let key = createKey(kp.validKeyFingerprint, true);
+            key.get('can_authenticate').then(function (result){
+                expect(result).to.be.a('boolean');
+                done();
+            });
+        });
+
+        it('Non-cached key async armored Key', function (done){
+            let key = createKey(kp.validKeyFingerprint, true);
+            key.get('armored').then(function (result){
+                expect(result).to.be.a('string');
+                expect(result).to.include('KEY BLOCK-----');
+                done();
+            });
+        });
+
+        it('Non-cached key async hasSecret', function (done){
+            let key = createKey(kp.validKeyFingerprint, true);
+            key.get('hasSecret').then(function (result){
+                expect(result).to.be.a('boolean');
+                done();
+            });
+        });
+
+        it('Non-cached key async hasSecret (no secret in Key)', function (done){
+            let key = createKey(kp.validFingerprintNoSecret, true);
+            key.get('hasSecret').then(function (result){
+                expect(result).to.be.a('boolean');
+                expect(result).to.equal(false);
+                done();
+            });
+        });
+
+        it('Querying non-existing Key returns an error', function (done) {
+            let key = createKey(kp.invalidKeyFingerprint);
+            key.refreshKey().then(function (){},
+                function (error){
+                    expect(error).to.be.an.instanceof(Error);
+                    expect(error.code).to.equal('KEY_NOKEY');
+                    done();
+                });
+        });
+
+        it('createKey returns error if parameters are wrong', function (){
+            for (let i=0; i< 4; i++){
+                expect(function (){
+                    createKey(wp.four_invalid_params[i]);
+                }).to.throw(
+                    err_list.PARAM_WRONG.msg
+                );
+
+            }
+        });
+
+    //     it('Overwriting getFingerprint does not work', function(){
+    //         const evilFunction = function(){
+    //             return 'bad Data';
+    //         };
+    //         let key = createKey(kp.validKeyFingerprint, true);
+    //         expect(key.fingerprint).to.equal(kp.validKeyFingerprint);
+    //         try {
+    //             key.getFingerprint = evilFunction;
+    //         }
+    //         catch(e) {
+    //             expect(e).to.be.an.instanceof(TypeError);
+    //         }
+    //         expect(key.fingerprint).to.equal(kp.validKeyFingerprint);
+    //         expect(key.getFingerprint).to.not.equal(evilFunction);
+    //     });
+    });
+
+    describe('GPGME_Keyring', function (){
+
+        it('correct Keyring initialization', function (){
+            let keyring = new GPGME_Keyring;
+            expect(keyring).to.be.an.instanceof(GPGME_Keyring);
+            expect(keyring.getKeys).to.be.a('function');
+        });
+
+        it('Loading Keys from Keyring, to be used synchronously',
+            function (done){
+                let keyring = new GPGME_Keyring;
+                keyring.getKeys({ prepare_sync: true }).then(function (result){
+                    expect(result).to.be.an('array');
+                    expect(result[0].get('hasSecret')).to.be.a('boolean');
+                    done();
+                });
+            }
+        );
+
+        it('Loading specific Key from Keyring, to be used synchronously',
+            function (done){
+                let keyring = new GPGME_Keyring;
+                keyring.getKeys({
+                    pattern: kp.validKeyFingerprint,
+                    prepare_sync: true }).then(
+                    function (result){
+                        expect(result).to.be.an('array');
+                        expect(result[0].get('hasSecret')).to.be.a('boolean');
+                        done();
+                    }
+                );
+            }
+        );
+
+        it('Querying non-existing Key from Keyring', function (done){
+            let keyring = new GPGME_Keyring;
+            keyring.getKeys({
+                pattern: kp.invalidKeyFingerprint,
+                prepare_sync: true
+            }).then(function (result){
+                expect(result).to.be.an('array');
+                expect(result.length).to.equal(0);
+                done();
+            });
+        });
+
+    });
+
+    describe('GPGME_Message', function (){
+
+        it('creating encrypt Message', function (){
+            let test0 = createMessage('encrypt');
+
+            expect(test0).to.be.an.instanceof(GPGME_Message);
+            expect(test0.isComplete()).to.be.false;
+        });
+
+        it('Message is complete after setting mandatory data', function (){
+            let test0 = createMessage('encrypt');
+            test0.setParameter('data', mp.valid_encrypt_data);
+            test0.setParameter('keys', hp.validFingerprints);
+
+            expect(test0.isComplete()).to.be.true;
+        });
+
+        it('Message is not complete after mandatory data is empty', function (){
+            let test0 = createMessage('encrypt');
+            test0.setParameter('keys', hp.validFingerprints);
+            expect(test0.isComplete()).to.be.false;
+            expect(function (){
+                test0.setParameter('data', '');
+            }).to.throw(
+                err_list.PARAM_WRONG.msg);
+        });
+
+        it('Complete Message contains the data that was set', function (){
+            let test0 = createMessage('encrypt');
+            test0.setParameter('data', mp.valid_encrypt_data);
+            test0.setParameter('keys', hp.validFingerprints);
+
+            expect(test0.message).to.not.be.null;
+            expect(test0.message).to.have.keys('op', 'data', 'keys',
+                'chunksize');
+            expect(test0.message.op).to.equal('encrypt');
+            expect(test0.message.data).to.equal(
+                mp.valid_encrypt_data);
+        });
+
+        it ('Not accepting non-allowed operation', function (){
+            expect(function () {
+                createMessage(mp.invalid_op_action);
+            }).to.throw(
+                err_list.MSG_WRONG_OP.msg);
+        });
+        it('Not accepting wrong parameter type', function (){
+            expect(function () {
+                createMessage(mp.invalid_op_type);
+            }).to.throw(
+                err_list.PARAM_WRONG.msg);
+        });
+
+        it('Not accepting wrong parameter name', function (){
+            let test0 = createMessage(mp.invalid_param_test.valid_op);
+            for (let i=0;
+                i < mp.invalid_param_test.invalid_param_names.length; i++){
+                expect(function (){
+                    test0.setParameter(
+                        mp.invalid_param_test.invalid_param_names[i],
+                        'Somevalue');}
+                ).to.throw(err_list.PARAM_WRONG.msg);
+            }
+        });
+
+        it('Not accepting wrong parameter value', function (){
+            let test0 = createMessage(mp.invalid_param_test.valid_op);
+            for (let j=0;
+                j < mp.invalid_param_test.invalid_values_0.length; j++){
+                expect(function (){
+                    test0.setParameter(
+                        mp.invalid_param_test.validparam_name_0,
+                        mp.invalid_param_test.invalid_values_0[j]);
+                }).to.throw(err_list.PARAM_WRONG.msg);
+            }
+        });
+    });
+
+}
+
+export default { unittests };
\ No newline at end of file
diff --git a/lang/js/webpack.conf.js b/lang/js/webpack.conf.js
new file mode 100644 (file)
index 0000000..19f3bbd
--- /dev/null
@@ -0,0 +1,36 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This is the configuration file for building the gpgmejs-Library with webpack
+ */
+/* global require, module, __dirname */
+const path = require('path');
+
+module.exports = {
+    entry: './src/index.js',
+    // mode: 'development',
+    mode: 'production',
+    output: {
+        path: path.resolve(__dirname, 'build'),
+        filename: 'gpgmejs.bundle.js',
+        libraryTarget: 'var',
+        libraryExport: 'default',
+        library: 'Gpgmejs'
+    }
+};
diff --git a/lang/js/webpack.conf_unittests.js b/lang/js/webpack.conf_unittests.js
new file mode 100644 (file)
index 0000000..c3c87f3
--- /dev/null
@@ -0,0 +1,36 @@
+/* gpgme.js - Javascript integration for gpgme
+ * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This is the configuration file for building the gpgmejs-Library with webpack
+ */
+/* global require, module, __dirname */
+
+const path = require('path');
+
+module.exports = {
+    entry: './unittests.js',
+    mode: 'production',
+    output: {
+        path: path.resolve(__dirname, 'build'),
+        filename: 'gpgmejs_unittests.bundle.js',
+        libraryTarget: 'var',
+        libraryExport: 'default',
+        library: 'Gpgmejs_test'
+    }
+};
index c34e84a..f4def41 100644 (file)
@@ -1,4 +1,10 @@
-recursive-include examples *.py
+recursive-include doc *.org
+recursive-include doc *.rst
+recursive-include doc *.tex
+recursive-include doc *.texi
+recursive-include doc *.info
+recursive-include examples *.py *.pyx
+include README README.org
 include gpgme.i
 include helpers.c helpers.h private.h
 include version.py
index 8d74cbd..6988faf 100644 (file)
@@ -22,6 +22,7 @@ EXTRA_DIST = \
        gpgme.i \
        helpers.c helpers.h private.h \
        examples \
+       doc \
        src
 
 SUBDIRS = . tests
@@ -33,7 +34,7 @@ prepare: copystamp
 # distutils are not VPATH-aware.
 copystamp:
        ln -sf "$(top_srcdir)/src/data.h" .
-       ln -sf "$(top_builddir)/config.h" .
+       ln -sf "$(top_builddir)/conf/config.h" .
        ln -sf "$(srcdir)/src" gpg
        touch $@
 
@@ -54,7 +55,8 @@ python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp
        top_builddir="$(top_builddir)" \
          $(PYTHON) setup.py sdist --verbose --dist-dir=python$(PYTHON_VERSION)-gpg-dist \
                --manifest=python$(PYTHON_VERSION)-gpg-dist/MANIFEST
-       gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
+       gpgbin=gpgconf --list-components | grep OpenPGP | sed -e 's/gpg:OpenPGP://g'
+       $(gpgbin) --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
 
 .PHONY: sdist
 sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
index ae0334f..95d6e7e 100644 (file)
@@ -114,7 +114,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES = version.py setup.py
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
@@ -398,6 +398,7 @@ EXTRA_DIST = \
        gpgme.i \
        helpers.c helpers.h private.h \
        examples \
+       doc \
        src
 
 SUBDIRS = . tests
@@ -730,7 +731,7 @@ prepare: copystamp
 # distutils are not VPATH-aware.
 copystamp:
        ln -sf "$(top_srcdir)/src/data.h" .
-       ln -sf "$(top_builddir)/config.h" .
+       ln -sf "$(top_builddir)/conf/config.h" .
        ln -sf "$(srcdir)/src" gpg
        touch $@
 
@@ -751,7 +752,8 @@ python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp
        top_builddir="$(top_builddir)" \
          $(PYTHON) setup.py sdist --verbose --dist-dir=python$(PYTHON_VERSION)-gpg-dist \
                --manifest=python$(PYTHON_VERSION)-gpg-dist/MANIFEST
-       gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
+       gpgbin=gpgconf --list-components | grep OpenPGP | sed -e 's/gpg:OpenPGP://g'
+       $(gpgbin) --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
 
 .PHONY: sdist
 sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
index 99da4dd..a13345f 100644 (file)
@@ -13,7 +13,7 @@ Table of Contents
 
 
 The "gpg" module is a python interface to the GPGME library:
-[https://www.gnupg.org/software/gpgme/]
+<https://www.gnupg.org/software/gpgme/>
 
 "gpg" offers two interfaces, one is a high-level, curated, and idiomatic
 interface that is implemented as a shim on top of the low-level
@@ -27,16 +27,16 @@ functionality of the underlying library.
 ══════════════
 
   For general discussion and help see the gnupg-users mailing list:
-  [https://lists.gnupg.org/mailman/listinfo/gnupg-users]
+  <https://lists.gnupg.org/mailman/listinfo/gnupg-users>
 
   For development see the gnupg-devel mailing list:
-  [https://lists.gnupg.org/mailman/listinfo/gnupg-devel]
+  <https://lists.gnupg.org/mailman/listinfo/gnupg-devel>
 
 
 2 Bugs
 ══════
 
-  Please report bugs using our bug tracker [https://bugs.gnupg.org] with
+  Please report bugs using our bug tracker <https://bugs.gnupg.org> with
   tag (aka project) 'gpgme'.
 
 
@@ -44,8 +44,8 @@ functionality of the underlying library.
 ═════════
 
   PyME was created by John Goerzen, and maintained, developed, and
-  cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, and everyone
-  who contributed to it in any way.
+  cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, Justus
+  Winter, and everyone who contributed to it in any way.
 
   In 2016 we merged a port of PyME to into the GPGME repository, and
   development will continue there.  Please see the VCS history for the
@@ -64,14 +64,14 @@ functionality of the underlying library.
   • The bindings have been merged into the GPGME repository in 2016.
 
   • The latest version of PyME for Python 3.2 and above (as of May,
-    2015) is v0.9.1.  [https://git.gnupg.org/gpgme.git/lang/py3-pyme]
+    2015) is v0.9.1.  <https://git.gnupg.org/gpgme.git/lang/py3-pyme>
 
   • The latest version of PyME for Python 2.6 and 2.7 (as of this
-    writing) is v0.9.0.  [https://bitbucket.org/malb/pyme]
+    writing) is v0.9.0.  <https://bitbucket.org/malb/pyme>
 
   • A previous version of PyME v0.8.0 can be found on sourceforge:
-    [http://pyme.sourceforge.net/]
+    <http://pyme.sourceforge.net/>
 
   • A previous version of PyME v0.5.1 which works with GPGME v0.3.15 can
-    be found on John Goerzen's PyME page: [http://quux.org/devel/pyme/]
-    [http://www.complete.org/JohnGoerzen]
+    be found on John Goerzen's PyME page: <http://quux.org/devel/pyme/>
+    <http://www.complete.org/JohnGoerzen>
diff --git a/lang/python/doc/README b/lang/python/doc/README
new file mode 100644 (file)
index 0000000..a14e1ad
--- /dev/null
@@ -0,0 +1,47 @@
+GPGME Python Bindings Documentation
+===================================
+
+As the GPGME Python bindings exist in two worlds within the FOSS
+universe, it's always had a little issue with regards to its
+documentation and specifically to the format of it.  The GnuPG
+Project, like much of the rest of the GNU Project, uses Texinfo to
+build its documentation.  While the actual format used to write and
+edit that documentation is Org mode.  Largely because most, if not
+all, of the GnuPG developers use GNU Emacs for much of their work.
+
+The Python world, however, utilises reStructuredText almost
+universally.  This in turn is used by Sphinx or Docutils directly to
+build the documentation.
+
+Each has various advantages for their own ecisystems, but this part of
+the GnuPG effort is aimed at both sides.  So, long story short, this
+documentation is provided as both Texinfo and reStructuredText files.
+
+This docs directory contains four main subdirectories:
+
+ 1. meta
+ 2. src
+ 3. rst
+ 4. texinfo
+
+The Meta directory is for docs that are not intended for distribution
+or are about the docs themselves.  The sole exception being this RDME
+file.
+
+The Src directory is where the original edited files are, from which
+the following two formats are generated initially.  Most, if not all,
+of these are written in Org Mode.
+
+The ReST directory contains reStructuredText files ehich have been
+converted to that format from the Org Mode files via Pandoc.
+
+The Texinfo directory contains Texinfo files which have been exported
+to that format from the Org Mode files by Org Mode itself within GNU
+Emacs.
+
+Those latter two directories should then be used by their respective
+build systems to produce the various output file formats they normally
+do.  They should not spill out into this parent directory.
+Particularly since it is quite possible, perhaps even likely, that
+alternatives to both of them may be added to this parent documentation
+directory at some future point.
diff --git a/lang/python/doc/meta/TODO.org b/lang/python/doc/meta/TODO.org
new file mode 100644 (file)
index 0000000..0be99b3
--- /dev/null
@@ -0,0 +1,251 @@
+#+TITLE: Stuff To Do
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Latin Modern Roman}
+
+* Project Task List
+  :PROPERTIES:
+  :CUSTOM_ID: task-list
+  :END:
+
+** DONE Documentation default format
+   CLOSED: [2018-02-15 Thu 21:29]
+   :PROPERTIES:
+   :CUSTOM_ID: todo-docs-default
+   :END:
+
+   Decide on a default file format for documentation.  The two main
+   contenders being Org Mode, the default for the GnuPG Project and
+   reStructuredText, the default for Python projects.  A third option
+   of DITA XML was considered due to a number of beneficial features
+   it provides.
+
+   The decision was made to use Org Mode in order to fully integrate
+   with the rest of the GPGME and GnuPG documentation.  It is possible
+   to produce reST versions via Pandoc and DITA XML can be reached
+   through converting to either Markdown or XHTML first.
+
+
+** TODO Documentation build systems
+   :PROPERTIES:
+   :CUSTOM_ID: todo-docs-build-systems
+   :END:
+
+Though Org Mode is being used for the default documentation format, it
+still needs to end up as usable by end users.  So the Org Mode files
+are used to produce the "source" files used by the two main contenders
+for documenting the bindings: Texinfo and ReStructuredText/Docutils.
+
+
+*** TODO Texinfo documentation
+    :PROPERTIES:
+    :CUSTOM_ID: todo-docs-build-texinfo
+    :END:
+
+Need to add all of Texinfo's ... special systems to make it do its
+things.
+
+
+*** TODO ReStructuredText documentation
+    :PROPERTIES:
+    :CUSTOM_ID: todo-docs-build-docutils
+    :END:
+
+Need to run Sphinx's quick start, add it to the requirements and tweak
+the index page for the rst files to point to the HOWTO and other files.
+
+It might just be easier to do all that in Org Mode and convert the
+lot, then the Sphinx bits can be automated.
+
+
+** STARTED Documentation HOWTO
+   :PROPERTIES:
+   :CUSTOM_ID: todo-docs-howto
+   :END:
+
+   - State "STARTED"    from "TODO"       [2018-03-08 Thu 13:59] \\
+     Started yesterday.
+   Write a HOWTO style guide for the current Python bindings.
+
+*** DONE Start python bindings HOWTO
+    CLOSED: [2018-03-07 Wed 18:14]
+    :PROPERTIES:
+    :CUSTOM_ID: howto-start
+    :END:
+
+
+*** STARTED Include certain specific instructions in the HOWTO
+    :PROPERTIES:
+    :CUSTOM_ID: howto-requests
+    :END:
+
+    Note: moved the S/MIME bits out to their own section of the TODO
+    list and may be served better by separate HOWTO documentation
+    anyway.
+
+    - State "STARTED"    from "TODO"       [2018-03-09 Fri 15:27]
+    Some functions can be worked out from the handful of examples
+    available, but many more can't and I've already begun receiving
+    requests for certain functions to be explained.
+
+
+**** DONE Standard scenarios
+     CLOSED: [2018-03-19 Mon 12:34]
+     :PROPERTIES:
+     :CUSTOM_ID: howto-the-basics
+     :END:
+
+     - State "DONE"       from "STARTED"    [2018-03-19 Mon 12:34] \\
+       All four of those are done.
+     - State "STARTED"    from "TODO"       [2018-03-09 Fri 15:26] \\
+       Began with the example code, now to add the text.
+     What everyone expects: encryption, decryption, signing and verifying.
+
+
+**** STARTED Key control
+     :PROPERTIES:
+     :CUSTOM_ID: howto-key-control
+     :END:
+
+     - State "STARTED"    from "TODO"       [2018-03-19 Mon 12:35] \\
+       Generating keys and subkeys are done, but revocation is still to be done.
+     Generating keys, adding subkeys, revoking subkeys (and keeping
+     the cert key), adding and revoking UIDs, signing/certifying keys.
+
+
+**** DONE More key control
+     CLOSED: [2018-03-19 Mon 12:36]
+     :PROPERTIES:
+     :CUSTOM_ID: howto-key-selection
+     :END:
+
+     - State "DONE"       from "TODO"       [2018-03-19 Mon 12:36] \\
+       Key selection, searching, matching and counting is done.
+     Selecting keys to encrypt to or manipulate in other ways (e.g. as
+     with key control or the basics).
+
+
+** TODO Documentation SWIG
+   :PROPERTIES:
+   :CUSTOM_ID: todo-docs-swig
+   :END:
+
+   Write documentation for the complete SWIG bindings demonstrating
+   the correspondence with GPGME itself.
+
+   Note: it is likely that this will be more in the nature of
+   something to be used in conjunction with the existing GPGME
+   documentation which makes it easier for Python developers to use.
+
+
+** TODO GUI examples
+   :PROPERTIES:
+   :CUSTOM_ID: todo-gui-examples
+   :END:
+
+   Create some examples of using Python bindings in a GUI application
+   to either match or be similar to the old GTK2 examples available
+   with PyME.
+
+
+** TODO Replace SWIG
+   :PROPERTIES:
+   :CUSTOM_ID: todo-replace-swig
+   :END:
+
+   Selecting SWIG for this project in 2002 was understandable and
+   effectively the only viable option.  The options available now,
+   however, are significantly improved and some of those would resolve
+   a number of existing problems with using SWIG, particularly when
+   running code on both POSIX compliant and Windows platforms.
+
+   The long term goal is to replace SWIG by reimplementing the Python
+   bindings using a more suitable means of interfacing with the GPGME
+   C source code.
+
+
+*** TODO Replacement for SWIG
+    :PROPERTIES:
+    :CUSTOM_ID: todo-replace-swig-replacement
+    :END:
+
+    Decide on a replacement for SWIG.  Currently CFFI is looking like
+    the most viable candidate, but some additional testing and checks
+    are yet to be completed.
+
+
+** TODO API for an API
+   :PROPERTIES:
+   :CUSTOM_ID: todo-api-squared
+   :END:
+
+   A C API like GPGME is not what most modern developers think of when
+   they hear the term API. Normally they think of something they can
+   interact with like a RESTful web API.  Though RESTful is unlikely
+   given the nature of GPGME and the process of encryption, it may be
+   possible to provide a more familiar interface which can be utilised
+   by developers of other languages for which bindings are not
+   available or for which it is too difficult to create proper
+   bindings.
+
+
+** TODO S/MIME
+   :PROPERTIES:
+   :CUSTOM_ID: s-mime
+   :END:
+
+   Eventually add some of this, but the OpenPGP details are far more
+   important at the moment.
+
+
+* Project Task Details
+  :PROPERTIES:
+  :CUSTOM_ID: detailed-tasks
+  :END:
+
+** Working examples
+   :PROPERTIES:
+   :CUSTOM_ID: working-examples
+   :END:
+
+   The old GUI examples were unable to be retained since they depended
+   on GTK2 and Python 2's integration with GTK2.
+
+   Current GPGME examples so far only include command line tools or
+   basic Python code for use with either Python 2.7 or Python 3.4 and
+   above.
+
+   Future GUI examples ought to utilise available GUI modules and
+   libraries supported by Python 3.  This may include Qt frameworks,
+   Tkinter, GTK3 or something else entirely.
+
+** Documentation
+   :PROPERTIES:
+   :CUSTOM_ID: documentation
+   :END:
+
+   The legacy documentation which no longer applies to the Python
+   bindings has been removed.
+
+   Current and future documentation will adhere to the GnuPG standard
+   of using Org Mode and not use the reStructuredText (reST) format
+   more commonly associated with Python documentation.  The reasons
+   for this are that this project is best served as shipping with the
+   rest of GPGME and the documentation ought to match that.  There are
+   also aspects of Org Mode's publishing features which are superior
+   to the defaults of reST, including the capacity to generate fully
+   validating strict XHTML output.
+
+   If reST files are required at a later point for future inclusion
+   with other Python packages, then that format can be generated from
+   the .org files with Pandoc before being leveraged by either
+   Docutils, Sphinx or something else.
+
+   While there are some advanced typesetting features of reST which
+   are not directly available to Org Mode, more often than not those
+   features are best implemented with either HTML and CSS, with LaTeX
+   to produce a PDF or via a number of XML solutions.  Both reST and
+   Org Mode have multiple paths by which to achieve all of these.
diff --git a/lang/python/doc/meta/old-commits.log b/lang/python/doc/meta/old-commits.log
new file mode 100644 (file)
index 0000000..93661e3
--- /dev/null
@@ -0,0 +1,2445 @@
+commit 2145348ec54c6027f2ea20f695de0277e2871405
+Merge: 348ba88 2036f1a
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 03:04:19 2015 +1000
+
+    Merge pull request #4 from Hasimir/master
+
+    history
+
+commit 2036f1a0a670a0561993e195c458059220b36114
+Merge: dbabf0c 348ba88
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 02:57:44 2015 +1000
+
+    Merge branch 'master' of github:adversary-org/pyme3
+
+commit dbabf0cf1f2985755c2293b619011832e34faa9c
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 02:52:23 2015 +1000
+
+    Added a short history
+
+    * A (very) brief summary of the project's history since 2002.
+    * Deals with why the commit log in the GPGME repo does not include the
+      history of PyME.
+    * Mentions that intact git repos will be maintained, but not where they
+      are (one will be on github, another will be in a user directory on
+      playfair.gnupg.org).
+
+ docs/Short_History.rst | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 57 insertions(+)
+
+commit 348ba883424778c711c04ae9b66035ccdb36eb8c
+Merge: 127d0a5 7c37a27
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 02:21:34 2015 +1000
+
+    Merge pull request #3 from Hasimir/master
+
+    Version release preparation
+
+commit 7c37a27a6845c58222d4d947c2efbe38e955b612
+Merge: f692cff 127d0a5
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 02:17:14 2015 +1000
+
+    Merge branch 'master' of github:adversary-org/pyme3
+
+commit f692cff50a89c2c61acdbd3d7dd60f5ce3cd15af
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 02:09:44 2015 +1000
+
+    TODO update
+
+    * Removed reference to GitHub, replaced with impending new home at gnupg.org.
+
+ docs/TODO.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit bd5ccf9e3bfe69fa681613757577e87b72ca08ec
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 02:00:44 2015 +1000
+
+    Version bump
+
+    * Bumped version number to 0.9.1 to keep it somewhat in line with the
+      existing PyME project, even though there will be some divergence at
+      some point (or even re-merging, depending on how many of the Python 3
+      modifications can be back-ported to the Python 2 version).
+    * Updated the author and copyright information to reflect the two
+      current authors (Martin and I).
+    * Replaced Igor's contact details with mine.
+    * Replaced project home page with the GnuPG one.
+
+ pyme/version.py | 16 +++++++++-------
+ 1 file changed, 9 insertions(+), 7 deletions(-)
+
+commit ec167512f4ca88d8f6e89e2ae831798c8283b4df
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 01:48:01 2015 +1000
+
+    README preparation.
+
+    * Changes in preparation for impending move of code to the GnuPG git
+      server as a part of GPGME.
+
+ README.rst | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+commit 8a48515e884c36b5bdb24a13cb4d2e49f4ee6f17
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Wed May 6 01:43:53 2015 +1000
+
+    TODO moved to docs
+
+    * As it says.
+
+ TODO.rst      | 25 -------------------------
+ docs/TODO.rst | 25 +++++++++++++++++++++++++
+ 2 files changed, 25 insertions(+), 25 deletions(-)
+
+commit f968c777472f01f308f6e57eac1740bf5c76c205
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 16:52:13 2015 +1000
+
+    Started another TODO file.
+
+ TODO.rst | 25 +++++++++++++++++++++++++
+ 1 file changed, 25 insertions(+)
+
+commit 127d0a56fa9f7ad1d4fb39d0b529b890a8d67365
+Merge: db72dea 44837f6
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 14:59:44 2015 +1000
+
+    Merge pull request #2 from Hasimir/master
+
+    Minor editing.
+
+commit 44837f6e50fc539c86aef1f75a6a3538b02029ea
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 14:56:55 2015 +1000
+
+    Minor editing.
+
+    * Fixed another URL.
+    * Changed Py3 version's version number to v0.9.1-beta0.
+
+ README.rst | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit db72deaae19c3513391df040bcaf66a88d9213af
+Merge: db34286 48eb185
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 14:26:11 2015 +1000
+
+    Merge pull request #1 from Hasimir/master
+
+    Links
+
+commit 48eb1856cb0739cc9f0b9084da9d965e1fc7fddd
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 14:22:30 2015 +1000
+
+    Links
+
+    * Fixed URLs for authors.
+    * Updated my entry to point to github location.
+    ** I strongly suspect the result of this work will be concurrent
+       projects, so preparing for that eventuality with this repo.
+
+ README.rst | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+commit db3428659783f30b9a76204403daedf9fc4cf7cf
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 11:29:00 2015 +1000
+
+    Explicit over Implicit ...
+
+    ... isn't just for code.
+
+    * Removed the 2to3 working directory and its contents.
+    * Made the README.rst file a little more clear that this branch is for
+      Python 3 (set Python 3.2 as a fairly arbitrary requirement for the
+      moment, but will probably raise this to 3.3).
+
+ 2to3/2to3-output-remaining.log |  60 ---
+ 2to3/2to3-output-setup.log     |  35 --
+ 2to3/2to3-output.log           | 950 -----------------------------------------
+ README.rst                     |  10 +-
+ 4 files changed, 7 insertions(+), 1048 deletions(-)
+
+commit 3edf07a4ba8a86af3a33246234d6e133074862af
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 11:19:41 2015 +1000
+
+    Added authors.
+
+    * In alphabetical order.
+    * Mine will need updating once Martin and I have decided what to do
+      regarding the two main branches.
+
+ README.rst | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+commit 811eb14b53e8856312d99f46b77215f7f9bd672c
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 10:23:00 2015 +1000
+
+    Docs and other things.
+
+    * Now able to import pyme.core without error, indicates port process is
+      successful.
+    * Code is *not* compatible with the Python 2 version.
+    * Will need to consider making this a parallel project with the master
+      branch.
+    * Got rid of the .org TODO file.
+    * Changed the README to use the reST file extension since it's full of
+      reST anyway.
+
+ 2to3/TODO.org |  5 -----
+ README.rst    | 32 ++++++++++++++++++++++++++++++++
+ README.txt    | 32 --------------------------------
+ 3 files changed, 32 insertions(+), 37 deletions(-)
+
+commit 79e784bdcce1de6f7856921b5431044c62c6f015
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 10:18:40 2015 +1000
+
+    Fixed another implicit import by making it explicit.  Hopefully this is the last one.
+
+ pyme/util.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 2b52b46ccda3e7abcc50eed0745062259d698661
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 10:16:01 2015 +1000
+
+    Fixed another implicit import by making it explicit.
+
+ pyme/errors.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 409c8fd565e21f23cd41daaeffc867e6d23a0863
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 10:08:22 2015 +1000
+
+    Bytes vs. Unicode
+
+    * Trying PyBytes instead of PyUnicode.
+
+ gpgme.i   | 14 +++++++-------
+ helpers.c |  8 ++++----
+ 2 files changed, 11 insertions(+), 11 deletions(-)
+
+commit d8164aa2ae98bf8c807c16e2d9be12c5fbea7cfd
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 09:22:58 2015 +1000
+
+    String to Unicode
+
+    * Replaced all instances of PyString with PyUnicode (and hoping there's
+      no byte data in there).
+
+ gpgme.i   | 14 +++++++-------
+ helpers.c |  8 ++++----
+ 2 files changed, 11 insertions(+), 11 deletions(-)
+
+commit bd99b7865656e559b17c419c6b64b412a22c6c44
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 09:17:06 2015 +1000
+
+    PyInt_AsLong
+
+    * Replaced all instances of PyInt with PyLong, as per C API docs.
+
+ gpgme.i   | 4 ++--
+ helpers.c | 8 ++++----
+ 2 files changed, 6 insertions(+), 6 deletions(-)
+
+commit 3c91e2ccf8ca788b51e3308e292c6b64888fdb15
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 05:59:36 2015 +1000
+
+    Import correction
+
+    * Once pygpgme.py is generated and moved, it will be in the right
+      directory for the explicit "from . import pygpgme" to be correct.
+
+ pyme/core.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 23a49e7070812ff1ce138d8d4cc46d0b80328897
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 05:38:29 2015 +1000
+
+    The -py3 flag.
+
+ Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit b1549587d6db5e33081b9c20f75d1348a1d25938
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 05:01:42 2015 +1000
+
+    Fixed indentation - 4.
+
+ pyme/core.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit a685142ce46761ee6f5176e90717176e38e0d24f
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 05:00:16 2015 +1000
+
+    Fixed indentation - 3.
+
+ pyme/core.py | 5 ++---
+ 1 file changed, 2 insertions(+), 3 deletions(-)
+
+commit 488a70b490cc64eb1c47d2483cb2f4079c6767f7
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:53:21 2015 +1000
+
+    Pet Peeve
+
+    def pet_peeve(self):
+        peeve = print("people who don't press return after a colon!")
+
+    FFS!
+
+ pyme/core.py | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+commit a5d38eb47d64bb17bb609fe594dae2aca480bac9
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:47:54 2015 +1000
+
+    Fixed indentation - 2.
+
+ pyme/core.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 476a207f732b8559abb1ea3c23147c0e34804730
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:46:01 2015 +1000
+
+    Fixed indentation.
+
+ pyme/core.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit 0572900eba9bcd9b0283c7d8e022e8972f06f9f8
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:43:49 2015 +1000
+
+    Replaced all tabs with 4 spaces.
+
+ pyme/core.py | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+commit 78c0b7677e94ce1e11b8cdb833a9064527187330
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:39:07 2015 +1000
+
+    SWIG flags in the wrong place.
+
+ Makefile | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit dfa7f2589963494a8f89277560d8c1116604a3c8
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:35:09 2015 +1000
+
+    Fixed subprocess call for swig (again).
+
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 249bfd8c714dcda53127b99b6cc8a6c7c4a99f20
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 04:32:40 2015 +1000
+
+    Fixed subprocess call for swig.
+
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 6fd7e719cf4c975f466ceb39835db7007df36fb2
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sun May 3 03:51:48 2015 +1000
+
+    Linking swig to py3
+
+    * Changed the swig invocations to run with the -python -py3 flags explicitly.
+
+ Makefile | 4 ++--
+ setup.py | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+commit 7a6b584f50ed6ddc8617a642185eea1f24ff791a
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 11:12:00 2015 +1000
+
+    String fun
+
+    * streamlined confdata details, including decoding strom binary to string.
+
+ setup.py | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+commit f7fd3f270592021a95a8f779bfe85ac18f4e390b
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 10:46:59 2015 +1000
+
+    Open File
+
+    * Removed deprecated file() and replaced with open().
+
+ examples/PyGtkGpgKeys.py | 2 +-
+ examples/pygpa.py        | 6 +++---
+ gpgme-h-clean.py         | 2 +-
+ 3 files changed, 5 insertions(+), 5 deletions(-)
+
+commit 4227d486f9558015e7e548d71085e58e1b50ec08
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 10:36:15 2015 +1000
+
+    print() fix
+
+    * Makefile includes a python print, changed from statement to function.
+
+ Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 406f7f2567b701502186fe0a325dc2a3491ff7f8
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 10:28:42 2015 +1000
+
+    Updated Makefile
+
+    * set make to use python3 instead.
+    * This will mean a successful port may need to be maintained seperately
+      from the original python2 code instead of merged, but ought to be able
+      to share most things.  So maybe merge with separated make files or a
+      pre-make script to set python2 or python3 prior to building ... decide
+      later, after it works.
+
+ Makefile | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+commit 90b3efa5b193d37e08dc9b4ee766ba9ebc9412af
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 10:15:20 2015 +1000
+
+    Env and a little license issue
+
+    * Updated all the /usr/bin/env paths to point to python3.
+    * Also fixed the hard coded /usr/bin/python paths.
+    * Updated part of setup.py which gave the impression this package was
+      only licensed under the GPL (it's actually licensed under the LGPL as
+      well, essentially the same dual licensing as the GPGME library).
+
+ examples/PyGtkGpgKeys.py   | 2 +-
+ examples/delkey.py         | 2 +-
+ examples/encrypt-to-all.py | 2 +-
+ examples/exportimport.py   | 2 +-
+ examples/genkey.py         | 2 +-
+ examples/inter-edit.py     | 2 +-
+ examples/pygpa.py          | 2 +-
+ examples/sign.py           | 2 +-
+ examples/signverify.py     | 2 +-
+ examples/simple.py         | 2 +-
+ examples/t-edit.py         | 2 +-
+ examples/testCMSgetkey.py  | 2 +-
+ examples/verifydetails.py  | 2 +-
+ gpgme-h-clean.py           | 2 +-
+ setup.py                   | 4 ++--
+ 15 files changed, 16 insertions(+), 16 deletions(-)
+
+commit 1a4b55dbccd2774344352e579130bf494bc5fa4b
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 08:50:54 2015 +1000
+
+    Removed extraneous files.
+
+    * The two .bak files.
+
+ pyme/errors.py.bak |  46 ---------------------
+ setup.py.bak       | 116 -----------------------------------------------------
+ 2 files changed, 162 deletions(-)
+
+commit 208879d4f2a6d0514c3f8ee2fc0da8bba42350de
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 08:19:37 2015 +1000
+
+    Added TODO.org
+
+    * TODO list in Emacs org-mode.
+    * Will eventually be removed along with this entire directory when the
+      porting process is complete.
+
+ 2to3/TODO.org | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+commit 1548bf201059638675c5387c6f124d4b703363a9
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 07:58:40 2015 +1000
+
+    2to3 conversion of remaining files
+
+    * Ran the extended version against all the unmodified python files.
+    * Only pyme/errors.py required additional work.
+
+ 2to3/2to3-output-remaining.log | 60 ++++++++++++++++++++++++++++++++++++++++++
+ pyme/errors.py                 |  2 +-
+ pyme/errors.py.bak             | 46 ++++++++++++++++++++++++++++++++
+ 3 files changed, 107 insertions(+), 1 deletion(-)
+
+commit 1230650bc6bbe4c14d1284f7877aa932f3e86eb4
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Sat May 2 07:50:39 2015 +1000
+
+    2to3 conversion of setup.py
+
+    * Ran extended 2to3 command to produce python 3 code for setup.py.
+    * Effectively testing for what to run against the other originally
+      unmodified py2 files.
+
+ 2to3/2to3-output-setup.log |  35 ++++++++++++++
+ setup.py                   |   7 ++-
+ setup.py.bak               | 116 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 154 insertions(+), 4 deletions(-)
+
+commit edad44955f59aa879e95a369591717fb19eec6b7
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Fri May 1 21:50:07 2015 +1000
+
+    Removing 2to3 generated .bak files.
+
+    * Not really needed with a real VCS, but couldn't hurt to have them for
+      a couple of revisions.  ;)
+
+ examples/PyGtkGpgKeys.py.bak           |  663 ---------------
+ examples/encrypt-to-all.py.bak         |   65 --
+ examples/exportimport.py.bak           |   75 --
+ examples/genkey.py.bak                 |   45 -
+ examples/inter-edit.py.bak             |   57 --
+ examples/pygpa.py.bak                  | 1457 --------------------------------
+ examples/sign.py.bak                   |   31 -
+ examples/signverify.py.bak             |   78 --
+ examples/simple.py.bak                 |   52 --
+ examples/t-edit.py.bak                 |   59 --
+ examples/testCMSgetkey.py.bak          |   45 -
+ examples/verifydetails.py.bak          |  100 ---
+ gpgme-h-clean.py.bak                   |   42 -
+ pyme/callbacks.py.bak                  |   47 --
+ pyme/constants/data/__init__.py.bak    |    4 -
+ pyme/constants/keylist/__init__.py.bak |    4 -
+ pyme/constants/sig/__init__.py.bak     |    4 -
+ pyme/core.py.bak                       |  463 ----------
+ pyme/util.py.bak                       |   72 --
+ pyme/version.py.bak                    |   41 -
+ 20 files changed, 3404 deletions(-)
+
+commit 1cfc3c969f885ed191610bffbbd60ac23fdd349e
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Fri May 1 21:45:50 2015 +1000
+
+    2to3 conversion log
+
+    * The output of the command to convert the code from Python 2 to 3.
+    * Note: this contains the list of files which were not modified and
+      which will or may need to be modified.
+
+ 2to3/2to3-output.log | 950 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 950 insertions(+)
+
+commit 078f6cf878aa62d12704fab424198a613a24cc8c
+Author: Ben McGinnes <ben@adversary.org>
+Date:   Fri May 1 21:36:58 2015 +1000
+
+    2to3 conversion of pyme master
+
+    * Branch from commit 459f3eca659b4949e394c4a032d9ce2053e6c721
+    * Ran this: or x in `find . | egrep .py$` ; do 2to3 -w $x; done ;
+    * Multiple files not modified, will record elsewhere (see next commit).
+
+ examples/PyGtkGpgKeys.py               |   10 +-
+ examples/PyGtkGpgKeys.py.bak           |  663 +++++++++++++++
+ examples/encrypt-to-all.py             |   12 +-
+ examples/encrypt-to-all.py.bak         |   65 ++
+ examples/exportimport.py               |   20 +-
+ examples/exportimport.py.bak           |   75 ++
+ examples/genkey.py                     |    2 +-
+ examples/genkey.py.bak                 |   45 +
+ examples/inter-edit.py                 |    8 +-
+ examples/inter-edit.py.bak             |   57 ++
+ examples/pygpa.py                      |   40 +-
+ examples/pygpa.py.bak                  | 1457 ++++++++++++++++++++++++++++++++
+ examples/sign.py                       |    2 +-
+ examples/sign.py.bak                   |   31 +
+ examples/signverify.py                 |   18 +-
+ examples/signverify.py.bak             |   78 ++
+ examples/simple.py                     |    8 +-
+ examples/simple.py.bak                 |   52 ++
+ examples/t-edit.py                     |   12 +-
+ examples/t-edit.py.bak                 |   59 ++
+ examples/testCMSgetkey.py              |    8 +-
+ examples/testCMSgetkey.py.bak          |   45 +
+ examples/verifydetails.py              |   34 +-
+ examples/verifydetails.py.bak          |  100 +++
+ gpgme-h-clean.py                       |    2 +-
+ gpgme-h-clean.py.bak                   |   42 +
+ pyme/callbacks.py                      |    6 +-
+ pyme/callbacks.py.bak                  |   47 ++
+ pyme/constants/data/__init__.py        |    2 +-
+ pyme/constants/data/__init__.py.bak    |    4 +
+ pyme/constants/keylist/__init__.py     |    2 +-
+ pyme/constants/keylist/__init__.py.bak |    4 +
+ pyme/constants/sig/__init__.py         |    2 +-
+ pyme/constants/sig/__init__.py.bak     |    4 +
+ pyme/core.py                           |   26 +-
+ pyme/core.py.bak                       |  463 ++++++++++
+ pyme/util.py                           |    6 +-
+ pyme/util.py.bak                       |   72 ++
+ pyme/version.py                        |    2 +-
+ pyme/version.py.bak                    |   41 +
+ 40 files changed, 3515 insertions(+), 111 deletions(-)
+
+commit 459f3eca659b4949e394c4a032d9ce2053e6c721
+Merge: c5966ab dae7f14
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Wed Jul 9 10:48:33 2014 +0100
+
+    Merged in jerrykan/pyme/fix_setup_26 (pull request #1)
+
+    Provide support for using setup.py with Python v2.6
+
+commit dae7f14a54e6c2bde0ad4da7308cc7fc0d0c0469
+Author: John Kristensen <john.kristensen@dpipwe.tas.gov.au>
+Date:   Wed Jul 9 15:54:39 2014 +1000
+
+    Provide support for using setup.py with Python v2.6
+
+    The setup.py script uses subprocess.check_output() which was introduced
+    in Python v2.7. The equivalent functionality can be achieved without
+    adding much extra code and provide support for Python v2.6.
+
+ setup.py | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+commit c5966abec9d772b3922d32650da288fd50a217be
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Thu May 15 19:43:00 2014 +0100
+
+    README.txt in ReST, including headlines
+
+ README.txt | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+commit 43ee8c6f34fa9b6d3975aa6ea60b3d4a741fa721
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Thu May 15 19:37:15 2014 +0100
+
+    README.txt in ReST
+
+ README.txt | 25 +++++++++++++------------
+ 1 file changed, 13 insertions(+), 12 deletions(-)
+
+commit f71a369484cba8801df23ccc5842335fa496c0df
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Thu May 15 19:28:12 2014 +0100
+
+    added MANIFEST.in and README.txt (instead of .md)
+
+ MANIFEST.in |  6 ++++++
+ README.md   | 27 ---------------------------
+ README.txt  | 27 +++++++++++++++++++++++++++
+ 3 files changed, 33 insertions(+), 27 deletions(-)
+
+commit d0d6755229f920b0bed043e9c2731de2d57c096c
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Tue May 13 09:52:44 2014 +0100
+
+    added mailing list to README
+
+ README.md | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+commit 30ca60ddf92df684de261cb24c83c68089be0adc
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sun May 11 13:34:28 2014 +0100
+
+    we don't need a separate out of date ChangeLog file
+
+ ChangeLog | 802 --------------------------------------------------------------
+ 1 file changed, 802 deletions(-)
+
+commit 8263f1a6d38fdb7f5f3dd5c7e28f83caa7528a08
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sun May 11 13:32:31 2014 +0100
+
+    adding README.md
+
+ README.md | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+commit 3fc71b47e9e14b0b984801c28d722723baa4b406
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 10 15:43:06 2014 +0100
+
+    ValueError -> RuntimeError
+
+ setup.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit eec432abea56296b9fa36aac0d10926a2335b739
+Merge: eea6537 d2738b3
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 10 15:41:02 2014 +0100
+
+    Merge branch 'master' of bitbucket.org:malb/pyme
+
+    Conflicts:
+        setup.py
+
+commit eea6537921061b4dcfc54e00a99d3fa110e71433
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 10 15:39:51 2014 +0100
+
+    check for swig
+
+ setup.py | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+commit 53867bf9715ee1b4ea873bf5e2fbb7d9740a2b4a
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 10 15:35:04 2014 +0100
+
+    more friendly error message if gpgme is missing
+
+ setup.py | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+commit d2738b35d63b1492d69641c5466103685f2d3a30
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 10 15:35:04 2014 +0100
+
+    more friendly error message if gpgme is missing
+
+ setup.py | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+commit c0b01240becf8ba6cf1d4c1f64b2cb4c056f5163
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Fri May 9 15:20:24 2014 +0100
+
+    version number should have three digits
+
+ pyme/version.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 6672bb60b9bec60d38e854016c48658b57774578
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Wed May 7 15:11:08 2014 +0100
+
+    bump version number for upcoming release
+
+ pyme/version.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 7bd6de700f33ca5d1f27bc16ebbd401f21d2e788
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 3 19:36:25 2014 +0100
+
+    bump version number to indicate changes
+
+ pyme/version.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 4fb6bd9b3f47c1a343242ac83b326cacd12a136e
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 3 19:34:07 2014 +0100
+
+    pyme instead of pygpgme
+
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 9548973138d78241a45ccb82333b25f2cf36ce7d
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 3 19:31:10 2014 +0100
+
+    dirty hack to make 'python setup.py install' work
+
+ setup.py | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+commit a961d7eab9db478b7e603324bc5d243bd3c84bad
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 3 19:05:44 2014 +0100
+
+    moved everything down to the toplevel directory
+
+ COPYING                                 |  340 ++
+ COPYING.LESSER                          |  510 +++
+ ChangeLog                               |  802 +++++
+ INSTALL                                 |   15 +
+ Makefile                                |  104 +
+ debian/README.Debian                    |    6 +
+ debian/changelog                        |   93 +
+ debian/control                          |   34 +
+ debian/copyright                        |   25 +
+ debian/docs                             |    2 +
+ debian/examples                         |    2 +
+ debian/rules                            |   99 +
+ examples/PyGtkGpgKeys.glade             | 1394 ++++++++
+ examples/PyGtkGpgKeys.gladep            |    8 +
+ examples/PyGtkGpgKeys.py                |  663 ++++
+ examples/delkey.py                      |   34 +
+ examples/encrypt-to-all.py              |   65 +
+ examples/exportimport.py                |   75 +
+ examples/genkey.py                      |   45 +
+ examples/inter-edit.py                  |   57 +
+ examples/pygpa.glade                    | 5546 +++++++++++++++++++++++++++++++
+ examples/pygpa.py                       | 1457 ++++++++
+ examples/sign.py                        |   31 +
+ examples/signverify.py                  |   78 +
+ examples/simple.py                      |   52 +
+ examples/t-edit.py                      |   59 +
+ examples/testCMSgetkey.py               |   45 +
+ examples/verifydetails.py               |  100 +
+ gpgme-h-clean.py                        |   42 +
+ gpgme.i                                 |  267 ++
+ helpers.c                               |  154 +
+ helpers.h                               |   36 +
+ pyme/COPYING                            |  340 --
+ pyme/COPYING.LESSER                     |  510 ---
+ pyme/ChangeLog                          |  802 -----
+ pyme/INSTALL                            |   15 -
+ pyme/Makefile                           |  104 -
+ pyme/__init__.py                        |  137 +
+ pyme/callbacks.py                       |   47 +
+ pyme/constants/__init__.py              |    7 +
+ pyme/constants/data/__init__.py         |    4 +
+ pyme/constants/data/encoding.py         |   20 +
+ pyme/constants/event.py                 |   20 +
+ pyme/constants/import.py                |   20 +
+ pyme/constants/keylist/__init__.py      |    4 +
+ pyme/constants/keylist/mode.py          |   20 +
+ pyme/constants/md.py                    |   20 +
+ pyme/constants/pk.py                    |   20 +
+ pyme/constants/protocol.py              |   20 +
+ pyme/constants/sig/__init__.py          |    4 +
+ pyme/constants/sig/mode.py              |   20 +
+ pyme/constants/sigsum.py                |   20 +
+ pyme/constants/status.py                |   20 +
+ pyme/constants/validity.py              |   20 +
+ pyme/core.py                            |  463 +++
+ pyme/debian/README.Debian               |    6 -
+ pyme/debian/changelog                   |   93 -
+ pyme/debian/control                     |   34 -
+ pyme/debian/copyright                   |   25 -
+ pyme/debian/docs                        |    2 -
+ pyme/debian/examples                    |    2 -
+ pyme/debian/rules                       |   99 -
+ pyme/errors.py                          |   46 +
+ pyme/examples/PyGtkGpgKeys.glade        | 1394 --------
+ pyme/examples/PyGtkGpgKeys.gladep       |    8 -
+ pyme/examples/PyGtkGpgKeys.py           |  663 ----
+ pyme/examples/delkey.py                 |   34 -
+ pyme/examples/encrypt-to-all.py         |   65 -
+ pyme/examples/exportimport.py           |   75 -
+ pyme/examples/genkey.py                 |   45 -
+ pyme/examples/inter-edit.py             |   57 -
+ pyme/examples/pygpa.glade               | 5546 -------------------------------
+ pyme/examples/pygpa.py                  | 1457 --------
+ pyme/examples/sign.py                   |   31 -
+ pyme/examples/signverify.py             |   78 -
+ pyme/examples/simple.py                 |   52 -
+ pyme/examples/t-edit.py                 |   59 -
+ pyme/examples/testCMSgetkey.py          |   45 -
+ pyme/examples/verifydetails.py          |  100 -
+ pyme/gpgme-h-clean.py                   |   42 -
+ pyme/gpgme.i                            |  267 --
+ pyme/helpers.c                          |  154 -
+ pyme/helpers.h                          |   36 -
+ pyme/pyme/__init__.py                   |  137 -
+ pyme/pyme/callbacks.py                  |   47 -
+ pyme/pyme/constants/__init__.py         |    7 -
+ pyme/pyme/constants/data/__init__.py    |    4 -
+ pyme/pyme/constants/data/encoding.py    |   20 -
+ pyme/pyme/constants/event.py            |   20 -
+ pyme/pyme/constants/import.py           |   20 -
+ pyme/pyme/constants/keylist/__init__.py |    4 -
+ pyme/pyme/constants/keylist/mode.py     |   20 -
+ pyme/pyme/constants/md.py               |   20 -
+ pyme/pyme/constants/pk.py               |   20 -
+ pyme/pyme/constants/protocol.py         |   20 -
+ pyme/pyme/constants/sig/__init__.py     |    4 -
+ pyme/pyme/constants/sig/mode.py         |   20 -
+ pyme/pyme/constants/sigsum.py           |   20 -
+ pyme/pyme/constants/status.py           |   20 -
+ pyme/pyme/constants/validity.py         |   20 -
+ pyme/pyme/core.py                       |  463 ---
+ pyme/pyme/errors.py                     |   46 -
+ pyme/pyme/util.py                       |   72 -
+ pyme/pyme/version.py                    |   41 -
+ pyme/setup.py                           |   99 -
+ pyme/util.py                            |   72 +
+ pyme/version.py                         |   41 +
+ setup.py                                |   99 +
+ 108 files changed, 13384 insertions(+), 13384 deletions(-)
+
+commit 8148cdd424c434e833ce427612ea8c89abc6e41c
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Sat May 3 18:58:52 2014 +0100
+
+    removing pyme-web
+
+ pyme-web/Makefile                                  |  15 -
+ pyme-web/default.css                               |  37 --
+ pyme-web/doc/gpgme/ASCII-Armor.html                |  57 ---
+ pyme-web/doc/gpgme/Advanced-Key-Editing.html       |  98 ----
+ pyme-web/doc/gpgme/Algorithms.html                 |  47 --
+ pyme-web/doc/gpgme/Building-the-Source.html        |  82 ----
+ .../doc/gpgme/Callback-Based-Data-Buffers.html     | 148 ------
+ pyme-web/doc/gpgme/Cancellation.html               |  67 ---
+ pyme-web/doc/gpgme/Concept-Index.html              | 186 -------
+ pyme-web/doc/gpgme/Context-Attributes.html         |  52 --
+ pyme-web/doc/gpgme/Contexts.html                   |  61 ---
+ pyme-web/doc/gpgme/Creating-Contexts.html          |  49 --
+ pyme-web/doc/gpgme/Creating-Data-Buffers.html      |  47 --
+ pyme-web/doc/gpgme/Creating-a-Signature.html       | 143 ------
+ pyme-web/doc/gpgme/Crypto-Engine.html              |  79 ---
+ pyme-web/doc/gpgme/Crypto-Operations.html          |  67 ---
+ .../doc/gpgme/Cryptographic-Message-Syntax.html    |  42 --
+ .../doc/gpgme/Data-Buffer-I_002fO-Operations.html  | 104 ----
+ pyme-web/doc/gpgme/Data-Buffer-Meta_002dData.html  | 100 ----
+ pyme-web/doc/gpgme/Decrypt-and-Verify.html         |  79 ---
+ pyme-web/doc/gpgme/Decrypt.html                    | 123 -----
+ pyme-web/doc/gpgme/Deleting-Keys.html              |  67 ---
+ pyme-web/doc/gpgme/Destroying-Contexts.html        |  46 --
+ pyme-web/doc/gpgme/Destroying-Data-Buffers.html    |  70 ---
+ pyme-web/doc/gpgme/Encrypt.html                    |  45 --
+ pyme-web/doc/gpgme/Encrypting-a-Plaintext.html     | 147 ------
+ pyme-web/doc/gpgme/Engine-Configuration.html       |  65 ---
+ pyme-web/doc/gpgme/Engine-Information.html         | 119 -----
+ pyme-web/doc/gpgme/Engine-Version-Check.html       |  48 --
+ pyme-web/doc/gpgme/Error-Codes.html                | 133 -----
+ pyme-web/doc/gpgme/Error-Handling.html             |  72 ---
+ pyme-web/doc/gpgme/Error-Sources.html              |  89 ----
+ pyme-web/doc/gpgme/Error-Strings.html              |  80 ---
+ pyme-web/doc/gpgme/Error-Values.html               | 159 ------
+ pyme-web/doc/gpgme/Exchanging-Data.html            |  58 ---
+ pyme-web/doc/gpgme/Exporting-Keys.html             | 101 ----
+ pyme-web/doc/gpgme/Features.html                   |  59 ---
+ pyme-web/doc/gpgme/File-Based-Data-Buffers.html    |  74 ---
+ pyme-web/doc/gpgme/Function-and-Data-Index.html    | 229 ---------
+ pyme-web/doc/gpgme/Generating-Keys.html            | 144 ------
+ pyme-web/doc/gpgme/Getting-Started.html            |  55 ---
+ pyme-web/doc/gpgme/Hash-Algorithms.html            |  59 ---
+ pyme-web/doc/gpgme/Header.html                     |  53 --
+ .../doc/gpgme/I_002fO-Callback-Example-GDK.html    |  85 ----
+ .../gpgme/I_002fO-Callback-Example-GTK_002b.html   |  86 ----
+ .../doc/gpgme/I_002fO-Callback-Example-Qt.html     |  99 ----
+ pyme-web/doc/gpgme/I_002fO-Callback-Example.html   | 259 ----------
+ pyme-web/doc/gpgme/I_002fO-Callback-Interface.html | 142 ------
+ pyme-web/doc/gpgme/Importing-Keys.html             | 171 -------
+ pyme-web/doc/gpgme/Included-Certificates.html      |  70 ---
+ pyme-web/doc/gpgme/Information-About-Keys.html     | 207 --------
+ .../doc/gpgme/Information-About-Trust-Items.html   |  75 ---
+ pyme-web/doc/gpgme/Introduction.html               |  53 --
+ pyme-web/doc/gpgme/Key-Listing-Mode.html           |  99 ----
+ pyme-web/doc/gpgme/Key-Management.html             | 260 ----------
+ pyme-web/doc/gpgme/Key-Signatures.html             | 130 -----
+ .../doc/gpgme/Largefile-Support-_0028LFS_0029.html | 110 -----
+ pyme-web/doc/gpgme/Library-Copying.html            | 542 ---------------------
+ pyme-web/doc/gpgme/Library-Version-Check.html      |  97 ----
+ pyme-web/doc/gpgme/Listing-Keys.html               | 204 --------
+ pyme-web/doc/gpgme/Listing-Trust-Items.html        |  88 ----
+ pyme-web/doc/gpgme/Locale.html                     |  69 ---
+ pyme-web/doc/gpgme/Manipulating-Data-Buffers.html  |  45 --
+ pyme-web/doc/gpgme/Manipulating-Keys.html          |  63 ---
+ pyme-web/doc/gpgme/Manipulating-Trust-Items.html   |  62 ---
+ pyme-web/doc/gpgme/Memory-Based-Data-Buffers.html  | 107 ----
+ pyme-web/doc/gpgme/Multi-Threading.html            |  93 ----
+ pyme-web/doc/gpgme/OpenPGP.html                    |  44 --
+ pyme-web/doc/gpgme/Overview.html                   |  57 ---
+ pyme-web/doc/gpgme/Passphrase-Callback.html        | 101 ----
+ pyme-web/doc/gpgme/Preparation.html                |  54 --
+ pyme-web/doc/gpgme/Progress-Meter-Callback.html    |  80 ---
+ pyme-web/doc/gpgme/Protocol-Selection.html         |  60 ---
+ pyme-web/doc/gpgme/Protocols-and-Engines.html      |  82 ----
+ pyme-web/doc/gpgme/Public-Key-Algorithms.html      |  74 ---
+ .../doc/gpgme/Registering-I_002fO-Callbacks.html   |  81 ---
+ pyme-web/doc/gpgme/Run-Control.html                |  53 --
+ pyme-web/doc/gpgme/Selecting-Signers.html          |  64 ---
+ pyme-web/doc/gpgme/Sign.html                       |  50 --
+ pyme-web/doc/gpgme/Signal-Handling.html            |  61 ---
+ pyme-web/doc/gpgme/Signature-Notation-Data.html    |  85 ----
+ pyme-web/doc/gpgme/Text-Mode.html                  |  63 ---
+ pyme-web/doc/gpgme/Trust-Item-Management.html      |  68 ---
+ pyme-web/doc/gpgme/Using-Automake.html             |  74 ---
+ pyme-web/doc/gpgme/Using-External-Event-Loops.html |  74 ---
+ pyme-web/doc/gpgme/Using-Libtool.html              |  44 --
+ pyme-web/doc/gpgme/Verify.html                     | 492 -------------------
+ pyme-web/doc/gpgme/Waiting-For-Completion.html     |  77 ---
+ pyme-web/doc/gpgme/index.html                      | 169 -------
+ pyme-web/doc/pyme/index.html                       | 164 -------
+ pyme-web/doc/pyme/pyme.callbacks.html              |  42 --
+ .../doc/pyme/pyme.constants.data.encoding.html     |  48 --
+ pyme-web/doc/pyme/pyme.constants.data.html         |  29 --
+ pyme-web/doc/pyme/pyme.constants.event.html        |  48 --
+ pyme-web/doc/pyme/pyme.constants.html              |  39 --
+ pyme-web/doc/pyme/pyme.constants.import.html       |  49 --
+ pyme-web/doc/pyme/pyme.constants.keylist.html      |  29 --
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html |  49 --
+ pyme-web/doc/pyme/pyme.constants.md.html           |  58 ---
+ pyme-web/doc/pyme/pyme.constants.pk.html           |  50 --
+ pyme-web/doc/pyme/pyme.constants.protocol.html     |  48 --
+ pyme-web/doc/pyme/pyme.constants.sig.html          |  29 --
+ pyme-web/doc/pyme/pyme.constants.sig.mode.html     |  47 --
+ pyme-web/doc/pyme/pyme.constants.sigsum.html       |  55 ---
+ pyme-web/doc/pyme/pyme.constants.status.html       | 126 -----
+ pyme-web/doc/pyme/pyme.constants.validity.html     |  50 --
+ pyme-web/doc/pyme/pyme.core.html                   | 277 -----------
+ pyme-web/doc/pyme/pyme.errors.html                 |  82 ----
+ pyme-web/doc/pyme/pyme.html                        | 164 -------
+ pyme-web/doc/pyme/pyme.util.html                   |  81 ---
+ pyme-web/doc/pyme/pyme.version.html                |  37 --
+ pyme-web/index.html                                |  72 ---
+ 112 files changed, 10551 deletions(-)
+
+commit 684d95feb7e10e538a56fb1b27f1456111bacb60
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Mon Jan 6 17:44:20 2014 +0100
+
+    fixing op_export_keys()
+
+    the conversion of gpgme_key_t [] was restricted to gpgme_key_t [] with the
+    name recv, i.e. only the use-cases of encryption were covered.
+
+    see: http://sourceforge.net/mailarchive/forum.php?forum_name=pyme-help&max_rows=25&style=nested&viewmonth=201309
+
+ pyme/gpgme.i | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+commit 658d23b95110d21eeb50abf4e74701a667521a88
+Author: Martin Albrecht <martinralbrecht@googlemail.com>
+Date:   Mon Jan 6 17:41:33 2014 +0100
+
+    deleting CVSROOT
+
+ CVSROOT/checkoutlist | 13 -------------
+ CVSROOT/commitinfo   | 15 ---------------
+ CVSROOT/config       | 21 ---------------------
+ CVSROOT/cvswrappers  | 19 -------------------
+ CVSROOT/editinfo     | 21 ---------------------
+ CVSROOT/loginfo      | 26 --------------------------
+ CVSROOT/modules      | 26 --------------------------
+ CVSROOT/notify       | 12 ------------
+ CVSROOT/rcsinfo      | 13 -------------
+ CVSROOT/taginfo      | 20 --------------------
+ CVSROOT/verifymsg    | 21 ---------------------
+ 11 files changed, 207 deletions(-)
+
+commit 576b555499c094c4786d42de9e59aa9826009b89
+Author: convert-repo <devnull@localhost>
+Date:   Mon Jan 6 15:22:44 2014 +0000
+
+    update tags
+
+commit 2dcf0c5b702eb5a18c66ff1e42a72eaa7427af1d
+Author: belyi <devnull@localhost>
+Date:   Wed Nov 26 02:38:33 2008 +0000
+
+    Move Windows specific fix from helpers.c to helpers.h so that it works
+    for edit callback as well as for the passphrase one.
+
+ pyme/helpers.c | 5 -----
+ pyme/helpers.h | 5 +++++
+ 2 files changed, 5 insertions(+), 5 deletions(-)
+
+commit 42a035f2ef62470fea7a7f8ee33a1297fa90a603
+Author: belyi <devnull@localhost>
+Date:   Mon Nov 24 21:44:30 2008 +0000
+
+    Update the way build directives are constructed on MinGW to have a bit
+    more robust. Update PyMe build version to 0.8.1 in version.py
+
+ pyme/pyme/version.py |  2 +-
+ pyme/setup.py        | 10 ++++++++--
+ 2 files changed, 9 insertions(+), 3 deletions(-)
+
+commit 3aaa20fbcba17066c9ffd580f5209946022793a2
+Author: belyi <devnull@localhost>
+Date:   Mon Nov 24 06:57:11 2008 +0000
+
+    Update changelog
+
+ pyme/debian/changelog | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+commit 689ff46b2550547e3883f809a6dc40c22c3e137e
+Author: belyi <devnull@localhost>
+Date:   Mon Nov 24 06:50:41 2008 +0000
+
+    Fix hang problem on Windows when password is written to a filehandle.
+    Fix the way path is constructed on MinGW platform.
+
+ pyme/helpers.c | 5 +++++
+ pyme/setup.py  | 4 ++--
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+commit 852a60d541d66cb56f40378182b976fd87a02c46
+Author: belyi <devnull@localhost>
+Date:   Sun Nov 23 04:31:31 2008 +0000
+
+    Add Bernard's example testCMSgetkey.py and his updates for
+    verifydetails.py
+
+ pyme/examples/testCMSgetkey.py | 45 ++++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/verifydetails.py | 43 +++++++++++++++++++++++++++++-----------
+ 2 files changed, 77 insertions(+), 11 deletions(-)
+
+commit f080527d9184f3360f0a8ef6136b9a188d8e7d2a
+Author: belyi <devnull@localhost>
+Date:   Thu May 29 18:29:37 2008 +0000
+
+    Remove debian packaging for python2.3 since it is removed from both
+    testing and unstable dists.
+    Update docs build target to have correct PYTHONPATH set.
+
+ pyme/Makefile         | 2 +-
+ pyme/debian/changelog | 4 +++-
+ pyme/debian/control   | 4 ++--
+ pyme/debian/rules     | 2 --
+ 4 files changed, 6 insertions(+), 6 deletions(-)
+
+commit c25d133fcbadf3c7f6e655586b4a05d6e3cf6f0b
+Author: belyi <devnull@localhost>
+Date:   Thu Apr 3 13:37:12 2008 +0000
+
+    Forgot to adjust mainText margin. Doing it now.
+
+ pyme-web/default.css | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit 897286a54a32336d060cd03305cdecb7905f34f1
+Author: belyi <devnull@localhost>
+Date:   Thu Apr 3 13:00:11 2008 +0000
+
+    Fix an error in default.css and make index.html "Standards Compliant".
+
+ pyme-web/default.css | 2 +-
+ pyme-web/index.html  | 7 ++++---
+ 2 files changed, 5 insertions(+), 4 deletions(-)
+
+commit 4e049212bd214449cc0ba1ce06e00782783f328a
+Author: belyi <devnull@localhost>
+Date:   Thu Apr 3 12:38:42 2008 +0000
+
+    Adjust spacing between links.
+
+ pyme-web/default.css | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+commit cb2bddfbd77483b1deb14f2eab0715a03dd33fcd
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 22:50:21 2008 +0000
+
+    Make style a big more IE friendly.
+
+ pyme-web/default.css | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+commit ad66f0a1bb01b46baac328e9fee439b35a60c232
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 11:58:32 2008 +0000
+
+    Make GPGME documentation a bit more web friendly on the index.html page.
+
+ pyme-web/doc/gpgme/Algorithms.html              |   2 +-
+ pyme-web/doc/gpgme/Concept-Index.html           |   2 +-
+ pyme-web/doc/gpgme/Contexts.html                |   2 +-
+ pyme-web/doc/gpgme/Error-Handling.html          |   2 +-
+ pyme-web/doc/gpgme/Exchanging-Data.html         |   2 +-
+ pyme-web/doc/gpgme/Function-and-Data-Index.html |   2 +-
+ pyme-web/doc/gpgme/Introduction.html            |   4 +-
+ pyme-web/doc/gpgme/Library-Copying.html         |   2 +-
+ pyme-web/doc/gpgme/Preparation.html             |   2 +-
+ pyme-web/doc/gpgme/Protocols-and-Engines.html   |   2 +-
+ pyme-web/doc/gpgme/index.html                   | 229 +-----------------------
+ 11 files changed, 12 insertions(+), 239 deletions(-)
+
+commit 4f57c0ccb049d4442e7732e2d1d05dabffd2a21d
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 06:12:57 2008 +0000
+
+    Add missing core.set_locale() to set default locale for contexts.
+
+ pyme/debian/changelog | 2 +-
+ pyme/pyme/core.py     | 4 ++++
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+commit acf7ead3dea8590cf9fe86b67bb125837ad6ed4f
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 05:50:24 2008 +0000
+
+    Avoid leaks caused by keys.
+    Add set/get methods for engine info.
+
+ pyme/debian/changelog | 10 ++++++++++
+ pyme/pyme/core.py     | 24 ++++++++++++++++++++++++
+ 2 files changed, 34 insertions(+)
+
+commit df4a2fb518adbb6420d95ce74af212c87abff7e7
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 04:04:41 2008 +0000
+
+    Update index.html to reflect new versions on the web.
+
+ pyme-web/Makefile             | 3 ++-
+ pyme-web/doc/gpgme/index.html | 4 +---
+ pyme-web/index.html           | 4 ++--
+ 3 files changed, 5 insertions(+), 6 deletions(-)
+
+commit bd3ffc9bdf98d6aafde6b689c6c8215fa468612d
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 04:01:04 2008 +0000
+
+    Update PyMe documentation to match 0.8.0 version of the package.
+
+ pyme-web/doc/pyme/index.html                       | 14 ++++-----
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html |  1 +
+ pyme-web/doc/pyme/pyme.constants.protocol.html     |  4 ++-
+ pyme-web/doc/pyme/pyme.constants.status.html       |  9 ++++++
+ pyme-web/doc/pyme/pyme.core.html                   | 36 ++++++++++++++++++----
+ pyme-web/doc/pyme/pyme.errors.html                 |  8 ++---
+ pyme-web/doc/pyme/pyme.html                        | 14 ++++-----
+ pyme-web/doc/pyme/pyme.util.html                   | 17 ++++++++--
+ pyme-web/doc/pyme/pyme.version.html                | 14 ++++-----
+ 9 files changed, 82 insertions(+), 35 deletions(-)
+
+commit 6973a69a317608a0d0661590d701f4e3f3a21b32
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 02:35:24 2008 +0000
+
+    Have a fix for Contents being put onto 'Function and Data Index' page.
+
+ pyme-web/doc/gpgme/Concept-Index.html           |   2 +-
+ pyme-web/doc/gpgme/Function-and-Data-Index.html | 153 +----------------------
+ pyme-web/doc/gpgme/index.html                   | 154 +++++++++++++++++++++++-
+ 3 files changed, 155 insertions(+), 154 deletions(-)
+
+commit 086315964cbc2abad1187f306dcb9c72ac3257f3
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 2 01:00:29 2008 +0000
+
+    Update GPGME documentation. It's for v1.1.6 now.
+
+ pyme-web/doc/gpgme/ASCII-Armor.html                |  57 ++
+ pyme-web/doc/gpgme/Advanced-Key-Editing.html       |  98 +++
+ pyme-web/doc/gpgme/Algorithms.html                 |  47 ++
+ pyme-web/doc/gpgme/Building-the-Source.html        |  82 +++
+ .../doc/gpgme/Callback-Based-Data-Buffers.html     | 148 +++++
+ pyme-web/doc/gpgme/Cancellation.html               |  67 ++
+ pyme-web/doc/gpgme/Concept-Index.html              | 186 ++++++
+ pyme-web/doc/gpgme/Context-Attributes.html         |  52 ++
+ pyme-web/doc/gpgme/Contexts.html                   |  61 ++
+ pyme-web/doc/gpgme/Creating-Contexts.html          |  49 ++
+ pyme-web/doc/gpgme/Creating-Data-Buffers.html      |  47 ++
+ pyme-web/doc/gpgme/Creating-a-Signature.html       | 143 +++++
+ pyme-web/doc/gpgme/Crypto-Engine.html              |  79 +++
+ pyme-web/doc/gpgme/Crypto-Operations.html          |  67 ++
+ .../doc/gpgme/Cryptographic-Message-Syntax.html    |  42 ++
+ .../doc/gpgme/Data-Buffer-I_002fO-Operations.html  | 104 ++++
+ pyme-web/doc/gpgme/Data-Buffer-Meta_002dData.html  | 100 +++
+ pyme-web/doc/gpgme/Decrypt-and-Verify.html         |  79 +++
+ pyme-web/doc/gpgme/Decrypt.html                    | 123 ++++
+ pyme-web/doc/gpgme/Deleting-Keys.html              |  67 ++
+ pyme-web/doc/gpgme/Destroying-Contexts.html        |  46 ++
+ pyme-web/doc/gpgme/Destroying-Data-Buffers.html    |  70 +++
+ pyme-web/doc/gpgme/Encrypt.html                    |  45 ++
+ pyme-web/doc/gpgme/Encrypting-a-Plaintext.html     | 147 +++++
+ pyme-web/doc/gpgme/Engine-Configuration.html       |  65 ++
+ pyme-web/doc/gpgme/Engine-Information.html         | 119 ++++
+ pyme-web/doc/gpgme/Engine-Version-Check.html       |  48 ++
+ pyme-web/doc/gpgme/Error-Codes.html                | 133 ++++
+ pyme-web/doc/gpgme/Error-Handling.html             |  72 +++
+ pyme-web/doc/gpgme/Error-Sources.html              |  89 +++
+ pyme-web/doc/gpgme/Error-Strings.html              |  80 +++
+ pyme-web/doc/gpgme/Error-Values.html               | 159 +++++
+ pyme-web/doc/gpgme/Exchanging-Data.html            |  58 ++
+ pyme-web/doc/gpgme/Exporting-Keys.html             | 101 +++
+ pyme-web/doc/gpgme/Features.html                   |  59 ++
+ pyme-web/doc/gpgme/File-Based-Data-Buffers.html    |  74 +++
+ pyme-web/doc/gpgme/Function-and-Data-Index.html    | 380 ++++++++++++
+ pyme-web/doc/gpgme/Generating-Keys.html            | 144 +++++
+ pyme-web/doc/gpgme/Getting-Started.html            |  55 ++
+ pyme-web/doc/gpgme/Hash-Algorithms.html            |  59 ++
+ pyme-web/doc/gpgme/Header.html                     |  53 ++
+ .../doc/gpgme/I_002fO-Callback-Example-GDK.html    |  85 +++
+ .../gpgme/I_002fO-Callback-Example-GTK_002b.html   |  86 +++
+ .../doc/gpgme/I_002fO-Callback-Example-Qt.html     |  99 +++
+ pyme-web/doc/gpgme/I_002fO-Callback-Example.html   | 259 ++++++++
+ pyme-web/doc/gpgme/I_002fO-Callback-Interface.html | 142 +++++
+ pyme-web/doc/gpgme/Importing-Keys.html             | 171 +++++
+ pyme-web/doc/gpgme/Included-Certificates.html      |  70 +++
+ pyme-web/doc/gpgme/Information-About-Keys.html     | 207 +++++++
+ .../doc/gpgme/Information-About-Trust-Items.html   |  75 +++
+ pyme-web/doc/gpgme/Introduction.html               |  53 ++
+ pyme-web/doc/gpgme/Key-Listing-Mode.html           |  99 +++
+ pyme-web/doc/gpgme/Key-Management.html             | 260 ++++++++
+ pyme-web/doc/gpgme/Key-Signatures.html             | 130 ++++
+ .../doc/gpgme/Largefile-Support-_0028LFS_0029.html | 110 ++++
+ pyme-web/doc/gpgme/Library-Copying.html            | 542 ++++++++++++++++
+ pyme-web/doc/gpgme/Library-Version-Check.html      |  97 +++
+ pyme-web/doc/gpgme/Listing-Keys.html               | 204 ++++++
+ pyme-web/doc/gpgme/Listing-Trust-Items.html        |  88 +++
+ pyme-web/doc/gpgme/Locale.html                     |  69 +++
+ pyme-web/doc/gpgme/Manipulating-Data-Buffers.html  |  45 ++
+ pyme-web/doc/gpgme/Manipulating-Keys.html          |  63 ++
+ pyme-web/doc/gpgme/Manipulating-Trust-Items.html   |  62 ++
+ pyme-web/doc/gpgme/Memory-Based-Data-Buffers.html  | 107 ++++
+ pyme-web/doc/gpgme/Multi-Threading.html            |  93 +++
+ pyme-web/doc/gpgme/OpenPGP.html                    |  44 ++
+ pyme-web/doc/gpgme/Overview.html                   |  57 ++
+ pyme-web/doc/gpgme/Passphrase-Callback.html        | 101 +++
+ pyme-web/doc/gpgme/Preparation.html                |  54 ++
+ pyme-web/doc/gpgme/Progress-Meter-Callback.html    |  80 +++
+ pyme-web/doc/gpgme/Protocol-Selection.html         |  60 ++
+ pyme-web/doc/gpgme/Protocols-and-Engines.html      |  82 +++
+ pyme-web/doc/gpgme/Public-Key-Algorithms.html      |  74 +++
+ .../doc/gpgme/Registering-I_002fO-Callbacks.html   |  81 +++
+ pyme-web/doc/gpgme/Run-Control.html                |  53 ++
+ pyme-web/doc/gpgme/Selecting-Signers.html          |  64 ++
+ pyme-web/doc/gpgme/Sign.html                       |  50 ++
+ pyme-web/doc/gpgme/Signal-Handling.html            |  61 ++
+ pyme-web/doc/gpgme/Signature-Notation-Data.html    |  85 +++
+ pyme-web/doc/gpgme/Text-Mode.html                  |  63 ++
+ pyme-web/doc/gpgme/Trust-Item-Management.html      |  68 ++
+ pyme-web/doc/gpgme/Using-Automake.html             |  74 +++
+ pyme-web/doc/gpgme/Using-External-Event-Loops.html |  74 +++
+ pyme-web/doc/gpgme/Using-Libtool.html              |  44 ++
+ pyme-web/doc/gpgme/Verify.html                     | 492 +++++++++++++++
+ pyme-web/doc/gpgme/Waiting-For-Completion.html     |  77 +++
+ pyme-web/doc/gpgme/gpgme.html                      | 251 --------
+ pyme-web/doc/gpgme/gpgme_1.html                    |  76 ---
+ pyme-web/doc/gpgme/gpgme_10.html                   |  61 --
+ pyme-web/doc/gpgme/gpgme_11.html                   | 130 ----
+ pyme-web/doc/gpgme/gpgme_12.html                   |  82 ---
+ pyme-web/doc/gpgme/gpgme_13.html                   | 130 ----
+ pyme-web/doc/gpgme/gpgme_14.html                   | 108 ----
+ pyme-web/doc/gpgme/gpgme_15.html                   |  69 ---
+ pyme-web/doc/gpgme/gpgme_16.html                   | 169 -----
+ pyme-web/doc/gpgme/gpgme_17.html                   |  63 --
+ pyme-web/doc/gpgme/gpgme_18.html                   |  63 --
+ pyme-web/doc/gpgme/gpgme_19.html                   |  66 --
+ pyme-web/doc/gpgme/gpgme_2.html                    |  79 ---
+ pyme-web/doc/gpgme/gpgme_20.html                   | 120 ----
+ pyme-web/doc/gpgme/gpgme_21.html                   | 102 ---
+ pyme-web/doc/gpgme/gpgme_22.html                   | 108 ----
+ pyme-web/doc/gpgme/gpgme_23.html                   | 237 -------
+ pyme-web/doc/gpgme/gpgme_24.html                   | 154 -----
+ pyme-web/doc/gpgme/gpgme_25.html                   | 248 --------
+ pyme-web/doc/gpgme/gpgme_26.html                   | 107 ----
+ pyme-web/doc/gpgme/gpgme_27.html                   |  80 ---
+ pyme-web/doc/gpgme/gpgme_28.html                   |  67 --
+ pyme-web/doc/gpgme/gpgme_29.html                   | 164 -----
+ pyme-web/doc/gpgme/gpgme_3.html                    |  86 ---
+ pyme-web/doc/gpgme/gpgme_30.html                   | 106 ----
+ pyme-web/doc/gpgme/gpgme_31.html                   | 232 -------
+ pyme-web/doc/gpgme/gpgme_32.html                   |  85 ---
+ pyme-web/doc/gpgme/gpgme_33.html                   | 223 -------
+ pyme-web/doc/gpgme/gpgme_34.html                   |  83 ---
+ pyme-web/doc/gpgme/gpgme_35.html                   |  70 ---
+ pyme-web/doc/gpgme/gpgme_36.html                   |  63 --
+ pyme-web/doc/gpgme/gpgme_37.html                   |  66 --
+ pyme-web/doc/gpgme/gpgme_38.html                   |  86 ---
+ pyme-web/doc/gpgme/gpgme_39.html                   |  79 ---
+ pyme-web/doc/gpgme/gpgme_4.html                    |  83 ---
+ pyme-web/doc/gpgme/gpgme_40.html                   |  89 ---
+ pyme-web/doc/gpgme/gpgme_41.html                   |  99 ---
+ pyme-web/doc/gpgme/gpgme_42.html                   | 144 -----
+ pyme-web/doc/gpgme/gpgme_43.html                   | 152 -----
+ pyme-web/doc/gpgme/gpgme_44.html                   | 112 ----
+ pyme-web/doc/gpgme/gpgme_45.html                   | 101 ---
+ pyme-web/doc/gpgme/gpgme_46.html                   | 459 --------------
+ pyme-web/doc/gpgme/gpgme_47.html                   | 292 ---------
+ pyme-web/doc/gpgme/gpgme_48.html                   | 363 -----------
+ pyme-web/doc/gpgme/gpgme_49.html                   | 209 -------
+ pyme-web/doc/gpgme/gpgme_5.html                    |  74 ---
+ pyme-web/doc/gpgme/gpgme_50.html                   |  88 ---
+ pyme-web/doc/gpgme/gpgme_51.html                   | 208 -------
+ pyme-web/doc/gpgme/gpgme_52.html                   | 154 -----
+ pyme-web/doc/gpgme/gpgme_53.html                   | 291 ---------
+ pyme-web/doc/gpgme/gpgme_54.html                   |  91 ---
+ pyme-web/doc/gpgme/gpgme_55.html                   | 107 ----
+ pyme-web/doc/gpgme/gpgme_56.html                   | 140 -----
+ pyme-web/doc/gpgme/gpgme_57.html                   | 106 ----
+ pyme-web/doc/gpgme/gpgme_58.html                   |  89 ---
+ pyme-web/doc/gpgme/gpgme_59.html                   |  97 ---
+ pyme-web/doc/gpgme/gpgme_6.html                    |  77 ---
+ pyme-web/doc/gpgme/gpgme_60.html                   | 142 -----
+ pyme-web/doc/gpgme/gpgme_61.html                   | 626 -------------------
+ pyme-web/doc/gpgme/gpgme_62.html                   | 107 ----
+ pyme-web/doc/gpgme/gpgme_63.html                   |  67 --
+ pyme-web/doc/gpgme/gpgme_64.html                   |  95 ---
+ pyme-web/doc/gpgme/gpgme_65.html                   | 233 -------
+ pyme-web/doc/gpgme/gpgme_66.html                   |  65 --
+ pyme-web/doc/gpgme/gpgme_67.html                   | 220 -------
+ pyme-web/doc/gpgme/gpgme_68.html                   |  75 ---
+ pyme-web/doc/gpgme/gpgme_69.html                   | 119 ----
+ pyme-web/doc/gpgme/gpgme_7.html                    | 123 ----
+ pyme-web/doc/gpgme/gpgme_70.html                   | 107 ----
+ pyme-web/doc/gpgme/gpgme_71.html                   | 218 -------
+ pyme-web/doc/gpgme/gpgme_72.html                   | 134 ----
+ pyme-web/doc/gpgme/gpgme_73.html                   | 299 ---------
+ pyme-web/doc/gpgme/gpgme_74.html                   | 103 ----
+ pyme-web/doc/gpgme/gpgme_75.html                   | 104 ----
+ pyme-web/doc/gpgme/gpgme_76.html                   | 118 ----
+ pyme-web/doc/gpgme/gpgme_77.html                   |  95 ---
+ pyme-web/doc/gpgme/gpgme_78.html                   |  71 ---
+ pyme-web/doc/gpgme/gpgme_79.html                   | 686 ---------------------
+ pyme-web/doc/gpgme/gpgme_8.html                    | 155 -----
+ pyme-web/doc/gpgme/gpgme_80.html                   | 120 ----
+ pyme-web/doc/gpgme/gpgme_81.html                   | 278 ---------
+ pyme-web/doc/gpgme/gpgme_82.html                   | 272 --------
+ pyme-web/doc/gpgme/gpgme_83.html                   | 180 ------
+ pyme-web/doc/gpgme/gpgme_84.html                   |  99 ---
+ pyme-web/doc/gpgme/gpgme_9.html                    | 104 ----
+ pyme-web/doc/gpgme/gpgme_abt.html                  | 206 -------
+ pyme-web/doc/gpgme/gpgme_fot.html                  |  53 --
+ pyme-web/doc/gpgme/gpgme_ovr.html                  |  68 --
+ pyme-web/doc/gpgme/gpgme_toc.html                  | 247 --------
+ pyme-web/doc/gpgme/index.html                      | 497 ++++++++-------
+ 176 files changed, 9054 insertions(+), 13378 deletions(-)
+
+commit 163c1053dc761682f5a4231da163bdd0ff7162d7
+Author: belyi <devnull@localhost>
+Date:   Tue Apr 1 21:14:29 2008 +0000
+
+    Update Home page to be a bit more visitor friendly.
+
+ pyme-web/Makefile    |  2 +-
+ pyme-web/default.css | 27 ++++++++++++++++++++
+ pyme-web/index.html  | 70 +++++++++++++++++++++++++++++++++++-----------------
+ 3 files changed, 75 insertions(+), 24 deletions(-)
+
+commit 05db2d17d8fda0ab8c948bbdc0643dfc1466830d
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 30 21:27:38 2008 +0000
+
+    Add a rule to build binary distribution for Windows.
+
+ pyme/Makefile | 16 ++++++++++++++--
+ 1 file changed, 14 insertions(+), 2 deletions(-)
+
+commit 57acb1089f5f8c24323ee62fc0a7f492a496b9c0
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 29 22:50:11 2008 +0000
+
+    Switch to using central location for python files (pycentral)
+    Update docs rule to fix location of the python source files.
+
+ pyme/Makefile             |  5 +++-
+ pyme/debian/changelog     |  4 ++-
+ pyme/debian/control       | 74 +++++------------------------------------------
+ pyme/debian/dirs          |  2 --
+ pyme/debian/docs          |  1 +
+ pyme/debian/postinst.ex   | 48 ------------------------------
+ pyme/debian/postrm.ex     | 38 ------------------------
+ pyme/debian/preinst.ex    | 44 ----------------------------
+ pyme/debian/prerm.ex      | 39 -------------------------
+ pyme/debian/rules         | 50 ++++++--------------------------
+ pyme/debian/setup.cfg-2.2 |  8 -----
+ pyme/debian/setup.cfg-2.3 |  8 -----
+ pyme/debian/setup.cfg-2.4 |  8 -----
+ pyme/gpgme-h-clean.py     |  2 +-
+ pyme/pyme/core.py         |  2 +-
+ pyme/pyme/util.py         |  2 +-
+ 16 files changed, 28 insertions(+), 307 deletions(-)
+
+commit 2b56fd10517cfbcffaa4ba98d8ea42f40f0d38a9
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 23 02:01:12 2008 +0000
+
+    Turn SWIG's autodoc feature on. Ignore 'next' in the types which are lists now.
+    Use new style for class declarations. Specify None as a default value for
+    core.check_version() method. Update version.py for 0.8.0 version.
+
+ pyme/examples/pygpa.py | 2 +-
+ pyme/gpgme.i           | 5 +++++
+ pyme/pyme/core.py      | 2 +-
+ pyme/pyme/util.py      | 5 +++--
+ pyme/pyme/version.py   | 6 +++---
+ 5 files changed, 13 insertions(+), 7 deletions(-)
+
+commit df5e25d7ee4dc0aa0d429f9d009322dd8ac33bb8
+Author: belyi <devnull@localhost>
+Date:   Thu Mar 20 19:07:00 2008 +0000
+
+    Improve matching for DEPRECATED typedefs
+
+ pyme/gpgme-h-clean.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit 78d8fc732848ac267ec65e9069265cd500587cdf
+Author: belyi <devnull@localhost>
+Date:   Wed Mar 19 19:28:40 2008 +0000
+
+    Update API to use list when types containing 'next' field are return.
+    Update examples accordingly
+    Add verifydetails.py example
+    Start adding bullets for 0.8.0 version.
+
+ pyme/Makefile                   |  2 +-
+ pyme/debian/changelog           | 14 +++++++-
+ pyme/examples/PyGtkGpgKeys.py   | 53 +++++++++++++--------------
+ pyme/examples/delkey.py         |  7 ++--
+ pyme/examples/encrypt-to-all.py |  7 ++--
+ pyme/examples/exportimport.py   |  7 ++--
+ pyme/examples/pygpa.py          | 70 ++++++++++++++++--------------------
+ pyme/examples/signverify.py     | 11 +++---
+ pyme/examples/verifydetails.py  | 79 +++++++++++++++++++++++++++++++++++++++++
+ pyme/gpgme.i                    | 19 +++++++++-
+ 10 files changed, 180 insertions(+), 89 deletions(-)
+
+commit 342d85b07475e7360bcd62804bf5facda039494f
+Author: belyi <devnull@localhost>
+Date:   Mon Mar 10 01:14:16 2008 +0000
+
+    Change references to source files so that they point to the WebCVS browse
+    location.
+
+ pyme-web/doc/pyme/index.html                        | 2 +-
+ pyme-web/doc/pyme/pyme.callbacks.html               | 2 +-
+ pyme-web/doc/pyme/pyme.constants.data.encoding.html | 2 +-
+ pyme-web/doc/pyme/pyme.constants.data.html          | 2 +-
+ pyme-web/doc/pyme/pyme.constants.event.html         | 2 +-
+ pyme-web/doc/pyme/pyme.constants.html               | 2 +-
+ pyme-web/doc/pyme/pyme.constants.import.html        | 2 +-
+ pyme-web/doc/pyme/pyme.constants.keylist.html       | 2 +-
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html  | 2 +-
+ pyme-web/doc/pyme/pyme.constants.md.html            | 2 +-
+ pyme-web/doc/pyme/pyme.constants.pk.html            | 2 +-
+ pyme-web/doc/pyme/pyme.constants.protocol.html      | 2 +-
+ pyme-web/doc/pyme/pyme.constants.sig.html           | 2 +-
+ pyme-web/doc/pyme/pyme.constants.sig.mode.html      | 2 +-
+ pyme-web/doc/pyme/pyme.constants.sigsum.html        | 2 +-
+ pyme-web/doc/pyme/pyme.constants.status.html        | 2 +-
+ pyme-web/doc/pyme/pyme.constants.validity.html      | 2 +-
+ pyme-web/doc/pyme/pyme.core.html                    | 2 +-
+ pyme-web/doc/pyme/pyme.errors.html                  | 2 +-
+ pyme-web/doc/pyme/pyme.html                         | 2 +-
+ pyme-web/doc/pyme/pyme.util.html                    | 2 +-
+ pyme-web/doc/pyme/pyme.version.html                 | 2 +-
+ 22 files changed, 22 insertions(+), 22 deletions(-)
+
+commit 4139dd1d066c1a6c892d84fe45dc3e6c4aa1b803
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 8 18:21:08 2008 +0000
+
+    Add core.check_version(None) to all examples since this function is used by
+    Gpgme to do internal initialization. Update debian/rules to use dh_pysupport
+    instead of deprecated dh_python.
+
+ pyme/debian/rules               | 8 +++-----
+ pyme/examples/PyGtkGpgKeys.py   | 7 ++++++-
+ pyme/examples/delkey.py         | 2 ++
+ pyme/examples/encrypt-to-all.py | 3 +++
+ pyme/examples/exportimport.py   | 2 ++
+ pyme/examples/genkey.py         | 1 +
+ pyme/examples/inter-edit.py     | 3 +++
+ pyme/examples/pygpa.py          | 5 +++++
+ pyme/examples/sign.py           | 2 ++
+ pyme/examples/signverify.py     | 2 ++
+ pyme/examples/simple.py         | 2 ++
+ pyme/examples/t-edit.py         | 3 +++
+ 12 files changed, 34 insertions(+), 6 deletions(-)
+
+commit ae76c6176457dd38e0634cbc17d794294a3a81d2
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 12 22:20:38 2006 +0000
+
+    Change name of internal package name from 'gpgme' to 'pygpgme' to avoid
+    conflict with gpgme.dll on Windows.
+    Fix build with SWIG 1.3.28.
+    Change version to 0.7.1 in a preparation for new release.
+
+ pyme/Makefile          |   3 +-
+ pyme/debian/changelog  |  12 ++++
+ pyme/gpgme.i           |  19 +++---
+ pyme/pyme/callbacks.py |   1 -
+ pyme/pyme/core.py      | 153 +++++++++++++++++++++++++------------------------
+ pyme/pyme/errors.py    |  12 ++--
+ pyme/pyme/util.py      |  10 ++--
+ pyme/pyme/version.py   |   2 +-
+ pyme/setup.py          |   4 +-
+ 9 files changed, 116 insertions(+), 100 deletions(-)
+
+commit d644383a76e9f83bc2d426628319e3c4a989dc2d
+Author: belyi <devnull@localhost>
+Date:   Sat Dec 17 01:34:53 2005 +0000
+
+    Put all constants into pyme.constants package to avoid stepping on python
+    reserved words.
+    Add build rules for Mingw32 and Cygwin on Windows. Rules for Mingw under
+    Debian are still to come.
+    Fixed a small bug in pygpa.py example.
+
+ pyme/Makefile                   | 11 ++++++++---
+ pyme/examples/pygpa.py          |  3 ++-
+ pyme/pyme/__init__.py           |  2 +-
+ pyme/pyme/constants/__init__.py |  3 +++
+ pyme/setup.py                   | 42 ++++++++++++++++++++++++++++++++++++-----
+ 5 files changed, 51 insertions(+), 10 deletions(-)
+
+commit 89eb370fcaa8adc9d219eadbaa579dde7bf06329
+Author: belyi <devnull@localhost>
+Date:   Mon Aug 1 03:08:32 2005 +0000
+
+    Imported changes provided by Joost van Baal:
+    Use dh_python in debian/rules and change the Section pyme belongs to from
+    'libs' to 'python'.
+
+ pyme/debian/control | 6 +++---
+ pyme/debian/rules   | 2 ++
+ 2 files changed, 5 insertions(+), 3 deletions(-)
+
+commit ad76d10c2a77b45b7459c62131279e946b860891
+Author: belyi <devnull@localhost>
+Date:   Fri Jun 10 03:01:22 2005 +0000
+
+    Update 'docs' rule in Makefile to build packages first to ensure that
+    documentation is build for the current version of pyme and not for the
+    installed one.
+
+    Added 'callbacks' into the list of visible pyme modules (__all__ var.)
+
+    Slightly updated INSTALL file.
+
+ pyme/INSTALL          | 11 ++++++++---
+ pyme/Makefile         |  4 ++--
+ pyme/pyme/__init__.py |  2 +-
+ 3 files changed, 11 insertions(+), 6 deletions(-)
+
+commit 2fe1a81e00721698bfa6850b3db2eb85e43d1724
+Author: belyi <devnull@localhost>
+Date:   Wed Jun 8 16:16:18 2005 +0000
+
+    Update pyme documentation to remove dead links to pyme.gpgme.html and
+    pyme._gpgme.html
+    Added reference to the installed GPGME and PyMe documentation to the head
+    web page.
+    Updated Makefile to install all *.html files and to clean *~ files in all
+    subdirectories
+
+ pyme-web/Makefile                     | 10 ++++++----
+ pyme-web/doc/pyme/index.html          |  8 +++-----
+ pyme-web/doc/pyme/pyme.callbacks.html |  8 --------
+ pyme-web/doc/pyme/pyme.core.html      |  1 -
+ pyme-web/doc/pyme/pyme.errors.html    |  8 --------
+ pyme-web/doc/pyme/pyme.html           |  8 +++-----
+ pyme-web/doc/pyme/pyme.util.html      |  8 --------
+ pyme-web/index.html                   |  9 +++++++--
+ 8 files changed, 19 insertions(+), 41 deletions(-)
+
+commit 6aa34cce4ea0099e50b4936dfee59778157b8ca8
+Author: belyi <devnull@localhost>
+Date:   Wed Jun 8 15:18:20 2005 +0000
+
+    Added pyme and gpgme documentation.
+
+ pyme-web/doc/gpgme/gpgme.html                      | 251 ++++++++
+ pyme-web/doc/gpgme/gpgme_1.html                    |  76 +++
+ pyme-web/doc/gpgme/gpgme_10.html                   |  61 ++
+ pyme-web/doc/gpgme/gpgme_11.html                   | 130 ++++
+ pyme-web/doc/gpgme/gpgme_12.html                   |  82 +++
+ pyme-web/doc/gpgme/gpgme_13.html                   | 130 ++++
+ pyme-web/doc/gpgme/gpgme_14.html                   | 108 ++++
+ pyme-web/doc/gpgme/gpgme_15.html                   |  69 +++
+ pyme-web/doc/gpgme/gpgme_16.html                   | 169 +++++
+ pyme-web/doc/gpgme/gpgme_17.html                   |  63 ++
+ pyme-web/doc/gpgme/gpgme_18.html                   |  63 ++
+ pyme-web/doc/gpgme/gpgme_19.html                   |  66 ++
+ pyme-web/doc/gpgme/gpgme_2.html                    |  79 +++
+ pyme-web/doc/gpgme/gpgme_20.html                   | 120 ++++
+ pyme-web/doc/gpgme/gpgme_21.html                   | 102 +++
+ pyme-web/doc/gpgme/gpgme_22.html                   | 108 ++++
+ pyme-web/doc/gpgme/gpgme_23.html                   | 237 +++++++
+ pyme-web/doc/gpgme/gpgme_24.html                   | 154 +++++
+ pyme-web/doc/gpgme/gpgme_25.html                   | 248 ++++++++
+ pyme-web/doc/gpgme/gpgme_26.html                   | 107 ++++
+ pyme-web/doc/gpgme/gpgme_27.html                   |  80 +++
+ pyme-web/doc/gpgme/gpgme_28.html                   |  67 ++
+ pyme-web/doc/gpgme/gpgme_29.html                   | 164 +++++
+ pyme-web/doc/gpgme/gpgme_3.html                    |  86 +++
+ pyme-web/doc/gpgme/gpgme_30.html                   | 106 ++++
+ pyme-web/doc/gpgme/gpgme_31.html                   | 232 +++++++
+ pyme-web/doc/gpgme/gpgme_32.html                   |  85 +++
+ pyme-web/doc/gpgme/gpgme_33.html                   | 223 +++++++
+ pyme-web/doc/gpgme/gpgme_34.html                   |  83 +++
+ pyme-web/doc/gpgme/gpgme_35.html                   |  70 +++
+ pyme-web/doc/gpgme/gpgme_36.html                   |  63 ++
+ pyme-web/doc/gpgme/gpgme_37.html                   |  66 ++
+ pyme-web/doc/gpgme/gpgme_38.html                   |  86 +++
+ pyme-web/doc/gpgme/gpgme_39.html                   |  79 +++
+ pyme-web/doc/gpgme/gpgme_4.html                    |  83 +++
+ pyme-web/doc/gpgme/gpgme_40.html                   |  89 +++
+ pyme-web/doc/gpgme/gpgme_41.html                   |  99 +++
+ pyme-web/doc/gpgme/gpgme_42.html                   | 144 +++++
+ pyme-web/doc/gpgme/gpgme_43.html                   | 152 +++++
+ pyme-web/doc/gpgme/gpgme_44.html                   | 112 ++++
+ pyme-web/doc/gpgme/gpgme_45.html                   | 101 +++
+ pyme-web/doc/gpgme/gpgme_46.html                   | 459 ++++++++++++++
+ pyme-web/doc/gpgme/gpgme_47.html                   | 292 +++++++++
+ pyme-web/doc/gpgme/gpgme_48.html                   | 363 +++++++++++
+ pyme-web/doc/gpgme/gpgme_49.html                   | 209 +++++++
+ pyme-web/doc/gpgme/gpgme_5.html                    |  74 +++
+ pyme-web/doc/gpgme/gpgme_50.html                   |  88 +++
+ pyme-web/doc/gpgme/gpgme_51.html                   | 208 +++++++
+ pyme-web/doc/gpgme/gpgme_52.html                   | 154 +++++
+ pyme-web/doc/gpgme/gpgme_53.html                   | 291 +++++++++
+ pyme-web/doc/gpgme/gpgme_54.html                   |  91 +++
+ pyme-web/doc/gpgme/gpgme_55.html                   | 107 ++++
+ pyme-web/doc/gpgme/gpgme_56.html                   | 140 +++++
+ pyme-web/doc/gpgme/gpgme_57.html                   | 106 ++++
+ pyme-web/doc/gpgme/gpgme_58.html                   |  89 +++
+ pyme-web/doc/gpgme/gpgme_59.html                   |  97 +++
+ pyme-web/doc/gpgme/gpgme_6.html                    |  77 +++
+ pyme-web/doc/gpgme/gpgme_60.html                   | 142 +++++
+ pyme-web/doc/gpgme/gpgme_61.html                   | 626 +++++++++++++++++++
+ pyme-web/doc/gpgme/gpgme_62.html                   | 107 ++++
+ pyme-web/doc/gpgme/gpgme_63.html                   |  67 ++
+ pyme-web/doc/gpgme/gpgme_64.html                   |  95 +++
+ pyme-web/doc/gpgme/gpgme_65.html                   | 233 +++++++
+ pyme-web/doc/gpgme/gpgme_66.html                   |  65 ++
+ pyme-web/doc/gpgme/gpgme_67.html                   | 220 +++++++
+ pyme-web/doc/gpgme/gpgme_68.html                   |  75 +++
+ pyme-web/doc/gpgme/gpgme_69.html                   | 119 ++++
+ pyme-web/doc/gpgme/gpgme_7.html                    | 123 ++++
+ pyme-web/doc/gpgme/gpgme_70.html                   | 107 ++++
+ pyme-web/doc/gpgme/gpgme_71.html                   | 218 +++++++
+ pyme-web/doc/gpgme/gpgme_72.html                   | 134 ++++
+ pyme-web/doc/gpgme/gpgme_73.html                   | 299 +++++++++
+ pyme-web/doc/gpgme/gpgme_74.html                   | 103 ++++
+ pyme-web/doc/gpgme/gpgme_75.html                   | 104 ++++
+ pyme-web/doc/gpgme/gpgme_76.html                   | 118 ++++
+ pyme-web/doc/gpgme/gpgme_77.html                   |  95 +++
+ pyme-web/doc/gpgme/gpgme_78.html                   |  71 +++
+ pyme-web/doc/gpgme/gpgme_79.html                   | 686 +++++++++++++++++++++
+ pyme-web/doc/gpgme/gpgme_8.html                    | 155 +++++
+ pyme-web/doc/gpgme/gpgme_80.html                   | 120 ++++
+ pyme-web/doc/gpgme/gpgme_81.html                   | 278 +++++++++
+ pyme-web/doc/gpgme/gpgme_82.html                   | 272 ++++++++
+ pyme-web/doc/gpgme/gpgme_83.html                   | 180 ++++++
+ pyme-web/doc/gpgme/gpgme_84.html                   |  99 +++
+ pyme-web/doc/gpgme/gpgme_9.html                    | 104 ++++
+ pyme-web/doc/gpgme/gpgme_abt.html                  | 206 +++++++
+ pyme-web/doc/gpgme/gpgme_fot.html                  |  53 ++
+ pyme-web/doc/gpgme/gpgme_ovr.html                  |  68 ++
+ pyme-web/doc/gpgme/gpgme_toc.html                  | 247 ++++++++
+ pyme-web/doc/gpgme/index.html                      | 251 ++++++++
+ pyme-web/doc/pyme/index.html                       | 166 +++++
+ pyme-web/doc/pyme/pyme.callbacks.html              |  50 ++
+ .../doc/pyme/pyme.constants.data.encoding.html     |  48 ++
+ pyme-web/doc/pyme/pyme.constants.data.html         |  29 +
+ pyme-web/doc/pyme/pyme.constants.event.html        |  48 ++
+ pyme-web/doc/pyme/pyme.constants.html              |  39 ++
+ pyme-web/doc/pyme/pyme.constants.import.html       |  49 ++
+ pyme-web/doc/pyme/pyme.constants.keylist.html      |  29 +
+ pyme-web/doc/pyme/pyme.constants.keylist.mode.html |  48 ++
+ pyme-web/doc/pyme/pyme.constants.md.html           |  58 ++
+ pyme-web/doc/pyme/pyme.constants.pk.html           |  50 ++
+ pyme-web/doc/pyme/pyme.constants.protocol.html     |  46 ++
+ pyme-web/doc/pyme/pyme.constants.sig.html          |  29 +
+ pyme-web/doc/pyme/pyme.constants.sig.mode.html     |  47 ++
+ pyme-web/doc/pyme/pyme.constants.sigsum.html       |  55 ++
+ pyme-web/doc/pyme/pyme.constants.status.html       | 117 ++++
+ pyme-web/doc/pyme/pyme.constants.validity.html     |  50 ++
+ pyme-web/doc/pyme/pyme.core.html                   | 254 ++++++++
+ pyme-web/doc/pyme/pyme.errors.html                 |  90 +++
+ pyme-web/doc/pyme/pyme.html                        | 166 +++++
+ pyme-web/doc/pyme/pyme.util.html                   |  78 +++
+ pyme-web/doc/pyme/pyme.version.html                |  37 ++
+ pyme-web/index.html                                |   6 +-
+ 113 files changed, 14966 insertions(+), 1 deletion(-)
+
+commit 2d6fe54479f042644f7b0f3d2fe35877d2056144
+Author: belyi <devnull@localhost>
+Date:   Thu May 19 02:06:09 2005 +0000
+
+    Added INSTALL file.
+
+ pyme/INSTALL | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+commit d6892fff0c3cedf41dba4c25ab8608e7f2bc039c
+Author: belyi <devnull@localhost>
+Date:   Tue May 17 16:49:28 2005 +0000
+
+    Update copyright note on simple.py
+
+ pyme/examples/simple.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+commit c2cd9cdf5995843aad7b200b929db2969effc9d2
+Author: belyi <devnull@localhost>
+Date:   Tue May 17 15:03:58 2005 +0000
+
+    Update simple.py to catch errors.
+
+ pyme/examples/simple.py | 17 +++++++++++------
+ 1 file changed, 11 insertions(+), 6 deletions(-)
+
+commit eaedae7c6a0ea993caab067efe781a59b6769c44
+Author: belyi <devnull@localhost>
+Date:   Tue May 17 01:18:23 2005 +0000
+
+    Added 'PYTHON = python' into Makefile for bug #1199122
+
+ pyme/Makefile               | 1 +
+ pyme/examples/signverify.py | 1 +
+ 2 files changed, 2 insertions(+)
+
+commit 56fd244bb2636a4d58629899ea3cde1d96428198
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 27 21:37:06 2005 +0000
+
+    Added pygpa example.
+
+ pyme/debian/changelog     |    3 +-
+ pyme/examples/pygpa.glade | 5546 +++++++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/pygpa.py    | 1459 ++++++++++++
+ 3 files changed, 7007 insertions(+), 1 deletion(-)
+
+commit 2d9a2a91a59ac3fee5410c953b7e0859e9e7cd35
+Author: belyi <devnull@localhost>
+Date:   Thu Apr 21 15:17:51 2005 +0000
+
+    Change version to 0.7.0 due to the change in license.
+
+ pyme/debian/changelog | 2 +-
+ pyme/pyme/version.py  | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+commit 94e34e38d742f145385bd235825b6ba1e30d8339
+Author: belyi <devnull@localhost>
+Date:   Thu Apr 21 03:53:12 2005 +0000
+
+    Changed license on PyMe from GPL to LGPL.
+    PyMe examples keep GPL license.
+
+ pyme/COPYING.LESSER                  | 510 +++++++++++++++++++++++++++++++++++
+ pyme/Makefile                        |  20 +-
+ pyme/debian/changelog                |   4 +-
+ pyme/debian/copyright                |  22 +-
+ pyme/gpgme-h-clean.py                |  16 ++
+ pyme/gpgme.i                         |  20 +-
+ pyme/helpers.c                       |  20 +-
+ pyme/helpers.h                       |  20 +-
+ pyme/pyme/__init__.py                |  20 +-
+ pyme/pyme/callbacks.py               |  20 +-
+ pyme/pyme/constants/data/encoding.py |  20 +-
+ pyme/pyme/constants/event.py         |  20 +-
+ pyme/pyme/constants/import.py        |  20 +-
+ pyme/pyme/constants/keylist/mode.py  |  20 +-
+ pyme/pyme/constants/md.py            |  20 +-
+ pyme/pyme/constants/pk.py            |  20 +-
+ pyme/pyme/constants/protocol.py      |  20 +-
+ pyme/pyme/constants/sig/mode.py      |  20 +-
+ pyme/pyme/constants/sigsum.py        |  20 +-
+ pyme/pyme/constants/status.py        |  20 +-
+ pyme/pyme/constants/validity.py      |  20 +-
+ pyme/pyme/core.py                    |  20 +-
+ pyme/pyme/errors.py                  |  20 +-
+ pyme/pyme/util.py                    |  20 +-
+ pyme/pyme/version.py                 |  22 +-
+ pyme/setup.py                        |  20 +-
+ 26 files changed, 761 insertions(+), 233 deletions(-)
+
+commit 0d8aa0f6335cb1506a37085095ed45173b099a02
+Author: belyi <devnull@localhost>
+Date:   Tue Apr 19 01:46:06 2005 +0000
+
+    Added __hash__ and __eq__ methods to GpgmeWrapper to allow both Context()
+    and Data() to be used as a dictionary key.
+    Changed core.wait() function to always return a tuple. On timeout now it
+    returns (0, None) instead of just None. Plus, return context is now a
+    Context() object instead of a wrapper return by underlying gpgme.
+
+ pyme/helpers.c    |  1 -
+ pyme/pyme/core.py | 25 +++++++++++++++----------
+ pyme/pyme/util.py |  9 +++++++++
+ 3 files changed, 24 insertions(+), 11 deletions(-)
+
+commit 63ff6d10637be1dcbcd78c939ac1ef1ac30b1024
+Author: belyi <devnull@localhost>
+Date:   Wed Apr 6 04:58:40 2005 +0000
+
+    Made hook parameter optional in passphrase_cb and progress_cb.
+    Allowed None for callbacks to unset ones set previously.
+    Removed cleanup of exception in callbacks - now just retrieve the error code.
+    Added prev_bad parameter in passphrase_cb since it can be used in
+    change password protocols.
+    Updated examples to follow new sets of arguments in callbacks
+    Updated op_edit to check if passed key is None (otherwise gpgme dumps core)
+    God rid of annoying warning "function declaration isn't a prototype" in
+    helpers.c and helpers.h by changing from () to (void) list of arguments.
+
+ pyme/debian/changelog       | 10 +++++---
+ pyme/examples/signverify.py |  2 +-
+ pyme/examples/t-edit.py     |  2 +-
+ pyme/gpgme.i                | 18 +++++++++-----
+ pyme/helpers.c              | 60 ++++++++++++++++++++++++++++++---------------
+ pyme/helpers.h              |  4 +--
+ pyme/pyme/callbacks.py      |  6 +++--
+ pyme/pyme/core.py           | 47 +++++++++++++++++++++--------------
+ pyme/pyme/errors.py         |  2 +-
+ 9 files changed, 96 insertions(+), 55 deletions(-)
+
+commit 8f0ab8138c7aa190936376ccbbf33bb09c64d6f1
+Author: belyi <devnull@localhost>
+Date:   Thu Mar 31 23:50:59 2005 +0000
+
+    Added exception handling in passphrase_cb and edit_cb. If GPGMEError
+    exception is thrown in those callbacks it will be converted into its
+    core representation and return as an error code to the caller.
+    On all other exceptions error code will be GPG_ERR_GENERAL.
+
+ pyme/Makefile         |  1 +
+ pyme/debian/changelog |  8 ++++++++
+ pyme/gpgme.i          | 20 ++++++++++++++------
+ pyme/helpers.c        | 51 +++++++++++++++++++++++++++++++++++++++++++++------
+ pyme/helpers.h        |  3 +++
+ 5 files changed, 71 insertions(+), 12 deletions(-)
+
+commit 9903d1fb11231e7e3d920e58d1ecb674c5988b07
+Author: belyi <devnull@localhost>
+Date:   Thu Mar 31 05:12:15 2005 +0000
+
+    Remove workaround from Context.wait() method since the bug report and
+    patch fixing gpgme_wait's behavior is sent to GPMGE developers already.
+    Added errorcheck into op_edit() so that it can report an error.
+
+ pyme/pyme/core.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+commit 45e8a5f4e13d3ca797ec3b0037242874a6be5562
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 26 19:44:18 2005 +0000
+
+    Updated verion number to 0.6.2 in version.py
+    Added examples/*.glade files into documentation package.
+
+ pyme/debian/examples | 1 +
+ pyme/pyme/version.py | 2 +-
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+commit 270b87bb40e180cb6e8f1de9a0e8161525ffa4ab
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 26 19:31:14 2005 +0000
+
+    Updated debian/changelog regarding PyGtkGpgKeys example and a fix in errors.
+
+ pyme/debian/changelog | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+commit ea4682009a506db91e5174ffd038fe7e4406b591
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 26 19:25:36 2005 +0000
+
+    Added handling of right mouse button click.
+    Changed reporting a string instead of a number on key generation failure.
+
+ pyme/examples/PyGtkGpgKeys.glade |  2 ++
+ pyme/examples/PyGtkGpgKeys.py    | 30 +++++++++++++++++++++++++++---
+ 2 files changed, 29 insertions(+), 3 deletions(-)
+
+commit f65ad1a703d0098a3204fb8527a54d253e5847e7
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 26 18:11:11 2005 +0000
+
+    Added another column indicating if a key has a secret part.
+    Automated generation of the View menu from the view field of the KeyColumn
+    class.
+
+ pyme/examples/PyGtkGpgKeys.glade | 93 ++--------------------------------------
+ pyme/examples/PyGtkGpgKeys.py    | 74 +++++++++++++++++---------------
+ 2 files changed, 44 insertions(+), 123 deletions(-)
+
+commit b54e83a7a7a5785502f3c7e8b95f15e23b40e65a
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 26 16:45:13 2005 +0000
+
+    Small change to the way gtk.TreeModel object is used.
+
+ pyme/examples/PyGtkGpgKeys.py | 21 ++++++++++-----------
+ 1 file changed, 10 insertions(+), 11 deletions(-)
+
+commit 7078db75cef4c1fd70cf03e37172bdb4f933fd1b
+Author: belyi <devnull@localhost>
+Date:   Fri Mar 25 23:33:06 2005 +0000
+
+    Use more comprehansible error reporting since gpgme_strerror_r returns None
+    all the time.
+
+ pyme/pyme/errors.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+commit 151213f4344d9984975721440af07de09e3df61c
+Author: belyi <devnull@localhost>
+Date:   Fri Mar 25 04:30:17 2005 +0000
+
+    Improved PyGtkGpgKeys example to manage owner_trust on keys.
+    Added another example inter-edit.py which is just a hepler to write
+    scripts for Context.op_edit() command.
+
+ pyme/examples/PyGtkGpgKeys.glade | 78 ++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/PyGtkGpgKeys.py    | 68 +++++++++++++++++++++++++++++++----
+ pyme/examples/inter-edit.py      | 54 ++++++++++++++++++++++++++++
+ pyme/examples/t-edit.py          | 18 ++++++++++
+ 4 files changed, 212 insertions(+), 6 deletions(-)
+
+commit fc7235af217bcee5231ce7fbd7f234712d5ad3b0
+Author: belyi <devnull@localhost>
+Date:   Fri Mar 25 00:30:39 2005 +0000
+
+    Updated PyGtkGpgKeys example to include import, export and reload
+    functionality. Also added ability to remove number of keys simultanously.
+    Rearanged how KeyColumn is used to avoid unnecessary sorts and duplication
+    of information in different parts of the code.
+
+ pyme/examples/PyGtkGpgKeys.glade |  86 +++++++++-
+ pyme/examples/PyGtkGpgKeys.py    | 332 ++++++++++++++++++++++++++++-----------
+ 2 files changed, 325 insertions(+), 93 deletions(-)
+
+commit 9f65749ccb1b7cab562e19c03f4371d5f7d94912
+Author: belyi <devnull@localhost>
+Date:   Thu Mar 24 05:51:03 2005 +0000
+
+    Added example of PyGTK+ and PyMe integration.
+    For now it does only simple things - listing, deleting, and generating keys.
+
+ pyme/examples/PyGtkGpgKeys.glade  | 1321 +++++++++++++++++++++++++++++++++++++
+ pyme/examples/PyGtkGpgKeys.gladep |    8 +
+ pyme/examples/PyGtkGpgKeys.py     |  424 ++++++++++++
+ 3 files changed, 1753 insertions(+)
+
+commit 59e23f32c3b46413c9ec09e23e1a385a110fb103
+Author: belyi <devnull@localhost>
+Date:   Thu Mar 24 05:44:58 2005 +0000
+
+    Added wait method Context class which handles asynchornous calls a little
+    bit better than the one generated by SWIG.
+
+ pyme/debian/changelog |  7 +++++++
+ pyme/gpgme.i          |  1 +
+ pyme/pyme/core.py     | 40 ++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 48 insertions(+)
+
+commit 4c1b5259e4985df2cba0ae4fc09f12cd94603a75
+Author: belyi <devnull@localhost>
+Date:   Tue Mar 22 18:29:31 2005 +0000
+
+    Added correct handling of Context.op_edit() method.
+    Added example/t-edit.py showing usage for this method.
+    Output of this example should match output of the  tests/gpg/t-edit
+    from the GPGME test suite.
+    Remove unused static function from helpers.c
+
+ pyme/examples/t-edit.py | 38 ++++++++++++++++++++++++++++++++++++++
+ pyme/gpgme.i            | 36 ++++++++++++++++++++++++++++++++++++
+ pyme/helpers.c          | 36 ------------------------------------
+ pyme/pyme/core.py       |  5 ++++-
+ 4 files changed, 78 insertions(+), 37 deletions(-)
+
+commit dc587e215283bfef2dd594f86a7b2945f74f5155
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 19 01:43:59 2005 +0000
+
+    Update changelog to include note about deprecated function in 0.6.1 release
+
+ pyme/debian/changelog           | 3 ++-
+ pyme/examples/encrypt-to-all.py | 3 +--
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+commit 86de4b3ad777f980ccf7ba3462c85bbe1787d1fd
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 19 01:40:07 2005 +0000
+
+    Remove deprecated functions from helpers.[ch]
+    Use gpgme-h-clean.py to remove deprecated functions and typedefs from
+    the GPGME header file. This will reduce the number of unused methods.
+
+ pyme/Makefile         |  4 ++--
+ pyme/gpgme-h-clean.py | 26 ++++++++++++++++++++++++++
+ pyme/helpers.c        |  8 --------
+ pyme/helpers.h        |  2 --
+ 4 files changed, 28 insertions(+), 12 deletions(-)
+
+commit 2483efcbd0d73c628c4d7717928a766c3b58f0aa
+Author: belyi <devnull@localhost>
+Date:   Fri Mar 18 22:15:52 2005 +0000
+
+    Update copyright and author values in pyme/version.py
+    Create rules to build distribution files - one full and one without
+    debian bits.
+
+ pyme/Makefile        | 28 ++++++++++++++++++++++------
+ pyme/pyme/version.py | 12 ++++++------
+ 2 files changed, 28 insertions(+), 12 deletions(-)
+
+commit 168593285380f5a7805f3dd08657d429a72d3621
+Author: belyi <devnull@localhost>
+Date:   Fri Mar 18 19:09:33 2005 +0000
+
+    Added package building for python2.4
+
+    Updated copyright notes to include myslef and avoid confusion who's the
+    maintainer. In John's own words: "I'd prefer to just step out of the picture".
+    Jonh's copyright notice left intact.
+
+ pyme/Makefile                        |  6 +++---
+ pyme/debian/changelog                |  7 +++++++
+ pyme/debian/control                  | 30 +++++++++++++++++++++++++++---
+ pyme/debian/copyright                | 10 ++++------
+ pyme/debian/rules                    |  4 ++++
+ pyme/debian/setup.cfg-2.4            |  8 ++++++++
+ pyme/examples/genkey.py              |  4 ++--
+ pyme/gpgme.i                         |  4 ++--
+ pyme/helpers.c                       |  4 ++--
+ pyme/helpers.h                       |  4 ++--
+ pyme/pyme/__init__.py                |  4 ++--
+ pyme/pyme/callbacks.py               |  4 ++--
+ pyme/pyme/constants/data/encoding.py |  4 ++--
+ pyme/pyme/constants/event.py         |  4 ++--
+ pyme/pyme/constants/import.py        |  4 ++--
+ pyme/pyme/constants/keylist/mode.py  |  4 ++--
+ pyme/pyme/constants/md.py            |  4 ++--
+ pyme/pyme/constants/pk.py            |  4 ++--
+ pyme/pyme/constants/protocol.py      |  4 ++--
+ pyme/pyme/constants/sig/mode.py      |  4 ++--
+ pyme/pyme/constants/sigsum.py        |  4 ++--
+ pyme/pyme/constants/status.py        |  4 ++--
+ pyme/pyme/constants/validity.py      |  4 ++--
+ pyme/pyme/core.py                    |  4 ++--
+ pyme/pyme/errors.py                  |  4 ++--
+ pyme/pyme/util.py                    |  4 ++--
+ pyme/pyme/version.py                 |  2 +-
+ pyme/setup.py                        |  3 ++-
+ 28 files changed, 96 insertions(+), 54 deletions(-)
+
+commit 6dbbb252771133724b2879ed6d767cd708196dae
+Author: belyi <devnull@localhost>
+Date:   Fri Mar 18 18:04:35 2005 +0000
+
+    Remove the note about gpgme.i to be generated - it's been the primary source
+    for some time.
+
+ pyme/gpgme.i | 6 ------
+ 1 file changed, 6 deletions(-)
+
+commit 9d449fa4889c6bda6d14583c0625b8d5c4ffe759
+Author: belyi <devnull@localhost>
+Date:   Fri May 7 18:31:22 2004 +0000
+
+    Added my copyright in genkey.py since there's enough changes made.
+    Updated signverify to use only keys generated by genkey.py, to check
+      that keys added to singers are able to sign and to check that the
+      list of signers is not empty. The last check is necessary to prevent
+      signing with the key of the user running signverify.py script.
+    Added delkey.py script to delete keys generated by genkey.py
+    Added exportimport.py example for key export/import.
+
+ pyme/examples/delkey.py       | 29 +++++++++++++++++
+ pyme/examples/exportimport.py | 76 +++++++++++++++++++++++++++++++++++++++++++
+ pyme/examples/genkey.py       |  6 ++--
+ pyme/examples/signverify.py   | 18 ++++++----
+ 4 files changed, 119 insertions(+), 10 deletions(-)
+
+commit df98c8d28245ad2c14b0ab50fc8f8932853bec8b
+Author: belyi <devnull@localhost>
+Date:   Tue May 4 17:34:15 2004 +0000
+
+    Added examples/signverify.py for unattended sing/verify.
+    Updated examples/genkey.py to work correctly.
+    Updated gpgme.i to allow None as a value for gpgme_data_t
+
+ pyme/examples/genkey.py     | 14 ++-------
+ pyme/examples/signverify.py | 72 +++++++++++++++++++++++++++++++++++++++++++++
+ pyme/gpgme.i                | 21 ++++++++-----
+ 3 files changed, 87 insertions(+), 20 deletions(-)
+
+commit ba45931abf530ab89ead46d7233ff1b62b629a18
+Author: belyi <devnull@localhost>
+Date:   Thu Apr 8 16:15:09 2004 +0000
+
+    Ensure that we support only python2.2 and up. :-)
+    Use generators in core.Context class which makes pyme.aux obsolete
+    Remove importing future nested_scopes since they are standart starting
+    with python2.2
+
+ pyme/pyme/__init__.py |  5 ++---
+ pyme/pyme/aux.py      | 56 ---------------------------------------------------
+ pyme/pyme/core.py     | 15 +++++++++++---
+ pyme/pyme/errors.py   |  1 -
+ pyme/pyme/util.py     |  2 +-
+ 5 files changed, 15 insertions(+), 64 deletions(-)
+
+commit 4e9be5a55ecffa4da7ad5c192cc892eddaaa9586
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 21 03:53:30 2004 +0000
+
+    Small change to index.html
+    Added clean: rule to the Makefile
+
+ pyme-web/Makefile   | 3 +++
+ pyme-web/index.html | 6 +++---
+ 2 files changed, 6 insertions(+), 3 deletions(-)
+
+commit 2efb95176f4edf56ed61c9ac0c3aa09c56534df0
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 21 03:00:32 2004 +0000
+
+    Added Makefile rules for pyme module installation.
+
+ pyme/Makefile | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+commit 2b83d5d8b513029cc3e54f2fa502ccc85618104b
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 21 02:29:54 2004 +0000
+
+    Decorative change.
+
+ pyme/pyme/aux.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit e3478015d763a036c1d806ae01433fce59712204
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 21 02:25:55 2004 +0000
+
+    Added RCS Id: tags
+
+ pyme/Makefile                           | 1 +
+ pyme/examples/encrypt-to-all.py         | 3 ++-
+ pyme/examples/genkey.py                 | 3 ++-
+ pyme/examples/sign.py                   | 3 ++-
+ pyme/examples/simple.py                 | 3 ++-
+ pyme/gpgme.i                            | 1 +
+ pyme/helpers.c                          | 1 +
+ pyme/helpers.h                          | 1 +
+ pyme/pyme/__init__.py                   | 1 +
+ pyme/pyme/aux.py                        | 1 +
+ pyme/pyme/callbacks.py                  | 1 +
+ pyme/pyme/constants/__init__.py         | 2 ++
+ pyme/pyme/constants/data/__init__.py    | 2 ++
+ pyme/pyme/constants/data/encoding.py    | 1 +
+ pyme/pyme/constants/event.py            | 1 +
+ pyme/pyme/constants/import.py           | 1 +
+ pyme/pyme/constants/keylist/__init__.py | 2 ++
+ pyme/pyme/constants/keylist/mode.py     | 1 +
+ pyme/pyme/constants/md.py               | 1 +
+ pyme/pyme/constants/pk.py               | 1 +
+ pyme/pyme/constants/protocol.py         | 1 +
+ pyme/pyme/constants/sig/__init__.py     | 2 ++
+ pyme/pyme/constants/sig/mode.py         | 1 +
+ pyme/pyme/constants/sigsum.py           | 1 +
+ pyme/pyme/constants/status.py           | 1 +
+ pyme/pyme/constants/validity.py         | 1 +
+ pyme/pyme/core.py                       | 1 +
+ pyme/pyme/errors.py                     | 1 +
+ pyme/pyme/util.py                       | 1 +
+ pyme/pyme/version.py                    | 2 ++
+ 30 files changed, 39 insertions(+), 4 deletions(-)
+
+commit b3b3712645332c5bc3e8d9d557aab21d48ff0f86
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 21 02:07:36 2004 +0000
+
+    Added Id: RCS tags to all files.
+
+ pyme-web/Makefile   | 2 ++
+ pyme-web/index.html | 3 ++-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+commit 6aea2426beaaa8c43e6f2310a37a2737c0c3a1b5
+Author: belyi <devnull@localhost>
+Date:   Sun Mar 21 01:50:55 2004 +0000
+
+    Update example on the init pyme.html page to match simple.py example.
+    Fix core.py to use getcode() instead of getvalue() method of the exception.
+
+ pyme/pyme/__init__.py | 22 ++++++++++++++--------
+ pyme/pyme/core.py     |  4 ++--
+ 2 files changed, 16 insertions(+), 10 deletions(-)
+
+commit dee337455ffd624d3f83e1c159c4bb2cefc692c9
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 20:32:29 2004 +0000
+
+    Added Makefile to simplify publishing web files.
+
+ pyme-web/Makefile | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+commit af7129baa8260697d85c2ddb434562e8a80b62d8
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 20:15:53 2004 +0000
+
+    Added minimum of formating and SF icon.
+
+ pyme-web/index.html | 18 +++++++++++-------
+ 1 file changed, 11 insertions(+), 7 deletions(-)
+
+commit 2e64dcbf99cee796b51667b04d8961e390edde87
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 18:30:09 2004 +0000
+
+    Initial revision
+
+ pyme-web/index.html | 33 +++++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+commit 1c51644b3d0b6611422d971758e35f303d2ad5df
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 05:10:46 2004 +0000
+
+    Update examples and package information on the initial pyme doc page.
+
+ pyme/pyme/__init__.py | 27 ++++++++++++---------------
+ 1 file changed, 12 insertions(+), 15 deletions(-)
+
+commit b2d31b0bfbffdff5247d6db4e3c95140cc1b1f19
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 04:47:42 2004 +0000
+
+    Deleted unnecessary files.
+    Updated debian/control to remove dependency on python-xml package since there's
+    none now.
+    Move example files from 'doc' into separate control file.
+    Update debian/rules to build documentation from *.py files and to exclude
+    CVS directories from the installation.
+
+ pyme/Makefile                   |  26 ++-----
+ pyme/debian/control             |   8 +--
+ pyme/debian/docs                |   1 -
+ pyme/debian/ex.package.doc-base |  22 ------
+ pyme/debian/examples            |   1 +
+ pyme/debian/manpage.1.ex        |  60 ----------------
+ pyme/debian/manpage.sgml.ex     | 152 ----------------------------------------
+ pyme/debian/rules               |  12 ++--
+ 8 files changed, 15 insertions(+), 267 deletions(-)
+
+commit 1b517dd9b82a433499b4696b06d94d756cd36e53
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 02:59:15 2004 +0000
+
+    Remove doc/gpgme directory containing GPGME documentation since this belongs
+    to a different project. Need to add reference in our documentation.
+
+ pyme/doc/gpgme/fdl.texi     |  402 ------
+ pyme/doc/gpgme/gpgme.texi   | 3372 -------------------------------------------
+ pyme/doc/gpgme/gpl.texi     |  397 -----
+ pyme/doc/gpgme/version.texi |    4 -
+ 4 files changed, 4175 deletions(-)
+
+commit 95d7d171da115a0fedfe2a4a7e5acc8aa408f673
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 02:45:03 2004 +0000
+
+    Change debian/rules to generate files by swig during build and to cleanup
+    those files on 'clean' rule.
+    Plus, leave generated gpgme_wrap.c in the root directory instead of moving
+    it into subdirectory 'generated'.
+
+ pyme/Makefile     | 8 +++-----
+ pyme/debian/rules | 3 ++-
+ pyme/setup.py     | 2 +-
+ 3 files changed, 6 insertions(+), 7 deletions(-)
+
+commit 545b3d90d445c5c78e8d72b2c1780863e02c789a
+Author: belyi <devnull@localhost>
+Date:   Sat Mar 20 02:18:01 2004 +0000
+
+    Initial revision
+
+ pyme/COPYING                            |  340 ++++
+ pyme/ChangeLog                          |  802 ++++++++
+ pyme/Makefile                           |   79 +
+ pyme/debian/README.Debian               |    6 +
+ pyme/debian/changelog                   |   19 +
+ pyme/debian/control                     |   68 +
+ pyme/debian/copyright                   |   27 +
+ pyme/debian/dirs                        |    2 +
+ pyme/debian/docs                        |    2 +
+ pyme/debian/ex.package.doc-base         |   22 +
+ pyme/debian/manpage.1.ex                |   60 +
+ pyme/debian/manpage.sgml.ex             |  152 ++
+ pyme/debian/postinst.ex                 |   48 +
+ pyme/debian/postrm.ex                   |   38 +
+ pyme/debian/preinst.ex                  |   44 +
+ pyme/debian/prerm.ex                    |   39 +
+ pyme/debian/rules                       |  130 ++
+ pyme/debian/setup.cfg-2.2               |    8 +
+ pyme/debian/setup.cfg-2.3               |    8 +
+ pyme/doc/gpgme/fdl.texi                 |  402 ++++
+ pyme/doc/gpgme/gpgme.texi               | 3372 +++++++++++++++++++++++++++++++
+ pyme/doc/gpgme/gpl.texi                 |  397 ++++
+ pyme/doc/gpgme/version.texi             |    4 +
+ pyme/examples/encrypt-to-all.py         |   63 +
+ pyme/examples/genkey.py                 |   55 +
+ pyme/examples/sign.py                   |   28 +
+ pyme/examples/simple.py                 |   44 +
+ pyme/gpgme.i                            |  191 ++
+ pyme/helpers.c                          |  139 ++
+ pyme/helpers.h                          |   29 +
+ pyme/pyme/__init__.py                   |  134 ++
+ pyme/pyme/aux.py                        |   55 +
+ pyme/pyme/callbacks.py                  |   45 +
+ pyme/pyme/constants/__init__.py         |    2 +
+ pyme/pyme/constants/data/__init__.py    |    2 +
+ pyme/pyme/constants/data/encoding.py    |   19 +
+ pyme/pyme/constants/event.py            |   19 +
+ pyme/pyme/constants/import.py           |   19 +
+ pyme/pyme/constants/keylist/__init__.py |    2 +
+ pyme/pyme/constants/keylist/mode.py     |   19 +
+ pyme/pyme/constants/md.py               |   19 +
+ pyme/pyme/constants/pk.py               |   19 +
+ pyme/pyme/constants/protocol.py         |   19 +
+ pyme/pyme/constants/sig/__init__.py     |    2 +
+ pyme/pyme/constants/sig/mode.py         |   19 +
+ pyme/pyme/constants/sigsum.py           |   19 +
+ pyme/pyme/constants/status.py           |   19 +
+ pyme/pyme/constants/validity.py         |   19 +
+ pyme/pyme/core.py                       |  367 ++++
+ pyme/pyme/errors.py                     |   46 +
+ pyme/pyme/util.py                       |   61 +
+ pyme/pyme/version.py                    |   39 +
+ pyme/setup.py                           |   60 +
+ 53 files changed, 7642 insertions(+)
+
+commit a3d5a442dc713b6c4d6fc4134db5b47e379dc41d
+Author: root <devnull@localhost>
+Date:   Fri Mar 19 14:12:30 2004 +0000
+
+    initial checkin
+
+ CVSROOT/checkoutlist | 13 +++++++++++++
+ CVSROOT/commitinfo   | 15 +++++++++++++++
+ CVSROOT/config       | 21 +++++++++++++++++++++
+ CVSROOT/cvswrappers  | 19 +++++++++++++++++++
+ CVSROOT/editinfo     | 21 +++++++++++++++++++++
+ CVSROOT/loginfo      | 26 ++++++++++++++++++++++++++
+ CVSROOT/modules      | 26 ++++++++++++++++++++++++++
+ CVSROOT/notify       | 12 ++++++++++++
+ CVSROOT/rcsinfo      | 13 +++++++++++++
+ CVSROOT/taginfo      | 20 ++++++++++++++++++++
+ CVSROOT/verifymsg    | 21 +++++++++++++++++++++
+ 11 files changed, 207 insertions(+)
diff --git a/lang/python/doc/rst/gpgme-python-howto.rst b/lang/python/doc/rst/gpgme-python-howto.rst
new file mode 100644 (file)
index 0000000..9181491
--- /dev/null
@@ -0,0 +1,2998 @@
+.. _intro:
+
+Introduction
+============
+
++-----------------------------------+-----------------------------------+
+| Version:                          | 0.1.4                             |
++-----------------------------------+-----------------------------------+
+| GPGME Version:                    | 1.12.0                            |
++-----------------------------------+-----------------------------------+
+| Author:                           | `Ben                              |
+|                                   | McGinnes <https://gnupg.org/peopl |
+|                                   | e/index.html#sec-1-5>`__          |
+|                                   | <ben@gnupg.org>                   |
++-----------------------------------+-----------------------------------+
+| Author GPG Key:                   | DB4724E6FA4286C92B4E55C4321E4E237 |
+|                                   | 3590E5D                           |
++-----------------------------------+-----------------------------------+
+| Language:                         | Australian English, British       |
+|                                   | English                           |
++-----------------------------------+-----------------------------------+
+| xml:lang:                         | en-AU, en-GB, en                  |
++-----------------------------------+-----------------------------------+
+
+This document provides basic instruction in how to use the GPGME Python
+bindings to programmatically leverage the GPGME library.
+
+.. _py2-vs-py3:
+
+Python 2 versus Python 3
+------------------------
+
+Though the GPGME Python bindings themselves provide support for both
+Python 2 and 3, the focus is unequivocally on Python 3 and specifically
+from Python 3.4 and above. As a consequence all the examples and
+instructions in this guide use Python 3 code.
+
+Much of it will work with Python 2, but much of it also deals with
+Python 3 byte literals, particularly when reading and writing data.
+Developers concentrating on Python 2.7, and possibly even 2.6, will need
+to make the appropriate modifications to support the older string and
+unicode types as opposed to bytes.
+
+There are multiple reasons for concentrating on Python 3; some of which
+relate to the immediate integration of these bindings, some of which
+relate to longer term plans for both GPGME and the python bindings and
+some of which relate to the impending EOL period for Python 2.7.
+Essentially, though, there is little value in tying the bindings to a
+version of the language which is a dead end and the advantages offered
+by Python 3 over Python 2 make handling the data types with which GPGME
+deals considerably easier.
+
+.. _howto-python3-examples:
+
+Examples
+--------
+
+All of the examples found in this document can be found as Python 3
+scripts in the ``lang/python/examples/howto`` directory.
+
+Unofficial Drafts
+-----------------
+
+In addition to shipping with each release of GPGME, there is a section
+on locations to read or download `draft editions <#draft-editions>`__ of
+this document from at the end of it. These are unofficial versions
+produced in between major releases.
+
+.. _new-stuff:
+
+What\'s New
+-----------
+
+The most obviously new point for those reading this guide is this
+section on other new things, but that\'s hardly important. Not given all
+the other things which spurred the need for adding this section and its
+subsections.
+
+.. _new-stuff-1-12-0:
+
+New in GPGME 1·12·0
+~~~~~~~~~~~~~~~~~~~
+
+There have been quite a number of additions to GPGME and the Python
+bindings to it since the last release of GPGME with versions 1.11.0 and
+1.11.1 in April, 2018.
+
+The bullet points of new additiions are:
+
+-  an expanded section on `installing <#installation>`__ and
+   `troubleshooting <#snafu>`__ the Python bindings.
+-  The release of Python 3.7.0; which appears to be working just fine
+   with our bindings, in spite of intermittent reports of problems for
+   many other Python projects with that new release.
+-  Python 3.7 has been moved to the head of the specified python
+   versions list in the build process.
+-  In order to fix some other issues, there are certain underlying
+   functions which are more exposed through the
+   `gpg.Context() <#howto-get-context>`__, but ongoing documentation
+   ought to clarify that or otherwise provide the best means of using
+   the bindings. Some additions to ``gpg.core`` and the ``Context()``,
+   however, were intended (see below).
+-  Continuing work in identifying and confirming the cause of
+   oft-reported `problems installing the Python bindings on
+   Windows <#snafu-runtime-not-funtime>`__.
+-  GSOC: Google\'s Surreptitiously Ordered Conscription ... erm ... oh,
+   right; Google\'s Summer of Code. Though there were two hopeful
+   candidates this year; only one ended up involved with the GnuPG
+   Project directly, the other concentrated on an unrelated third party
+   project with closer ties to one of the GNU/Linux distributions than
+   to the GnuPG Project. Thus the Python bindings benefited from GSOC
+   participant Jacob Adams, who added the key\ :sub:`import` function;
+   building on prior work by Tobias Mueller.
+-  Several new methods functions were added to the gpg.Context(),
+   including: `key\ import <#howto-import-key>`__,
+   `key\ export <#howto-export-key>`__,
+   `key\ exportminimal <#howto-export-public-key>`__ and
+   `key\ exportsecret <#howto-export-secret-key>`__.
+-  Importing and exporting examples include versions integrated with
+   Marcel Fest\'s recently released `HKP for
+   Python <https://github.com/Selfnet/hkp4py>`__ module. Some
+   `additional notes on this module <#hkp4py>`__ are included at the end
+   of the HOWTO.
+-  Instructions for dealing with semi-walled garden implementations like
+   ProtonMail are also included. This is intended to make things a
+   little easier when communicating with users of ProtonMail\'s services
+   and should not be construed as an endorsement of said service. The
+   GnuPG Project neither favours, nor disfavours ProtonMail and the
+   majority of this deals with interacting with the ProtonMail
+   keyserver.
+-  Semi-formalised the location where `draft
+   versions <#draft-editions>`__ of this HOWTO may periodically be
+   accessible. This is both for the reference of others and testing the
+   publishing of the document itself. Renamed this file at around the
+   same time.
+-  The Texinfo documentation build configuration has been replicated
+   from the parent project in order to make to maintain consistency with
+   that project (and actually ship with each release).
+-  a reStructuredText (``.rst``) version is also generated for Python
+   developers more used to and comfortable with that format as it is the
+   standard Python documentation format and Python developers may wish
+   to use it with Sphinx. Please note that there has been no testing of
+   the reStructuredText version with Sphinx at all. The reST file was
+   generated by the simple expedient of using
+   `Pandoc <https://pandoc.org/>`__.
+-  Added a new section for `advanced or experimental
+   use <#advanced-use>`__.
+-  Began the advanced use cases with `a section <#cython>`__ on using
+   the module with `Cython <http://cython.org/>`__.
+-  Added a number of new scripts to the ``example/howto/`` directory;
+   some of which may be in advance of their planned sections of the
+   HOWTO (and some are just there because it seemed like a good idea at
+   the time).
+-  Cleaned up a lot of things under the hood.
+
+GPGME Concepts
+==============
+
+.. _gpgme-c-api:
+
+A C API
+-------
+
+Unlike many modern APIs with which programmers will be more familiar
+with these days, the GPGME API is a C API. The API is intended for use
+by C coders who would be able to access its features by including the
+``gpgme.h`` header file with their own C source code and then access its
+functions just as they would any other C headers.
+
+This is a very effective method of gaining complete access to the API
+and in the most efficient manner possible. It does, however, have the
+drawback that it cannot be directly used by other languages without some
+means of providing an interface to those languages. This is where the
+need for bindings in various languages stems.
+
+.. _gpgme-python-bindings:
+
+Python bindings
+---------------
+
+The Python bindings for GPGME provide a higher level means of accessing
+the complete feature set of GPGME itself. It also provides a more
+pythonic means of calling these API functions.
+
+The bindings are generated dynamically with SWIG and the copy of
+``gpgme.h`` generated when GPGME is compiled.
+
+This means that a version of the Python bindings is fundamentally tied
+to the exact same version of GPGME used to generate that copy of
+``gpgme.h``.
+
+.. _gpgme-python-bindings-diffs:
+
+Difference between the Python bindings and other GnuPG Python packages
+----------------------------------------------------------------------
+
+There have been numerous attempts to add GnuPG support to Python over
+the years. Some of the most well known are listed here, along with what
+differentiates them.
+
+.. _diffs-python-gnupg:
+
+The python-gnupg package maintained by Vinay Sajip
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is arguably the most popular means of integrating GPG with Python.
+The package utilises the ``subprocess`` module to implement wrappers for
+the ``gpg`` and ``gpg2`` executables normally invoked on the command
+line (``gpg.exe`` and ``gpg2.exe`` on Windows).
+
+The popularity of this package stemmed from its ease of use and
+capability in providing the most commonly required features.
+
+Unfortunately it has been beset by a number of security issues in the
+past; most of which stemmed from using unsafe methods of accessing the
+command line via the ``subprocess`` calls. While some effort has been
+made over the last two to three years (as of 2018) to mitigate this,
+particularly by no longer providing shell access through those
+subprocess calls, the wrapper is still somewhat limited in the scope of
+its GnuPG features coverage.
+
+The python-gnupg package is available under the MIT license.
+
+.. _diffs-isis-gnupg:
+
+The gnupg package created and maintained by Isis Lovecruft
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In 2015 Isis Lovecruft from the Tor Project forked and then
+re-implemented the python-gnupg package as just gnupg. This new package
+also relied on subprocess to call the ``gpg`` or ``gpg2`` binaries, but
+did so somewhat more securely.
+
+The naming and version numbering selected for this package, however,
+resulted in conflicts with the original python-gnupg and since its
+functions were called in a different manner to python-gnupg, the release
+of this package also resulted in a great deal of consternation when
+people installed what they thought was an upgrade that subsequently
+broke the code relying on it.
+
+The gnupg package is available under the GNU General Public License
+version 3.0 (or any later version).
+
+.. _diffs-pyme:
+
+The PyME package maintained by Martin Albrecht
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This package is the origin of these bindings, though they are somewhat
+different now. For details of when and how the PyME package was folded
+back into GPGME itself see the `Short History <short-history.org>`__
+document. [1]_
+
+The PyME package was first released in 2002 and was also the first
+attempt to implement a low level binding to GPGME. In doing so it
+provided access to considerably more functionality than either the
+``python-gnupg`` or ``gnupg`` packages.
+
+The PyME package is only available for Python 2.6 and 2.7.
+
+Porting the PyME package to Python 3.4 in 2015 is what resulted in it
+being folded into the GPGME project and the current bindings are the end
+result of that effort.
+
+The PyME package is available under the same dual licensing as GPGME
+itself: the GNU General Public License version 2.0 (or any later
+version) and the GNU Lesser General Public License version 2.1 (or any
+later version).
+
+.. _gpgme-python-install:
+
+GPGME Python bindings installation
+==================================
+
+.. _do-not-use-pypi:
+
+No PyPI
+-------
+
+Most third-party Python packages and modules are available and
+distributed through the Python Package Installer, known as PyPI.
+
+Due to the nature of what these bindings are and how they work, it is
+infeasible to install the GPGME Python bindings in the same way.
+
+This is because the bindings use SWIG to dynamically generate C bindings
+against ``gpgme.h`` and ``gpgme.h`` is generated from ``gpgme.h.in`` at
+compile time when GPGME is built from source. Thus to include a package
+in PyPI which actually built correctly would require either statically
+built libraries for every architecture bundled with it or a full
+implementation of C for each architecture.
+
+See the additional notes regarding `CFFI and SWIG <#snafu-cffi>`__ at
+the end of this section for further details.
+
+.. _gpgme-python-requirements:
+
+Requirements
+------------
+
+The GPGME Python bindings only have three requirements:
+
+#. A suitable version of Python 2 or Python 3. With Python 2 that means
+   CPython 2.7 and with Python 3 that means CPython 3.4 or higher.
+#. `SWIG <https://www.swig.org>`__.
+#. GPGME itself. Which also means that all of GPGME\'s dependencies must
+   be installed too.
+
+.. _gpgme-python-recommendations:
+
+Recommended Additions
+~~~~~~~~~~~~~~~~~~~~~
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings. In some cases these
+recommendations refer to which version(s) of CPython to use the bindings
+with, while others refer to third party modules which provide a
+significant advantage in some way.
+
+#. If possible, use Python 3 instead of 2.
+#. Favour a more recent version of Python since even 3.4 is due to reach
+   EOL soon. In production systems and services, Python 3.6 should be
+   robust enough to be relied on.
+#. If possible add the following Python modules which are not part of
+   the standard library:
+   `Requests <http://docs.python-requests.org/en/latest/index.html>`__,
+   `Cython <http://cython.org/>`__ and
+   `hkp4py <https://github.com/Selfnet/hkp4py>`__. Chances are quite
+   high that at least the first one and maybe two of those will already
+   be installed.
+
+Note that, as with Cython, some of the planned additions to the
+`Advanced <#advanced-use>`__ section, will bring with them additional
+requirements. Most of these will be fairly well known and commonly
+installed ones, however, which are in many cases likely to have already
+been installed on many systems or be familiar to Python programmers.
+
+Installation
+------------
+
+Installing the Python bindings is effectively achieved by compiling and
+installing GPGME itself.
+
+Once SWIG is installed with Python and all the dependencies for GPGME
+are installed you only need to confirm that the version(s) of Python you
+want the bindings installed for are in your ``$PATH``.
+
+By default GPGME will attempt to install the bindings for the most
+recent or highest version number of Python 2 and Python 3 it detects in
+``$PATH``. It specifically checks for the ``python`` and ``python3``
+executables first and then checks for specific version numbers.
+
+For Python 2 it checks for these executables in this order: ``python``,
+``python2`` and ``python2.7``.
+
+For Python 3 it checks for these executables in this order: ``python3``,
+``python3.7``, ``python3.6``, ``python3.5`` and ``python3.4``. [2]_
+
+On systems where ``python`` is actually ``python3`` and not ``python2``
+it may be possible that ``python2`` may be overlooked, but there have
+been no reports of that actually occurring as yet.
+
+In the three months or so since the release of Python 3.7.0 there has
+been extensive testing and work with these bindings with no issues
+specifically relating to the new version of Python or any of the new
+features of either the language or the bindings. This has also been the
+case with Python 3.7.1rc1. With that in mind and given the release of
+Python 3.7.1 is scheduled for around the same time as GPGME 1.12.0, the
+order of preferred Python versions has been changed to move Python 3.7
+ahead of Python 3.6.
+
+.. _install-gpgme:
+
+Installing GPGME
+~~~~~~~~~~~~~~~~
+
+See the GPGME ``README`` file for details of how to install GPGME from
+source.
+
+.. _snafu:
+
+Known Issues
+------------
+
+There are a few known issues with the current build process and the
+Python bindings. For the most part these are easily addressed should
+they be encountered.
+
+.. _snafu-a-swig-of-this-builds-character:
+
+Breaking Builds
+~~~~~~~~~~~~~~~
+
+Occasionally when installing GPGME with the Python bindings included it
+may be observed that the ``make`` portion of that process induces a
+large very number of warnings and, eventually errors which end that part
+of the build process. Yet following that with ``make check`` and
+``make install`` appears to work seamlessly.
+
+The cause of this is related to the way SWIG needs to be called to
+dynamically generate the C bindings for GPGME in the first place. So the
+entire process will always produce ``lang/python/python2-gpg/`` and
+``lang/python/python3-gpg/`` directories. These should contain the build
+output generated during compilation, including the complete bindings and
+module installed into ``site-packages``.
+
+Occasionally the errors in the early part or some other conflict (e.g.
+not installing as **root** or **su**) may result in nothing being
+installed to the relevant ``site-packages`` directory and the build
+directory missing a lot of expected files. Even when this occurs, the
+solution is actually quite simple and will always work.
+
+That solution is simply to run the following commands as either the
+**root** user or prepended with ``sudo -H``\  [3]_ in the
+``lang/python/`` directory:
+
+.. code:: shell
+
+   /path/to/pythonX.Y setup.py build
+   /path/to/pythonX.Y setup.py build
+   /path/to/pythonX.Y setup.py install
+
+Yes, the build command does need to be run twice. Yes, you still need to
+run the potentially failing or incomplete steps during the
+``configure``, ``make`` and ``make install`` steps with installing
+GPGME. This is because those steps generate a lot of essential files
+needed, both by and in order to create, the bindings (including both the
+``setup.py`` and ``gpgme.h`` files).
+
+#. IMPORTANT Note
+
+   If specifying a selected number of languages to create bindings for,
+   try to leave Python last. Currently the majority of the other
+   language bindings are also preceding Python of either version when
+   listed alphabetically and so that just happens by default currently.
+
+   If Python is set to precede one of the other languages then it is
+   possible that the errors described here may interrupt the build
+   process before generating bindings for those other languages. In
+   these cases it may be preferable to configure all preferred language
+   bindings separately with alternative ``configure`` steps for GPGME
+   using the ``--enable-languages=$LANGUAGE`` option.
+
+.. _snafu-lessons-for-the-lazy:
+
+Reinstalling Responsibly
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regardless of whether you\'re installing for one version of Python or
+several, there will come a point where reinstallation is required. With
+most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about. Then the
+module is upgraded, the new files are copied over the old and that\'s
+the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant ``site-packages`` directory for the version of Python in
+question and removing the ``gpg/`` directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on the
+system, but the same is true of installing the module in the first
+place.
+
+.. _snafu-the-full-monty:
+
+Multiple installations
+~~~~~~~~~~~~~~~~~~~~~~
+
+For a veriety of reasons it may be either necessary or just preferable
+to install the bindings to alternative installed Python versions which
+meet the requirements of these bindings.
+
+On POSIX systems this will generally be most simply achieved by running
+the manual installation commands (build, build, install) as described in
+the previous section for each Python installation the bindings need to
+be installed to.
+
+As per the SWIG documentation: the compilers, libraries and runtime used
+to build GPGME and the Python Bindings **must** match those used to
+compile Python itself, including the version number(s) (at least going
+by major version numbers and probably minor numbers too).
+
+On most POSIX systems, including OS X, this will very likely be the case
+in most, if not all, cases.
+
+.. _snafu-runtime-not-funtime:
+
+Won\'t Work With Windows
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are semi-regular reports of Windows users having considerable
+difficulty in installing and using the Python bindings at all. Very
+often, possibly even always, these reports come from Cygwin users and/or
+MinGW users and/or Msys2 users. Though not all of them have been
+confirmed, it appears that these reports have also come from people who
+installed Python using the Windows installer files from the `Python
+website <https://python.org>`__ (i.e. mostly MSI installers, sometimes
+self-extracting ``.exe`` files).
+
+The Windows versions of Python are not built using Cygwin, MinGW or
+Msys2; they\'re built using Microsoft Visual Studio. Furthermore the
+version used is *considerably* more advanced than the version which
+MinGW obtained a small number of files from many years ago in order to
+be able to compile anything at all. Not only that, but there are changes
+to the version of Visual Studio between some micro releases, though that
+is is particularly the case with Python 2.7, since it has been kept
+around far longer than it should have been.
+
+There are two theoretical solutions to this issue:
+
+#. Compile and install the GnuPG stack, including GPGME and the Python
+   bibdings using the same version of Microsoft Visual Studio used by
+   the Python Foundation to compile the version of Python installed.
+
+   If there are multiple versions of Python then this will need to be
+   done with each different version of Visual Studio used.
+
+#. Compile and install Python using the same tools used by choice, such
+   as MinGW or Msys2.
+
+Do **not** use the official Windows installer for Python unless
+following the first method.
+
+In this type of situation it may even be for the best to accept that
+there are less limitations on permissive software than free software and
+simply opt to use a recent version of the Community Edition of Microsoft
+Visual Studio to compile and build all of it, no matter what.
+
+Investigations into the extent or the limitations of this issue are
+ongoing.
+
+.. _snafu-cffi:
+
+CFFI is the Best™ and GPGME should use it instead of SWIG
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are many reasons for favouring
+`CFFI <https://cffi.readthedocs.io/en/latest/overview.html>`__ and
+proponents of it are quite happy to repeat these things as if all it
+would take to switch from SWIG to CFFI is repeating that list as if it
+were a new concept.
+
+The fact is that there are things which Python\'s CFFI implementation
+cannot handle in the GPGME C code. Beyond that there are features of
+SWIG which are simply not available with CFFI at all. SWIG generates the
+bindings to Python using the ``gpgme.h`` file, but that file is not a
+single version shipped with each release, it too is generated when GPGME
+is compiled.
+
+CFFI is currently unable to adapt to such a potentially mutable
+codebase. If there were some means of applying SWIG\'s dynamic code
+generation to produce the Python/CFFI API modes of accessing the GPGME
+libraries (or the source source code directly), but such a thing does
+not exist yet either and it currently appears that work is needed in at
+least one of CFFI\'s dependencies before any of this can be addressed.
+
+So if you\'re a massive fan of CFFI; that\'s great, but if you want this
+project to switch to CFFI then rather than just insisting that it
+should, I\'d suggest you volunteer to bring CFFI up to the level this
+project needs.
+
+If you\'re actually seriously considering doing so, then I\'d suggest
+taking the ``gpgme-tool.c`` file in the GPGME ``src/`` directory and
+getting that to work with any of the CFFI API methods (not the ABI
+methods, they\'ll work with pretty much anything). When you start
+running into trouble with \"ifdefs\" then you\'ll know what sort of
+things are lacking. That doesn\'t even take into account the amount of
+work saved via SWIG\'s code generation techniques either.
+
+.. _snafu-venv:
+
+Virtualised Environments
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is fairly common practice amongst Python developers to, as much as
+possible, use packages like virtualenv to keep various things that are
+to be installed from interfering with each other. Given how much of the
+GPGME bindings is often at odds with the usual pythonic way of doing
+things, it stands to reason that this would be called into question too.
+
+As it happens the answer as to whether or not the bindings can be used
+with virtualenv, the answer is both yes and no.
+
+In general we recommend installing to the relevant path and matching
+prefix of GPGME itself. Which means that when GPGME, and ideally the
+rest of the GnuPG stack, is installed to a prefix like ``/usr/local`` or
+``/opt/local`` then the bindings would need to be installed to the main
+Python installation and not a virtualised abstraction. Attempts to
+separate the two in the past have been known to cause weird and
+intermittent errors ranging from minor annoyances to complete failures
+in the build process.
+
+As a consequence we only recommend building with and installing to the
+main Python installations within the same prefix as GPGME is installed
+to or which are found by GPGME\'s configuration stage immediately prior
+to running the make commands. Which is exactly what the compiling and
+installing process of GPGME does by default.
+
+Once that is done, however, it appears that a copy the compiled module
+may be installed into a virtualenv of the same major and minor version
+matching the build. Alternatively it is possible to utilise a
+``sites.pth`` file in the ``site-packages/`` directory of a viertualenv
+installation, which links back to the system installations corresponding
+directory in order to import anything installed system wide. This may or
+may not be appropriate on a case by case basis.
+
+Though extensive testing of either of these options is not yet complete,
+preliminary testing of them indicates that both are viable as long as
+the main installation is complete. Which means that certain other
+options normally restricted to virtual environments are also available,
+including integration with pythonic test suites (e.g.
+`pytest <https://docs.pytest.org/en/latest/index.html>`__) and other
+large projects.
+
+That said, it is worth reiterating the warning regarding non-standard
+installations. If one were to attempt to install the bindings only to a
+virtual environment without somehow also including the full GnuPG stack
+(or enough of it as to include GPGME) then it is highly likely that
+errors would be encountered at some point and more than a little likely
+that the build process itself would break.
+
+If a degree of separation from the main operating system is still
+required in spite of these warnings, then consider other forms of
+virtualisation. Either a virtual machine (e.g.
+`VirtualBox <https://www.virtualbox.org/>`__), a hardware emulation
+layer (e.g. `QEMU <https://www.qemu.org/>`__) or an application
+container (e.g. `Docker <https://www.docker.com/why-docker>`__).
+
+Finally it should be noted that the limited tests conducted thus far
+have been using the ``virtualenv`` command in a new directory to create
+the virtual python environment. As opposed to the standard ``python3
+-m venv`` and it is possible that this will make a difference depending
+on the system and version of Python in use. Another option is to run the
+command ``python3 -m virtualenv /path/to/install/virtual/thingy``
+instead.
+
+.. _howto-fund-a-mental:
+
+Fundamentals
+============
+
+Before we can get to the fun stuff, there are a few matters regarding
+GPGME\'s design which hold true whether you\'re dealing with the C code
+directly or these Python bindings.
+
+.. _no-rest-for-the-wicked:
+
+No REST
+-------
+
+The first part of which is or will be fairly blatantly obvious upon
+viewing the first example, but it\'s worth reiterating anyway. That
+being that this API is **not** a REST API. Nor indeed could it ever be
+one.
+
+Most, if not all, Python programmers (and not just Python programmers)
+know how easy it is to work with a RESTful API. In fact they\'ve become
+so popular that many other APIs attempt to emulate REST-like behaviour
+as much as they are able. Right down to the use of JSON formatted output
+to facilitate the use of their API without having to retrain developers.
+
+This API does not do that. It would not be able to do that and also
+provide access to the entire C API on which it\'s built. It does,
+however, provide a very pythonic interface on top of the direct bindings
+and it\'s this pythonic layer that this HOWTO deals with.
+
+.. _howto-get-context:
+
+Context
+-------
+
+One of the reasons which prevents this API from being RESTful is that
+most operations require more than one instruction to the API to perform
+the task. Sure, there are certain functions which can be performed
+simultaneously, particularly if the result known or strongly anticipated
+(e.g. selecting and encrypting to a key known to be in the public
+keybox).
+
+There are many more, however, which cannot be manipulated so readily:
+they must be performed in a specific sequence and the result of one
+operation has a direct bearing on the outcome of subsequent operations.
+Not merely by generating an error either.
+
+When dealing with this type of persistent state on the web, full of both
+the RESTful and REST-like, it\'s most commonly referred to as a session.
+In GPGME, however, it is called a context and every operation type has
+one.
+
+.. _howto-keys:
+
+Working with keys
+=================
+
+.. _howto-keys-selection:
+
+Key selection
+-------------
+
+Selecting keys to encrypt to or to sign with will be a common occurrence
+when working with GPGMe and the means available for doing so are quite
+simple.
+
+They do depend on utilising a Context; however once the data is recorded
+in another variable, that Context does not need to be the same one which
+subsequent operations are performed.
+
+The easiest way to select a specific key is by searching for that key\'s
+key ID or fingerprint, preferably the full fingerprint without any
+spaces in it. A long key ID will probably be okay, but is not advised
+and short key IDs are already a problem with some being generated to
+match specific patterns. It does not matter whether the pattern is upper
+or lower case.
+
+So this is the best method:
+
+.. code:: python
+
+   import gpg
+
+   k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+   keys = list(k)
+
+This is passable and very likely to be common:
+
+.. code:: python
+
+   import gpg
+
+   k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+   keys = list(k)
+
+And this is a really bad idea:
+
+.. code:: python
+
+   import gpg
+
+   k = gpg.Context().keylist(pattern="0xDEADBEEF")
+   keys = list(k)
+
+Alternatively it may be that the intention is to create a list of keys
+which all match a particular search string. For instance all the
+addresses at a particular domain, like this:
+
+.. code:: python
+
+   import gpg
+
+   ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+   nsa = list(ncsc)
+
+.. _howto-keys-counting:
+
+Counting keys
+~~~~~~~~~~~~~
+
+Counting the number of keys in your public keybox (``pubring.kbx``), the
+format which has superseded the old keyring format (``pubring.gpg`` and
+``secring.gpg``), or the number of secret keys is a very simple task.
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+   seckeys = c.keylist(pattern=None, secret=True)
+   pubkeys = c.keylist(pattern=None, secret=False)
+
+   seclist = list(seckeys)
+   secnum = len(seclist)
+
+   publist = list(pubkeys)
+   pubnum = len(publist)
+
+   print("""
+     Number of secret keys:  {0}
+     Number of public keys:  {1}
+   """.format(secnum, pubnum))
+
+NOTE: The `Cython <#cython>`__ introduction in the `Advanced and
+Experimental <#advanced-use>`__ section uses this same key counting code
+with Cython to demonstrate some areas where Cython can improve
+performance even with the bindings. Users with large public keyrings or
+keyboxes, for instance, should consider these options if they are
+comfortable with using Cython.
+
+.. _howto-get-key:
+
+Get key
+-------
+
+An alternative method of getting a single key via its fingerprint is
+available directly within a Context with ``Context().get_key``. This is
+the preferred method of selecting a key in order to modify it, sign or
+certify it and for obtaining relevant data about a single key as a part
+of other functions; when verifying a signature made by that key, for
+instance.
+
+By default this method will select public keys, but it can select secret
+keys as well.
+
+This first example demonstrates selecting the current key of Werner
+Koch, which is due to expire at the end of 2018:
+
+.. code:: python
+
+   import gpg
+
+   fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+   key = gpg.Context().get_key(fingerprint)
+
+Whereas this example demonstrates selecting the author\'s current key
+with the ``secret`` key word argument set to ``True``:
+
+.. code:: python
+
+   import gpg
+
+   fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+   key = gpg.Context().get_key(fingerprint, secret=True)
+
+It is, of course, quite possible to select expired, disabled and revoked
+keys with this function, but only to effectively display information
+about those keys.
+
+It is also possible to use both unicode or string literals and byte
+literals with the fingerprint when getting a key in this way.
+
+.. _howto-import-key:
+
+Importing keys
+--------------
+
+Importing keys is possible with the ``key_import()`` method and takes
+one argument which is a bytes literal object containing either the
+binary or ASCII armoured key data for one or more keys.
+
+The following example retrieves one or more keys from the SKS keyservers
+via the web using the requests module. Since requests returns the
+content as a bytes literal object, we can then use that directly to
+import the resulting data into our keybox.
+
+.. code:: python
+
+   import gpg
+   import os.path
+   import requests
+
+   c = gpg.Context()
+   url = "https://sks-keyservers.net/pks/lookup"
+   pattern = input("Enter the pattern to search for key or user IDs: ")
+   payload = {"op": "get", "search": pattern}
+
+   r = requests.get(url, verify=True, params=payload)
+   result = c.key_import(r.content)
+
+   if result is not None and hasattr(result, "considered") is False:
+       print(result)
+   elif result is not None and hasattr(result, "considered") is True:
+       num_keys = len(result.imports)
+       new_revs = result.new_revocations
+       new_sigs = result.new_signatures
+       new_subs = result.new_sub_keys
+       new_uids = result.new_user_ids
+       new_scrt = result.secret_imported
+       nochange = result.unchanged
+       print("""
+     The total number of keys considered for import was:  {0}
+
+        Number of keys revoked:  {1}
+      Number of new signatures:  {2}
+         Number of new subkeys:  {3}
+        Number of new user IDs:  {4}
+     Number of new secret keys:  {5}
+      Number of unchanged keys:  {6}
+
+     The key IDs for all considered keys were:
+   """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+              nochange))
+       for i in range(num_keys):
+           print("{0}\n".format(result.imports[i].fpr))
+   else:
+       pass
+
+NOTE: When searching for a key ID of any length or a fingerprint
+(without spaces), the SKS servers require the the leading ``0x``
+indicative of hexadecimal be included. Also note that the old short key
+IDs (e.g. ``0xDEADBEEF``) should no longer be used due to the relative
+ease by which such key IDs can be reproduced, as demonstrated by the
+Evil32 Project in 2014 (which was subsequently exploited in 2016).
+
+.. _import-protonmail:
+
+Working with ProtonMail
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+.. code:: python
+
+   import gpg
+   import requests
+   import sys
+
+   print("""
+   This script searches the ProtonMail key server for the specified key and
+   imports it.
+   """)
+
+   c = gpg.Context(armor=True)
+   url = "https://api.protonmail.ch/pks/lookup"
+   ksearch = []
+
+   if len(sys.argv) >= 2:
+       keyterm = sys.argv[1]
+   else:
+       keyterm = input("Enter the key ID, UID or search string: ")
+
+   if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+       ksearch.append(keyterm[1:])
+       ksearch.append(keyterm[1:])
+       ksearch.append(keyterm[1:])
+   elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+       ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+       ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+       ksearch.append("{0}@pm.me".format(keyterm[1:]))
+   elif keyterm.count("@") == 0:
+       ksearch.append("{0}@protonmail.com".format(keyterm))
+       ksearch.append("{0}@protonmail.ch".format(keyterm))
+       ksearch.append("{0}@pm.me".format(keyterm))
+   elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+       uidlist = keyterm.split("@")
+       for uid in uidlist:
+           ksearch.append("{0}@protonmail.com".format(uid))
+           ksearch.append("{0}@protonmail.ch".format(uid))
+           ksearch.append("{0}@pm.me".format(uid))
+   elif keyterm.count("@") > 2:
+       uidlist = keyterm.split("@")
+       for uid in uidlist:
+           ksearch.append("{0}@protonmail.com".format(uid))
+           ksearch.append("{0}@protonmail.ch".format(uid))
+           ksearch.append("{0}@pm.me".format(uid))
+   else:
+       ksearch.append(keyterm)
+
+   for k in ksearch:
+       payload = {"op": "get", "search": k}
+       try:
+           r = requests.get(url, verify=True, params=payload)
+           if r.ok is True:
+               result = c.key_import(r.content)
+           elif r.ok is False:
+               result = r.content
+       except Exception as e:
+           result = None
+
+       if result is not None and hasattr(result, "considered") is False:
+           print("{0} for {1}".format(result.decode(), k))
+       elif result is not None and hasattr(result, "considered") is True:
+           num_keys = len(result.imports)
+           new_revs = result.new_revocations
+           new_sigs = result.new_signatures
+           new_subs = result.new_sub_keys
+           new_uids = result.new_user_ids
+           new_scrt = result.secret_imported
+           nochange = result.unchanged
+           print("""
+   The total number of keys considered for import was:  {0}
+
+   With UIDs wholely or partially matching the following string:
+
+           {1}
+
+      Number of keys revoked:  {2}
+    Number of new signatures:  {3}
+       Number of new subkeys:  {4}
+      Number of new user IDs:  {5}
+   Number of new secret keys:  {6}
+    Number of unchanged keys:  {7}
+
+   The key IDs for all considered keys were:
+   """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+              nochange))
+           for i in range(num_keys):
+               print(result.imports[i].fpr)
+           print("")
+       elif result is None:
+           print(e)
+
+Both the above example,
+`pmkey-import.py <../examples/howto/pmkey-import.py>`__, and a version
+which prompts for an alternative GnuPG home directory,
+`pmkey-import-alt.py <../examples/howto/pmkey-import-alt.py>`__, are
+available with the other examples and are executable scripts.
+
+Note that while the ProtonMail servers are based on the SKS servers,
+their server is related more to their API and is not feature complete by
+comparison to the servers in the SKS pool. One notable difference being
+that the ProtonMail server does not permit non ProtonMail users to
+update their own keys, which could be a vector for attacking ProtonMail
+users who may not receive a key\'s revocation if it had been
+compromised.
+
+.. _import-hkp4py:
+
+Importing with HKP for Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Performing the same tasks with the `hkp4py
+module <https://github.com/Selfnet/hkp4py>`__ (available via PyPI) is
+not too much different, but does provide a number of options of benefit
+to end users. Not least of which being the ability to perform some
+checks on a key before importing it or not. For instance it may be the
+policy of a site or project to only import keys which have not been
+revoked. The hkp4py module permits such checks prior to the importing of
+the keys found.
+
+.. code:: python
+
+   import gpg
+   import hkp4py
+   import sys
+
+   c = gpg.Context()
+   server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+   results = []
+
+   if len(sys.argv) > 2:
+       pattern = " ".join(sys.argv[1:])
+   elif len(sys.argv) == 2:
+       pattern = sys.argv[1]
+   else:
+       pattern = input("Enter the pattern to search for keys or user IDs: ")
+
+   try:
+       keys = server.search(pattern)
+       print("Found {0} key(s).".format(len(keys)))
+   except Exception as e:
+       keys = []
+       for logrus in pattern.split():
+           if logrus.startswith("0x") is True:
+               key = server.search(logrus)
+           else:
+               key = server.search("0x{0}".format(logrus))
+           keys.append(key[0])
+       print("Found {0} key(s).".format(len(keys)))
+
+   for key in keys:
+       import_result = c.key_import(key.key_blob)
+       results.append(import_result)
+
+   for result in results:
+       if result is not None and hasattr(result, "considered") is False:
+           print(result)
+       elif result is not None and hasattr(result, "considered") is True:
+           num_keys = len(result.imports)
+           new_revs = result.new_revocations
+           new_sigs = result.new_signatures
+           new_subs = result.new_sub_keys
+           new_uids = result.new_user_ids
+           new_scrt = result.secret_imported
+           nochange = result.unchanged
+           print("""
+   The total number of keys considered for import was:  {0}
+
+      Number of keys revoked:  {1}
+    Number of new signatures:  {2}
+       Number of new subkeys:  {3}
+      Number of new user IDs:  {4}
+   Number of new secret keys:  {5}
+    Number of unchanged keys:  {6}
+
+   The key IDs for all considered keys were:
+   """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+              nochange))
+           for i in range(num_keys):
+               print(result.imports[i].fpr)
+           print("")
+       else:
+           pass
+
+Since the hkp4py module handles multiple keys just as effectively as one
+(``keys`` is a list of responses per matching key), the example above is
+able to do a little bit more with the returned data before anything is
+actually imported.
+
+.. _import-protonmail-hkp4py:
+
+Importing from ProtonMail with HKP for Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Though this can provide certain benefits even when working with
+ProtonMail, the scope is somewhat constrained there due to the
+limitations of the ProtonMail keyserver.
+
+For instance, searching the SKS keyserver pool for the term \"gnupg\"
+produces hundreds of results from any time the word appears in any part
+of a user ID. Performing the same search on the ProtonMail keyserver
+returns zero results, even though there are at least two test accounts
+which include it as part of the username.
+
+The cause of this discrepancy is the deliberate configuration of that
+server by ProtonMail to require an exact match of the full email address
+of the ProtonMail user whose key is being requested. Presumably this is
+intended to reduce breaches of privacy of their users as an email
+address must already be known before a key for that address can be
+obtained.
+
+#. Import from ProtonMail via HKP for Python Example no. 1
+
+   The following script is avalable with the rest of the examples under
+   the somewhat less than original name, ``pmkey-import-hkp.py``.
+
+   .. code:: python
+
+      import gpg
+      import hkp4py
+      import os.path
+      import sys
+
+      print("""
+      This script searches the ProtonMail key server for the specified key and
+      imports it.
+
+      Usage:  pmkey-import-hkp.py [search strings]
+      """)
+
+      c = gpg.Context(armor=True)
+      server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+      keyterms = []
+      ksearch = []
+      allkeys = []
+      results = []
+      paradox = []
+      homeless = None
+
+      if len(sys.argv) > 2:
+          keyterms = sys.argv[1:]
+      elif len(sys.argv) == 2:
+          keyterm = sys.argv[1]
+          keyterms.append(keyterm)
+      else:
+          key_term = input("Enter the key ID, UID or search string: ")
+          keyterms = key_term.split()
+
+      for keyterm in keyterms:
+          if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+              ksearch.append(keyterm[1:])
+              ksearch.append(keyterm[1:])
+              ksearch.append(keyterm[1:])
+          elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+              ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+              ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+              ksearch.append("{0}@pm.me".format(keyterm[1:]))
+          elif keyterm.count("@") == 0:
+              ksearch.append("{0}@protonmail.com".format(keyterm))
+              ksearch.append("{0}@protonmail.ch".format(keyterm))
+              ksearch.append("{0}@pm.me".format(keyterm))
+          elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+              uidlist = keyterm.split("@")
+              for uid in uidlist:
+                  ksearch.append("{0}@protonmail.com".format(uid))
+                  ksearch.append("{0}@protonmail.ch".format(uid))
+                  ksearch.append("{0}@pm.me".format(uid))
+          elif keyterm.count("@") > 2:
+              uidlist = keyterm.split("@")
+              for uid in uidlist:
+                  ksearch.append("{0}@protonmail.com".format(uid))
+                  ksearch.append("{0}@protonmail.ch".format(uid))
+                  ksearch.append("{0}@pm.me".format(uid))
+          else:
+              ksearch.append(keyterm)
+
+      for k in ksearch:
+          print("Checking for key for: {0}".format(k))
+          try:
+              keys = server.search(k)
+              if isinstance(keys, list) is True:
+                  for key in keys:
+                      allkeys.append(key)
+                      try:
+                          import_result = c.key_import(key.key_blob)
+                      except Exception as e:
+                          import_result = c.key_import(key.key)
+              else:
+                  paradox.append(keys)
+                  import_result = None
+          except Exception as e:
+              import_result = None
+          results.append(import_result)
+
+      for result in results:
+          if result is not None and hasattr(result, "considered") is False:
+              print("{0} for {1}".format(result.decode(), k))
+          elif result is not None and hasattr(result, "considered") is True:
+              num_keys = len(result.imports)
+              new_revs = result.new_revocations
+              new_sigs = result.new_signatures
+              new_subs = result.new_sub_keys
+              new_uids = result.new_user_ids
+              new_scrt = result.secret_imported
+              nochange = result.unchanged
+              print("""
+      The total number of keys considered for import was:  {0}
+
+      With UIDs wholely or partially matching the following string:
+
+              {1}
+
+         Number of keys revoked:  {2}
+       Number of new signatures:  {3}
+          Number of new subkeys:  {4}
+         Number of new user IDs:  {5}
+      Number of new secret keys:  {6}
+       Number of unchanged keys:  {7}
+
+      The key IDs for all considered keys were:
+      """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+                 nochange))
+              for i in range(num_keys):
+                  print(result.imports[i].fpr)
+              print("")
+          elif result is None:
+              pass
+
+#. Import from ProtonMail via HKP for Python Example no. 2
+
+   Like its counterpart above, this script can also be found with the
+   rest of the examples, by the name pmkey-import-hkp-alt.py.
+
+   With this script a modicum of effort has been made to treat anything
+   passed as a ``homedir`` which either does not exist or which is not a
+   directory, as also being a pssible user ID to check for. It\'s not
+   guaranteed to pick up on all such cases, but it should cover most of
+   them.
+
+   .. code:: python
+
+      import gpg
+      import hkp4py
+      import os.path
+      import sys
+
+      print("""
+      This script searches the ProtonMail key server for the specified key and
+      imports it.  Optionally enables specifying a different GnuPG home directory.
+
+      Usage:  pmkey-import-hkp.py [homedir] [search string]
+         or:  pmkey-import-hkp.py [search string]
+      """)
+
+      c = gpg.Context(armor=True)
+      server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+      keyterms = []
+      ksearch = []
+      allkeys = []
+      results = []
+      paradox = []
+      homeless = None
+
+      if len(sys.argv) > 3:
+          homedir = sys.argv[1]
+          keyterms = sys.argv[2:]
+      elif len(sys.argv) == 3:
+          homedir = sys.argv[1]
+          keyterm = sys.argv[2]
+          keyterms.append(keyterm)
+      elif len(sys.argv) == 2:
+          homedir = ""
+          keyterm = sys.argv[1]
+          keyterms.append(keyterm)
+      else:
+          keyterm = input("Enter the key ID, UID or search string: ")
+          homedir = input("Enter the GPG configuration directory path (optional): ")
+          keyterms.append(keyterm)
+
+      if len(homedir) == 0:
+          homedir = None
+          homeless = False
+
+      if homedir is not None:
+          if homedir.startswith("~"):
+              if os.path.exists(os.path.expanduser(homedir)) is True:
+                  if os.path.isdir(os.path.expanduser(homedir)) is True:
+                      c.home_dir = os.path.realpath(os.path.expanduser(homedir))
+                  else:
+                      homeless = True
+              else:
+                  homeless = True
+          elif os.path.exists(os.path.realpath(homedir)) is True:
+              if os.path.isdir(os.path.realpath(homedir)) is True:
+                  c.home_dir = os.path.realpath(homedir)
+              else:
+                  homeless = True
+          else:
+              homeless = True
+
+      # First check to see if the homedir really is a homedir and if not, treat it as
+      # a search string.
+      if homeless is True:
+          keyterms.append(homedir)
+          c.home_dir = None
+      else:
+          pass
+
+      for keyterm in keyterms:
+          if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+              ksearch.append(keyterm[1:])
+              ksearch.append(keyterm[1:])
+              ksearch.append(keyterm[1:])
+          elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+              ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+              ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+              ksearch.append("{0}@pm.me".format(keyterm[1:]))
+          elif keyterm.count("@") == 0:
+              ksearch.append("{0}@protonmail.com".format(keyterm))
+              ksearch.append("{0}@protonmail.ch".format(keyterm))
+              ksearch.append("{0}@pm.me".format(keyterm))
+          elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+              uidlist = keyterm.split("@")
+              for uid in uidlist:
+                  ksearch.append("{0}@protonmail.com".format(uid))
+                  ksearch.append("{0}@protonmail.ch".format(uid))
+                  ksearch.append("{0}@pm.me".format(uid))
+          elif keyterm.count("@") > 2:
+              uidlist = keyterm.split("@")
+              for uid in uidlist:
+                  ksearch.append("{0}@protonmail.com".format(uid))
+                  ksearch.append("{0}@protonmail.ch".format(uid))
+                  ksearch.append("{0}@pm.me".format(uid))
+          else:
+              ksearch.append(keyterm)
+
+      for k in ksearch:
+          print("Checking for key for: {0}".format(k))
+          try:
+              keys = server.search(k)
+              if isinstance(keys, list) is True:
+                  for key in keys:
+                      allkeys.append(key)
+                      try:
+                          import_result = c.key_import(key.key_blob)
+                      except Exception as e:
+                          import_result = c.key_import(key.key)
+              else:
+                  paradox.append(keys)
+                  import_result = None
+          except Exception as e:
+              import_result = None
+          results.append(import_result)
+
+      for result in results:
+          if result is not None and hasattr(result, "considered") is False:
+              print("{0} for {1}".format(result.decode(), k))
+          elif result is not None and hasattr(result, "considered") is True:
+              num_keys = len(result.imports)
+              new_revs = result.new_revocations
+              new_sigs = result.new_signatures
+              new_subs = result.new_sub_keys
+              new_uids = result.new_user_ids
+              new_scrt = result.secret_imported
+              nochange = result.unchanged
+              print("""
+      The total number of keys considered for import was:  {0}
+
+      With UIDs wholely or partially matching the following string:
+
+              {1}
+
+         Number of keys revoked:  {2}
+       Number of new signatures:  {3}
+          Number of new subkeys:  {4}
+         Number of new user IDs:  {5}
+      Number of new secret keys:  {6}
+       Number of unchanged keys:  {7}
+
+      The key IDs for all considered keys were:
+      """.format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+                 nochange))
+              for i in range(num_keys):
+                  print(result.imports[i].fpr)
+              print("")
+          elif result is None:
+              pass
+
+.. _howto-export-key:
+
+Exporting keys
+--------------
+
+Exporting keys remains a reasonably simple task, but has been separated
+into three different functions for the OpenPGP cryptographic engine. Two
+of those functions are for exporting public keys and the third is for
+exporting secret keys.
+
+.. _howto-export-public-key:
+
+Exporting public keys
+~~~~~~~~~~~~~~~~~~~~~
+
+There are two methods of exporting public keys, both of which are very
+similar to the other. The default method, ``key_export()``, will export
+a public key or keys matching a specified pattern as normal. The
+alternative, the ``key_export_minimal()`` method, will do the same thing
+except producing a minimised output with extra signatures and third
+party signatures or certifications removed.
+
+.. code:: python
+
+   import gpg
+   import os.path
+   import sys
+
+   print("""
+   This script exports one or more public keys.
+   """)
+
+   c = gpg.Context(armor=True)
+
+   if len(sys.argv) >= 4:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = sys.argv[3]
+   elif len(sys.argv) == 3:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   elif len(sys.argv) == 2:
+       keyfile = sys.argv[1]
+       logrus = input("Enter the UID matching the key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   else:
+       keyfile = input("Enter the path and filename to save the secret key to: ")
+       logrus = input("Enter the UID matching the key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+
+   if homedir.startswith("~"):
+       if os.path.exists(os.path.expanduser(homedir)) is True:
+           c.home_dir = os.path.expanduser(homedir)
+       else:
+           pass
+   elif os.path.exists(homedir) is True:
+       c.home_dir = homedir
+   else:
+       pass
+
+   try:
+       result = c.key_export(pattern=logrus)
+   except:
+       result = c.key_export(pattern=None)
+
+   if result is not None:
+       with open(keyfile, "wb") as f:
+           f.write(result)
+   else:
+       pass
+
+It should be noted that the result will only return ``None`` when a
+search pattern has been entered, but has not matched any keys. When the
+search pattern itself is set to ``None`` this triggers the exporting of
+the entire public keybox.
+
+.. code:: python
+
+   import gpg
+   import os.path
+   import sys
+
+   print("""
+   This script exports one or more public keys in minimised form.
+   """)
+
+   c = gpg.Context(armor=True)
+
+   if len(sys.argv) >= 4:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = sys.argv[3]
+   elif len(sys.argv) == 3:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   elif len(sys.argv) == 2:
+       keyfile = sys.argv[1]
+       logrus = input("Enter the UID matching the key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   else:
+       keyfile = input("Enter the path and filename to save the secret key to: ")
+       logrus = input("Enter the UID matching the key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+
+   if homedir.startswith("~"):
+       if os.path.exists(os.path.expanduser(homedir)) is True:
+           c.home_dir = os.path.expanduser(homedir)
+       else:
+           pass
+   elif os.path.exists(homedir) is True:
+       c.home_dir = homedir
+   else:
+       pass
+
+   try:
+       result = c.key_export_minimal(pattern=logrus)
+   except:
+       result = c.key_export_minimal(pattern=None)
+
+   if result is not None:
+       with open(keyfile, "wb") as f:
+           f.write(result)
+   else:
+       pass
+
+.. _howto-export-secret-key:
+
+Exporting secret keys
+~~~~~~~~~~~~~~~~~~~~~
+
+Exporting secret keys is, functionally, very similar to exporting public
+keys; save for the invocation of ``pinentry`` via ``gpg-agent`` in order
+to securely enter the key\'s passphrase and authorise the export.
+
+The following example exports the secret key to a file which is then set
+with the same permissions as the output files created by the command
+line secret key export options.
+
+.. code:: python
+
+   import gpg
+   import os
+   import os.path
+   import sys
+
+   print("""
+   This script exports one or more secret keys.
+
+   The gpg-agent and pinentry are invoked to authorise the export.
+   """)
+
+   c = gpg.Context(armor=True)
+
+   if len(sys.argv) >= 4:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = sys.argv[3]
+   elif len(sys.argv) == 3:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   elif len(sys.argv) == 2:
+       keyfile = sys.argv[1]
+       logrus = input("Enter the UID matching the secret key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   else:
+       keyfile = input("Enter the path and filename to save the secret key to: ")
+       logrus = input("Enter the UID matching the secret key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+
+   if len(homedir) == 0:
+       homedir = None
+   elif homedir.startswith("~"):
+       userdir = os.path.expanduser(homedir)
+       if os.path.exists(userdir) is True:
+           homedir = os.path.realpath(userdir)
+       else:
+           homedir = None
+   else:
+       homedir = os.path.realpath(homedir)
+
+   if os.path.exists(homedir) is False:
+       homedir = None
+   else:
+       if os.path.isdir(homedir) is False:
+           homedir = None
+       else:
+           pass
+
+   if homedir is not None:
+       c.home_dir = homedir
+   else:
+       pass
+
+   try:
+       result = c.key_export_secret(pattern=logrus)
+   except:
+       result = c.key_export_secret(pattern=None)
+
+   if result is not None:
+       with open(keyfile, "wb") as f:
+           f.write(result)
+       os.chmod(keyfile, 0o600)
+   else:
+       pass
+
+Alternatively the approach of the following script can be used. This
+longer example saves the exported secret key(s) in files in the GnuPG
+home directory, in addition to setting the file permissions as only
+readable and writable by the user. It also exports the secret key(s)
+twice in order to output both GPG binary (``.gpg``) and ASCII armoured
+(``.asc``) files.
+
+.. code:: python
+
+   import gpg
+   import os
+   import os.path
+   import subprocess
+   import sys
+
+   print("""
+   This script exports one or more secret keys as both ASCII armored and binary
+   file formats, saved in files within the user's GPG home directory.
+
+   The gpg-agent and pinentry are invoked to authorise the export.
+   """)
+
+   if sys.platform == "win32":
+       gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+   else:
+       gpgconfcmd = "gpgconf --list-dirs homedir"
+
+   a = gpg.Context(armor=True)
+   b = gpg.Context()
+   c = gpg.Context()
+
+   if len(sys.argv) >= 4:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = sys.argv[3]
+   elif len(sys.argv) == 3:
+       keyfile = sys.argv[1]
+       logrus = sys.argv[2]
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   elif len(sys.argv) == 2:
+       keyfile = sys.argv[1]
+       logrus = input("Enter the UID matching the secret key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+   else:
+       keyfile = input("Enter the filename to save the secret key to: ")
+       logrus = input("Enter the UID matching the secret key(s) to export: ")
+       homedir = input("Enter the GPG configuration directory path (optional): ")
+
+   if len(homedir) == 0:
+       homedir = None
+   elif homedir.startswith("~"):
+       userdir = os.path.expanduser(homedir)
+       if os.path.exists(userdir) is True:
+           homedir = os.path.realpath(userdir)
+       else:
+           homedir = None
+   else:
+       homedir = os.path.realpath(homedir)
+
+   if os.path.exists(homedir) is False:
+       homedir = None
+   else:
+       if os.path.isdir(homedir) is False:
+           homedir = None
+       else:
+           pass
+
+   if homedir is not None:
+       c.home_dir = homedir
+   else:
+       pass
+
+   if c.home_dir is not None:
+       if c.home_dir.endswith("/"):
+           gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
+           ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
+       else:
+           gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
+           ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
+   else:
+       if os.path.exists(os.environ["GNUPGHOME"]) is True:
+           hd = os.environ["GNUPGHOME"]
+       else:
+           try:
+               hd = subprocess.getoutput(gpgconfcmd)
+           except:
+               process = subprocess.Popen(gpgconfcmd.split(),
+                                          stdout=subprocess.PIPE)
+               procom = process.communicate()
+               if sys.version_info[0] == 2:
+                   hd = procom[0].strip()
+               else:
+                   hd = procom[0].decode().strip()
+       gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
+       ascfile = "{0}/{1}.asc".format(hd, keyfile)
+
+   try:
+       a_result = a.key_export_secret(pattern=logrus)
+       b_result = b.key_export_secret(pattern=logrus)
+   except:
+       a_result = a.key_export_secret(pattern=None)
+       b_result = b.key_export_secret(pattern=None)
+
+   if a_result is not None:
+       with open(ascfile, "wb") as f:
+           f.write(a_result)
+       os.chmod(ascfile, 0o600)
+   else:
+       pass
+
+   if b_result is not None:
+       with open(gpgfile, "wb") as f:
+           f.write(b_result)
+       os.chmod(gpgfile, 0o600)
+   else:
+       pass
+
+.. _howto-send-public-key:
+
+Sending public keys to the SKS Keyservers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As with the previous section on importing keys, the ``hkp4py`` module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+.. code:: python
+
+   import gpg
+   import hkp4py
+   import os.path
+   import sys
+
+   print("""
+   This script sends one or more public keys to the SKS keyservers and is
+   essentially a slight variation on the export-key.py script.
+   """)
+
+   c = gpg.Context(armor=True)
+   server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+   if len(sys.argv) > 2:
+       logrus = " ".join(sys.argv[1:])
+   elif len(sys.argv) == 2:
+       logrus = sys.argv[1]
+   else:
+       logrus = input("Enter the UID matching the key(s) to send: ")
+
+   if len(logrus) > 0:
+       try:
+           export_result = c.key_export(pattern=logrus)
+       except Exception as e:
+           print(e)
+           export_result = None
+   else:
+       export_result = c.key_export(pattern=None)
+
+   if export_result is not None:
+       try:
+           try:
+               send_result = server.add(export_result)
+           except:
+               send_result = server.add(export_result.decode())
+           if send_result is not None:
+               print(send_result)
+           else:
+               pass
+       except Exception as e:
+           print(e)
+   else:
+       pass
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples directory
+as ``send-key-to-keyserver.py``.
+
+The ``hkp4py`` module appears to handle both string and byte literal
+text data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
+.. _howto-the-basics:
+
+Basic Functions
+===============
+
+The most frequently called features of any cryptographic library will be
+the most fundamental tasks for encryption software. In this section we
+will look at how to programmatically encrypt data, decrypt it, sign it
+and verify signatures.
+
+.. _howto-basic-encryption:
+
+Encryption
+----------
+
+Encrypting is very straight forward. In the first example below the
+message, ``text``, is encrypted to a single recipient\'s key. In the
+second example the message will be encrypted to multiple recipients.
+
+.. _howto-basic-encryption-single:
+
+Encrypting to one key
+~~~~~~~~~~~~~~~~~~~~~
+
+Once the the Context is set the main issues with encrypting data is
+essentially reduced to key selection and the keyword arguments specified
+in the ``gpg.Context().encrypt()`` method.
+
+Those keyword arguments are: ``recipients``, a list of keys encrypted to
+(covered in greater detail in the following section); ``sign``, whether
+or not to sign the plaintext data, see subsequent sections on signing
+and verifying signatures below (defaults to ``True``); ``sink``, to
+write results or partial results to a secure sink instead of returning
+it (defaults to ``None``); ``passphrase``, only used when utilising
+symmetric encryption (defaults to ``None``); ``always_trust``, used to
+override the trust model settings for recipient keys (defaults to
+``False``); ``add_encrypt_to``, utilises any preconfigured
+``encrypt-to`` or ``default-key`` settings in the user\'s ``gpg.conf``
+file (defaults to ``False``); ``prepare``, prepare for encryption
+(defaults to ``False``); ``expect_sign``, prepare for signing (defaults
+to ``False``); ``compress``, compresses the plaintext prior to
+encryption (defaults to ``True``).
+
+.. code:: python
+
+   import gpg
+
+   a_key = "0x12345678DEADBEEF"
+   text = b"""Some text to test with.
+
+   Since the text in this case must be bytes, it is most likely that
+   the input form will be a separate file which is opened with "rb"
+   as this is the simplest method of obtaining the correct data format.
+   """
+
+   c = gpg.Context(armor=True)
+   rkey = list(c.keylist(pattern=a_key, secret=False))
+   ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+   with open("secret_plans.txt.asc", "wb") as afile:
+       afile.write(ciphertext)
+
+Though this is even more likely to be used like this; with the plaintext
+input read from a file, the recipient keys used for encryption
+regardless of key trust status and the encrypted output also encrypted
+to any preconfigured keys set in the ``gpg.conf`` file:
+
+.. code:: python
+
+   import gpg
+
+   a_key = "0x12345678DEADBEEF"
+
+   with open("secret_plans.txt", "rb") as afile:
+       text = afile.read()
+
+   c = gpg.Context(armor=True)
+   rkey = list(c.keylist(pattern=a_key, secret=False))
+   ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
+                                               always_trust=True,
+                                               add_encrypt_to=True)
+
+   with open("secret_plans.txt.asc", "wb") as afile:
+       afile.write(ciphertext)
+
+If the ``recipients`` paramater is empty then the plaintext is encrypted
+symmetrically. If no ``passphrase`` is supplied as a parameter or via a
+callback registered with the ``Context()`` then an out-of-band prompt
+for the passphrase via pinentry will be invoked.
+
+.. _howto-basic-encryption-multiple:
+
+Encrypting to multiple keys
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Encrypting to multiple keys essentially just expands upon the key
+selection process and the recipients from the previous examples.
+
+The following example encrypts a message (``text``) to everyone with an
+email address on the ``gnupg.org`` domain, [4]_ but does *not* encrypt
+to a default key or other key which is configured to normally encrypt
+to.
+
+.. code:: python
+
+   import gpg
+
+   text = b"""Oh look, another test message.
+
+   The same rules apply as with the previous example and more likely
+   than not, the message will actually be drawn from reading the
+   contents of a file or, maybe, from entering data at an input()
+   prompt.
+
+   Since the text in this case must be bytes, it is most likely that
+   the input form will be a separate file which is opened with "rb"
+   as this is the simplest method of obtaining the correct data
+   format.
+   """
+
+   c = gpg.Context(armor=True)
+   rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+   logrus = []
+
+   for i in range(len(rpattern)):
+       if rpattern[i].can_encrypt == 1:
+           logrus.append(rpattern[i])
+
+   ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                               sign=False, always_trust=True)
+
+   with open("secret_plans.txt.asc", "wb") as afile:
+       afile.write(ciphertext)
+
+All it would take to change the above example to sign the message and
+also encrypt the message to any configured default keys would be to
+change the ``c.encrypt`` line to this:
+
+.. code:: python
+
+   ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                               always_trust=True,
+                                               add_encrypt_to=True)
+
+The only keyword arguments requiring modification are those for which
+the default values are changing. The default value of ``sign`` is
+``True``, the default of ``always_trust`` is ``False``, the default of
+``add_encrypt_to`` is ``False``.
+
+If ``always_trust`` is not set to ``True`` and any of the recipient keys
+are not trusted (e.g. not signed or locally signed) then the encryption
+will raise an error. It is possible to mitigate this somewhat with
+something more like this:
+
+.. code:: python
+
+   import gpg
+
+   with open("secret_plans.txt.asc", "rb") as afile:
+       text = afile.read()
+
+   c = gpg.Context(armor=True)
+   rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+   logrus = []
+
+   for i in range(len(rpattern)):
+       if rpattern[i].can_encrypt == 1:
+           logrus.append(rpattern[i])
+
+       try:
+           ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                       add_encrypt_to=True)
+       except gpg.errors.InvalidRecipients as e:
+           for i in range(len(e.recipients)):
+               for n in range(len(logrus)):
+                   if logrus[n].fpr == e.recipients[i].fpr:
+                       logrus.remove(logrus[n])
+                   else:
+                       pass
+           try:
+               ciphertext, result, sign_result = c.encrypt(text,
+                                                           recipients=logrus,
+                                                           add_encrypt_to=True)
+               with open("secret_plans.txt.asc", "wb") as afile:
+                   afile.write(ciphertext)
+           except:
+               pass
+
+This will attempt to encrypt to all the keys searched for, then remove
+invalid recipients if it fails and try again.
+
+.. _howto-basic-decryption:
+
+Decryption
+----------
+
+Decrypting something encrypted to a key in one\'s secret keyring is
+fairly straight forward.
+
+In this example code, however, preconfiguring either ``gpg.Context()``
+or ``gpg.core.Context()`` as ``c`` is unnecessary because there is no
+need to modify the Context prior to conducting the decryption and since
+the Context is only used once, setting it to ``c`` simply adds lines for
+no gain.
+
+.. code:: python
+
+   import gpg
+
+   ciphertext = input("Enter path and filename of encrypted file: ")
+   newfile = input("Enter path and filename of file to save decrypted data to: ")
+
+   with open(ciphertext, "rb") as cfile:
+       try:
+           plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+       except gpg.errors.GPGMEError as e:
+           plaintext = None
+           print(e)
+
+   if plaintext is not None:
+       with open(newfile, "wb") as nfile:
+           nfile.write(plaintext)
+       else:
+           pass
+
+The data available in ``plaintext`` in this example is the decrypted
+content as a byte object, the recipient key IDs and algorithms in
+``result`` and the results of verifying any signatures of the data in
+``verify_result``.
+
+.. _howto-basic-signing:
+
+Signing text and files
+----------------------
+
+The following sections demonstrate how to specify keys to sign with.
+
+.. _howto-basic-signing-signers:
+
+Signing key selection
+~~~~~~~~~~~~~~~~~~~~~
+
+By default GPGME and the Python bindings will use the default key
+configured for the user invoking the GPGME API. If there is no default
+key specified and there is more than one secret key available it may be
+necessary to specify the key or keys with which to sign messages and
+files.
+
+.. code:: python
+
+   import gpg
+
+   logrus = input("Enter the email address or string to match signing keys to: ")
+   hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+   sig_src = list(hancock)
+
+The signing examples in the following sections include the explicitly
+designated ``signers`` parameter in two of the five examples; once where
+the resulting signature would be ASCII armoured and once where it would
+not be armoured.
+
+While it would be possible to enter a key ID or fingerprint here to
+match a specific key, it is not possible to enter two fingerprints and
+match two keys since the patten expects a string, bytes or None and not
+a list. A string with two fingerprints won\'t match any single key.
+
+.. _howto-basic-signing-normal:
+
+Normal or default signing messages or files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The normal or default signing process is essentially the same as is most
+often invoked when also encrypting a message or file. So when the
+encryption component is not utilised, the result is to produce an
+encoded and signed output which may or may not be ASCII armoured and
+which may or may not also be compressed.
+
+By default compression will be used unless GnuPG detects that the
+plaintext is already compressed. ASCII armouring will be determined
+according to the value of ``gpg.Context().armor``.
+
+The compression algorithm is selected in much the same way as the
+symmetric encryption algorithm or the hash digest algorithm is when
+multiple keys are involved; from the preferences saved into the key
+itself or by comparison with the preferences with all other keys
+involved.
+
+.. code:: python
+
+   import gpg
+
+   text0 = """Declaration of ... something.
+
+   """
+   text = text0.encode()
+
+   c = gpg.Context(armor=True, signers=sig_src)
+   signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+   with open("/path/to/statement.txt.asc", "w") as afile:
+       afile.write(signed_data.decode())
+
+Though everything in this example is accurate, it is more likely that
+reading the input data from another file and writing the result to a new
+file will be performed more like the way it is done in the next example.
+Even if the output format is ASCII armoured.
+
+.. code:: python
+
+   import gpg
+
+   with open("/path/to/statement.txt", "rb") as tfile:
+       text = tfile.read()
+
+   c = gpg.Context()
+   signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+   with open("/path/to/statement.txt.sig", "wb") as afile:
+       afile.write(signed_data)
+
+.. _howto-basic-signing-detached:
+
+Detached signing messages and files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Detached signatures will often be needed in programmatic uses of GPGME,
+either for signing files (e.g. tarballs of code releases) or as a
+component of message signing (e.g. PGP/MIME encoded email).
+
+.. code:: python
+
+   import gpg
+
+   text0 = """Declaration of ... something.
+
+   """
+   text = text0.encode()
+
+   c = gpg.Context(armor=True)
+   signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+   with open("/path/to/statement.txt.asc", "w") as afile:
+       afile.write(signed_data.decode())
+
+As with normal signatures, detached signatures are best handled as byte
+literals, even when the output is ASCII armoured.
+
+.. code:: python
+
+   import gpg
+
+   with open("/path/to/statement.txt", "rb") as tfile:
+       text = tfile.read()
+
+   c = gpg.Context(signers=sig_src)
+   signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+   with open("/path/to/statement.txt.sig", "wb") as afile:
+       afile.write(signed_data)
+
+.. _howto-basic-signing-clear:
+
+Clearsigning messages or text
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Though PGP/in-line messages are no longer encouraged in favour of
+PGP/MIME, there is still sometimes value in utilising in-line
+signatures. This is where clear-signed messages or text is of value.
+
+.. code:: python
+
+   import gpg
+
+   text0 = """Declaration of ... something.
+
+   """
+   text = text0.encode()
+
+   c = gpg.Context()
+   signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+   with open("/path/to/statement.txt.asc", "w") as afile:
+       afile.write(signed_data.decode())
+
+In spite of the appearance of a clear-signed message, the data handled
+by GPGME in signing it must still be byte literals.
+
+.. code:: python
+
+   import gpg
+
+   with open("/path/to/statement.txt", "rb") as tfile:
+       text = tfile.read()
+
+   c = gpg.Context()
+   signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+   with open("/path/to/statement.txt.asc", "wb") as afile:
+       afile.write(signed_data)
+
+.. _howto-basic-verification:
+
+Signature verification
+----------------------
+
+Essentially there are two principal methods of verification of a
+signature. The first of these is for use with the normal or default
+signing method and for clear-signed messages. The second is for use with
+files and data with detached signatures.
+
+The following example is intended for use with the default signing
+method where the file was not ASCII armoured:
+
+.. code:: python
+
+   import gpg
+   import time
+
+   filename = "statement.txt"
+   gpg_file = "statement.txt.gpg"
+
+   c = gpg.Context()
+
+   try:
+       data, result = c.verify(open(gpg_file))
+       verified = True
+   except gpg.errors.BadSignatures as e:
+       verified = False
+       print(e)
+
+   if verified is True:
+       for i in range(len(result.signatures)):
+           sign = result.signatures[i]
+           print("""Good signature from:
+   {0}
+   with key {1}
+   made at {2}
+   """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+              time.ctime(sign.timestamp)))
+   else:
+       pass
+
+Whereas this next example, which is almost identical would work with
+normal ASCII armoured files and with clear-signed files:
+
+.. code:: python
+
+   import gpg
+   import time
+
+   filename = "statement.txt"
+   asc_file = "statement.txt.asc"
+
+   c = gpg.Context()
+
+   try:
+       data, result = c.verify(open(asc_file))
+       verified = True
+   except gpg.errors.BadSignatures as e:
+       verified = False
+       print(e)
+
+   if verified is True:
+       for i in range(len(result.signatures)):
+           sign = result.signatures[i]
+           print("""Good signature from:
+   {0}
+   with key {1}
+   made at {2}
+   """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+              time.ctime(sign.timestamp)))
+   else:
+       pass
+
+In both of the previous examples it is also possible to compare the
+original data that was signed against the signed data in ``data`` to see
+if it matches with something like this:
+
+.. code:: python
+
+   with open(filename, "rb") as afile:
+       text = afile.read()
+
+   if text == data:
+       print("Good signature.")
+   else:
+       pass
+
+The following two examples, however, deal with detached signatures. With
+his method of verification the data that was signed does not get
+returned since it is already being explicitly referenced in the first
+argument of ``c.verify``. So ``data`` is ``None`` and only the
+information in ``result`` is available.
+
+.. code:: python
+
+   import gpg
+   import time
+
+   filename = "statement.txt"
+   sig_file = "statement.txt.sig"
+
+   c = gpg.Context()
+
+   try:
+       data, result = c.verify(open(filename), open(sig_file))
+       verified = True
+   except gpg.errors.BadSignatures as e:
+       verified = False
+       print(e)
+
+   if verified is True:
+       for i in range(len(result.signatures)):
+           sign = result.signatures[i]
+           print("""Good signature from:
+   {0}
+   with key {1}
+   made at {2}
+   """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+              time.ctime(sign.timestamp)))
+   else:
+       pass
+
+.. code:: python
+
+   import gpg
+   import time
+
+   filename = "statement.txt"
+   asc_file = "statement.txt.asc"
+
+   c = gpg.Context()
+
+   try:
+       data, result = c.verify(open(filename), open(asc_file))
+       verified = True
+   except gpg.errors.BadSignatures as e:
+       verified = False
+       print(e)
+
+   if verified is True:
+       for i in range(len(result.signatures)):
+           sign = result.signatures[i]
+           print("""Good signature from:
+   {0}
+   with key {1}
+   made at {2}
+   """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+              time.ctime(sign.timestamp)))
+   else:
+       pass
+
+.. _key-generation:
+
+Creating keys and subkeys
+=========================
+
+The one thing, aside from GnuPG itself, that GPGME depends on, of
+course, is the keys themselves. So it is necessary to be able to
+generate them and modify them by adding subkeys, revoking or disabling
+them, sometimes deleting them and doing the same for user IDs.
+
+In the following examples a key will be created for the world\'s
+greatest secret agent, Danger Mouse. Since Danger Mouse is a secret
+agent he needs to be able to protect information to ``SECRET`` level
+clearance, so his keys will be 3072-bit keys.
+
+The pre-configured ``gpg.conf`` file which sets cipher, digest and other
+preferences contains the following configuration parameters:
+
+.. code:: conf
+
+   expert
+   allow-freeform-uid
+   allow-secret-key-import
+   trust-model tofu+pgp
+   tofu-default-policy unknown
+   enable-large-rsa
+   enable-dsa2
+   cert-digest-algo SHA512
+   default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+   personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+   personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+   personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+
+.. _keygen-primary:
+
+Primary key
+-----------
+
+Generating a primary key uses the ``create_key`` method in a Context. It
+contains multiple arguments and keyword arguments, including:
+``userid``, ``algorithm``, ``expires_in``, ``expires``, ``sign``,
+``encrypt``, ``certify``, ``authenticate``, ``passphrase`` and
+``force``. The defaults for all of those except ``userid``,
+``algorithm``, ``expires_in``, ``expires`` and ``passphrase`` is
+``False``. The defaults for ``algorithm`` and ``passphrase`` is
+``None``. The default for ``expires_in`` is ``0``. The default for
+``expires`` is ``True``. There is no default for ``userid``.
+
+If ``passphrase`` is left as ``None`` then the key will not be generated
+with a passphrase, if ``passphrase`` is set to a string then that will
+be the passphrase and if ``passphrase`` is set to ``True`` then
+gpg-agent will launch pinentry to prompt for a passphrase. For the sake
+of convenience, these examples will keep ``passphrase`` set to ``None``.
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+
+   c.home_dir = "~/.gnupg-dm"
+   userid = "Danger Mouse <dm@secret.example.net>"
+
+   dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
+                        sign=True, certify=True)
+
+One thing to note here is the use of setting the ``c.home_dir``
+parameter. This enables generating the key or keys in a different
+location. In this case to keep the new key data created for this example
+in a separate location rather than adding it to existing and active key
+store data. As with the default directory, ``~/.gnupg``, any temporary
+or separate directory needs the permissions set to only permit access by
+the directory owner. On posix systems this means setting the directory
+permissions to 700.
+
+The ``temp-homedir-config.py`` script in the HOWTO examples directory
+will create an alternative homedir with these configuration options
+already set and the correct directory and file permissions.
+
+The successful generation of the key can be confirmed via the returned
+``GenkeyResult`` object, which includes the following data:
+
+.. code:: python
+
+   print("""
+    Fingerprint:  {0}
+    Primary Key:  {1}
+     Public Key:  {2}
+     Secret Key:  {3}
+    Sub Key:  {4}
+   User IDs:  {5}
+   """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
+              dmkey.uid))
+
+Alternatively the information can be confirmed using the command line
+program:
+
+.. code:: shell
+
+   bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+   ~/.gnupg-dm/pubring.kbx
+   ----------------------
+   sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+     177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+   uid           [ultimate] Danger Mouse <dm@secret.example.net>
+
+   bash-4.4$
+
+As with generating keys manually, to preconfigure expanded preferences
+for the cipher, digest and compression algorithms, the ``gpg.conf`` file
+must contain those details in the home directory in which the new key is
+being generated. I used a cut down version of my own ``gpg.conf`` file
+in order to be able to generate this:
+
+.. code:: shell
+
+   bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
+   Secret key is available.
+
+   sec  rsa3072/026D2F19E99E63AA
+        created: 2018-03-15  expires: 2019-03-15  usage: SC
+        trust: ultimate      validity: ultimate
+   [ultimate] (1). Danger Mouse <dm@secret.example.net>
+
+   [ultimate] (1). Danger Mouse <dm@secret.example.net>
+        Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
+        Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
+        Compression: ZLIB, BZIP2, ZIP, Uncompressed
+        Features: MDC, Keyserver no-modify
+
+   bash-4.4$
+
+.. _keygen-subkeys:
+
+Subkeys
+-------
+
+Adding subkeys to a primary key is fairly similar to creating the
+primary key with the ``create_subkey`` method. Most of the arguments are
+the same, but not quite all. Instead of the ``userid`` argument there is
+now a ``key`` argument for selecting which primary key to add the subkey
+to.
+
+In the following example an encryption subkey will be added to the
+primary key. Since Danger Mouse is a security conscious secret agent,
+this subkey will only be valid for about six months, half the length of
+the primary key.
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+   c.home_dir = "~/.gnupg-dm"
+
+   key = c.get_key(dmkey.fpr, secret=True)
+   dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
+                           encrypt=True)
+
+As with the primary key, the results here can be checked with:
+
+.. code:: python
+
+   print("""
+    Fingerprint:  {0}
+    Primary Key:  {1}
+     Public Key:  {2}
+     Secret Key:  {3}
+    Sub Key:  {4}
+   User IDs:  {5}
+   """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
+              dmsub.uid))
+
+As well as on the command line with:
+
+.. code:: shell
+
+   bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+   ~/.gnupg-dm/pubring.kbx
+   ----------------------
+   sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+     177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+   uid           [ultimate] Danger Mouse <dm@secret.example.net>
+   ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+   bash-4.4$
+
+.. _keygen-uids:
+
+User IDs
+--------
+
+.. _keygen-uids-add:
+
+Adding User IDs
+~~~~~~~~~~~~~~~
+
+By comparison to creating primary keys and subkeys, adding a new user ID
+to an existing key is much simpler. The method used to do this is
+``key_add_uid`` and the only arguments it takes are for the ``key`` and
+the new ``uid``.
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+   c.home_dir = "~/.gnupg-dm"
+
+   dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+   key = c.get_key(dmfpr, secret=True)
+   uid = "Danger Mouse <danger.mouse@secret.example.net>"
+
+   c.key_add_uid(key, uid)
+
+Unsurprisingly the result of this is:
+
+.. code:: shell
+
+   bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+   ~/.gnupg-dm/pubring.kbx
+   ----------------------
+   sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+     177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+   uid           [ultimate] Danger Mouse <danger.mouse@secret.example.net>
+   uid           [ultimate] Danger Mouse <dm@secret.example.net>
+   ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+   bash-4.4$
+
+.. _keygen-uids-revoke:
+
+Revokinging User IDs
+~~~~~~~~~~~~~~~~~~~~
+
+Revoking a user ID is a fairly similar process, except that it uses the
+``key_revoke_uid`` method.
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+   c.home_dir = "~/.gnupg-dm"
+
+   dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+   key = c.get_key(dmfpr, secret=True)
+   uid = "Danger Mouse <danger.mouse@secret.example.net>"
+
+   c.key_revoke_uid(key, uid)
+
+.. _key-sign:
+
+Key certification
+-----------------
+
+Since key certification is more frequently referred to as key signing,
+the method used to perform this function is ``key_sign``.
+
+The ``key_sign`` method takes four arguments: ``key``, ``uids``,
+``expires_in`` and ``local``. The default value of ``uids`` is ``None``
+and which results in all user IDs being selected. The default value of
+both ``expires_in`` and ``local`` is ``False``; which results in the
+signature never expiring and being able to be exported.
+
+The ``key`` is the key being signed rather than the key doing the
+signing. To change the key doing the signing refer to the signing key
+selection above for signing messages and files.
+
+If the ``uids`` value is not ``None`` then it must either be a string to
+match a single user ID or a list of strings to match multiple user IDs.
+In this case the matching of those strings must be precise and it is
+case sensitive.
+
+To sign Danger Mouse\'s key for just the initial user ID with a
+signature which will last a little over a month, do this:
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+   uid = "Danger Mouse <dm@secret.example.net>"
+
+   dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+   key = c.get_key(dmfpr, secret=True)
+   c.key_sign(key, uids=uid, expires_in=2764800)
+
+.. _advanced-use:
+
+Advanced or Experimental Use Cases
+==================================
+
+.. _cython:
+
+C plus Python plus SWIG plus Cython
+-----------------------------------
+
+In spite of the apparent incongruence of using Python bindings to a C
+interface only to generate more C from the Python; it is in fact quite
+possible to use the GPGME bindings with
+`Cython <http://docs.cython.org/en/latest/index.html>`__. Though in many
+cases the benefits may not be obvious since the most computationally
+intensive work never leaves the level of the C code with which GPGME
+itself is interacting with.
+
+Nevertheless, there are some situations where the benefits are
+demonstrable. One of the better and easier examples being the one of the
+early examples in this HOWTO, the `key
+counting <#howto-keys-counting>`__ code. Running that example as an
+executable Python script, ``keycount.py`` (available in the
+``examples/howto/`` directory), will take a noticable amount of time to
+run on most systems where the public keybox or keyring contains a few
+thousand public keys.
+
+Earlier in the evening, prior to starting this section, I ran that
+script on my laptop; as I tend to do periodically and timed it using
+``time`` utility, with the following results:
+
+.. code:: shell
+
+   bash-4.4$ time keycount.py
+
+   Number of secret keys:  23
+   Number of public keys:  12112
+
+
+   real  11m52.945s
+   user  0m0.913s
+   sys   0m0.752s
+
+   bash-4.4$
+
+Sometime after that I imported another key and followed it with a little
+test of Cython. This test was kept fairly basic, essentially lifting the
+material from the `Cython Basic
+Tutorial <http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html>`__
+to demonstrate compiling Python code to C. The first step was to take
+the example key counting code quoted previously, essentially from the
+importing of the ``gpg`` module to the end of the script:
+
+.. code:: python
+
+   import gpg
+
+   c = gpg.Context()
+   seckeys = c.keylist(pattern=None, secret=True)
+   pubkeys = c.keylist(pattern=None, secret=False)
+
+   seclist = list(seckeys)
+   secnum = len(seclist)
+
+   publist = list(pubkeys)
+   pubnum = len(publist)
+
+   print("""
+       Number of secret keys:  {0}
+       Number of public keys:  {1}
+
+   """.format(secnum, pubnum))
+
+Save that into a file called ``keycount.pyx`` and then create a
+``setup.py`` file which contains this:
+
+.. code:: python
+
+   from distutils.core import setup
+   from Cython.Build import cythonize
+
+   setup(
+       ext_modules = cythonize("keycount.pyx")
+   )
+
+Compile it:
+
+.. code:: shell
+
+   bash-4.4$ python setup.py build_ext --inplace
+   bash-4.4$
+
+Then run it in a similar manner to ``keycount.py``:
+
+.. code:: shell
+
+   bash-4.4$ time python3.7 -c "import keycount"
+
+   Number of secret keys:  23
+   Number of public keys:  12113
+
+
+   real  6m47.905s
+   user  0m0.785s
+   sys   0m0.331s
+
+   bash-4.4$
+
+Cython turned ``keycount.pyx`` into an 81KB ``keycount.o`` file in the
+``build/`` directory, a 24KB ``keycount.cpython-37m-darwin.so`` file to
+be imported into Python 3.7 and a 113KB ``keycount.c`` generated C
+source code file of nearly three thousand lines. Quite a bit bigger than
+the 314 bytes of the ``keycount.pyx`` file or the full 1,452 bytes of
+the full executable ``keycount.py`` example script.
+
+On the other hand it ran in nearly half the time; taking 6 minutes and
+47.905 seconds to run. As opposed to the 11 minutes and 52.945 seconds
+which the CPython script alone took.
+
+The ``keycount.pyx`` and ``setup.py`` files used to generate this
+example have been added to the ``examples/howto/advanced/cython/``
+directory The example versions include some additional options to
+annotate the existing code and to detect Cython\'s use. The latter comes
+from the `Magic
+Attributes <http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd>`__
+section of the Cython documentation.
+
+.. _cheats-and-hacks:
+
+Miscellaneous extras and work-arounds
+=====================================
+
+Most of the things in the following sections are here simply because
+there was no better place to put them, even though some are only
+peripherally related to the GPGME Python bindings. Some are also
+workarounds for functions not integrated with GPGME as yet. This is
+especially true of the first of these, dealing with `group
+lines <#group-lines>`__.
+
+Group lines
+-----------
+
+There is not yet an easy way to access groups configured in the gpg.conf
+file from within GPGME. As a consequence these central groupings of keys
+cannot be shared amongst multiple programs, such as MUAs readily.
+
+The following code, however, provides a work-around for obtaining this
+information in Python.
+
+.. code:: python
+
+   import subprocess
+   import sys
+
+   if sys.platform == "win32":
+       gpgconfcmd = "gpgconf.exe --list-options gpg"
+   else:
+       gpgconfcmd = "gpgconf --list-options gpg"
+
+   try:
+       lines = subprocess.getoutput(gpgconfcmd).splitlines()
+   except:
+       process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+       procom = process.communicate()
+       if sys.version_info[0] == 2:
+           lines = procom[0].splitlines()
+       else:
+           lines = procom[0].decode().splitlines()
+
+   for i in range(len(lines)):
+       if lines[i].startswith("group") is True:
+           line = lines[i]
+       else:
+           pass
+
+   groups = line.split(":")[-1].replace('"', '').split(',')
+
+   group_lines = []
+   group_lists = []
+
+   for i in range(len(groups)):
+       group_lines.append(groups[i].split("="))
+       group_lists.append(groups[i].split("="))
+
+   for i in range(len(group_lists)):
+       group_lists[i][1] = group_lists[i][1].split()
+
+The result of that code is that ``group_lines`` is a list of lists where
+``group_lines[i][0]`` is the name of the group and ``group_lines[i][1]``
+is the key IDs of the group as a string.
+
+The ``group_lists`` result is very similar in that it is a list of
+lists. The first part, ``group_lists[i][0]`` matches
+``group_lines[i][0]`` as the name of the group, but
+``group_lists[i][1]`` is the key IDs of the group as a string.
+
+A demonstration of using the ``groups.py`` module is also available in
+the form of the executable ``mutt-groups.py`` script. This second script
+reads all the group entries in a user\'s ``gpg.conf`` file and converts
+them into crypt-hooks suitable for use with the Mutt and Neomutt mail
+clients.
+
+.. _hkp4py:
+
+Keyserver access for Python
+---------------------------
+
+The `hkp4py <https://github.com/Selfnet/hkp4py>`__ module by Marcel Fest
+was originally a port of the old
+`python-hkp <https://github.com/dgladkov/python-hkp>`__ module from
+Python 2 to Python 3 and updated to use the
+`requests <http://docs.python-requests.org/en/latest/index.html>`__
+module instead. It has since been modified to provide support for Python
+2.7 as well and is available via PyPI.
+
+Since it rewrites the ``hkp`` protocol prefix as ``http`` and ``hkps``
+as ``https``, the module is able to be used even with servers which do
+not support the full scope of keyserver functions. [5]_ It also works
+quite readily when incorporated into a `Cython <#cython>`__ generated
+and compiled version of any code.
+
+.. _hkp4py-strings:
+
+Key import format
+~~~~~~~~~~~~~~~~~
+
+The hkp4py module returns key data via requests as string literals
+(``r.text``) instead of byte literals (``r.content``). This means that
+the retrurned key data must be encoded to UTF-8 when importing that key
+material using a ``gpg.Context().key_import()`` method.
+
+For this reason an alternative method has been added to the ``search``
+function of ``hkp4py.KeyServer()`` which returns the key in the correct
+format as expected by ``key_import``. When importing using this module,
+it is now possible to import with this:
+
+.. code:: python
+
+   for key in keys:
+       if key.revoked is False:
+           gpg.Context().key_import(key.key_blob)
+       else:
+           pass
+
+Without that recent addition it would have been necessary to encode the
+contents of each ``hkp4py.KeyServer().search()[i].key`` in
+``hkp4py.KeyServer().search()`` before trying to import it.
+
+An example of this is included in the `Importing
+Keys <#howto-import-key>`__ section of this HOWTO and the corresponding
+executable version of that example is available in the
+``lang/python/examples/howto`` directory as normal; the executable
+version is the ``import-keys-hkp.py`` file.
+
+.. _copyright-and-license:
+
+Copyright and Licensing
+=======================
+
+Copyright
+---------
+
+Copyright © The GnuPG Project, 2018.
+
+Copyright (C) The GnuPG Project, 2018.
+
+.. _draft-editions:
+
+Draft Editions of this HOWTO
+----------------------------
+
+Draft editions of this HOWTO may be periodically available directly from
+the author at any of the following URLs:
+
+-  `GPGME Python Bindings HOWTO draft (XHTML AWS S3
+   SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.html>`__
+-  `GPGME Python Bindings HOWTO draft (XHTML AWS S3 no
+   SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.html>`__
+-  `GPGME Python Bindings HOWTO draft (Texinfo file AWS S3
+   SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.texi>`__
+-  `GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no
+   SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.texi>`__
+-  `GPGME Python Bindings HOWTO draft (Info file AWS S3
+   SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.info>`__
+-  `GPGME Python Bindings HOWTO draft (Info file AWS S3 no
+   SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.info>`__
+-  `GPGME Python Bindings HOWTO draft (reST file AWS S3
+   SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.rst>`__
+-  `GPGME Python Bindings HOWTO draft (reST file AWS S3 no
+   SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.rst>`__
+-  `GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3
+   SSL) <https://files.au.adversary.org/crypto/gpgme-python-howto.xml>`__
+-  `GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no
+   SSL) <http://files.au.adversary.org/crypto/gpgme-python-howto.xml>`__
+
+All of these draft versions except for one have been generated from this
+document via Emacs `Org mode <https://orgmode.org/>`__ and `GNU
+Texinfo <https://www.gnu.org/software/texinfo/>`__. Though it is likely
+that the specific
+`file <https://files.au.adversary.org/crypto/gpgme-python-howto.org>`__
+`version <http://files.au.adversary.org/crypto/gpgme-python-howto.org>`__
+used will be on the same server with the generated output formats.
+
+The one exception is the reStructuredText version, which was converted
+using the latest version of Pandoc from the Org mode source file using
+the following command:
+
+.. code:: shell
+
+   pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org
+
+In addition to these there is a significantly less frequently updated
+version as a HTML `WebHelp
+site <https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html>`__
+(AWS S3 SSL); generated from DITA XML source files, which can be found
+in `an alternative
+branch <https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/>`__
+of the GPGME git repository.
+
+These draft editions are not official documents and the version of
+documentation in the master branch or which ships with released versions
+is the only official documentation. Nevertheless, these draft editions
+may occasionally be of use by providing more accessible web versions
+which are updated between releases. They are provided on the
+understanding that they may contain errors or may contain content
+subject to change prior to an official release.
+
+.. _license:
+
+License GPL compatible
+----------------------
+
+This file is free software; as a special exception the author gives
+unlimited permission to copy and/or distribute it, with or without
+modifications, as long as this notice is preserved.
+
+This file is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY, to the extent permitted by law; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+Footnotes
+=========
+
+.. [1]
+   ``short-history.org`` and/or ``short-history.html``.
+
+.. [2]
+   With no issues reported specific to Python 3.7, the release of Python
+   3.7.1 at around the same time as GPGME 1.12.0 and the testing with
+   Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of 3.6
+   now. Production environments with more conservative requirements will
+   always enforce their own policies anyway and installation to each
+   supported minor release is quite possible too.
+
+.. [3]
+   Yes, even if you use virtualenv with everything you do in Python. If
+   you want to install this module as just your user account then you
+   will need to manually configure, compile and install the *entire*
+   GnuPG stack as that user as well. This includes libraries which are
+   not often installed that way. It can be done and there are
+   circumstances under which it is worthwhile, but generally only on
+   POSIX systems which utilise single user mode (some even require it).
+
+.. [4]
+   You probably don\'t really want to do this. Searching the keyservers
+   for \"gnupg.org\" produces over 400 results, the majority of which
+   aren\'t actually at the gnupg.org domain, but just included a comment
+   regarding the project in their key somewhere.
+
+.. [5]
+   Such as with ProtonMail servers. This also means that restricted
+   servers which only advertise either HTTP or HTTPS end points and not
+   HKP or HKPS end points must still be identified as as HKP or HKPS
+   within the Python Code. The ``hkp4py`` module will rewrite these
+   appropriately when the connection is made to the server.
diff --git a/lang/python/doc/rst/index.rst b/lang/python/doc/rst/index.rst
new file mode 100644 (file)
index 0000000..31dc146
--- /dev/null
@@ -0,0 +1,12 @@
+.. _index:
+
+GPGME Python Bindings
+=====================
+
+.. _index-contents:
+
+Contents
+--------
+
+-  `A short history of the project <short-history.org>`__
+-  `GPGME Python Bindings HOWTO <gpgme-python-howto.org>`__
diff --git a/lang/python/doc/rst/short-history.rst b/lang/python/doc/rst/short-history.rst
new file mode 100644 (file)
index 0000000..8cf604f
--- /dev/null
@@ -0,0 +1,152 @@
+Overview
+========
+
+The GPGME Python bindings passed through many hands and numerous phases
+before, after a fifteen year journey, coming full circle to return to
+the source. This is a short explanation of that journey.
+
+.. _in-the-begining:
+
+In the beginning
+----------------
+
+In 2002 John Goerzen released PyME; Python bindings for the GPGME module
+which utilised the current release of Python of the time and SWIG. [1]_
+Shortly after creating it and ensuring it worked he stopped supporting
+it, though he left his work available on his Gopher site.
+
+Keeping the flame alive
+-----------------------
+
+A couple of years later the project was picked up by Igor Belyi and
+actively developed and maintained by him from 2004 to 2008. Igor's
+whereabouts at the time of this document's creation are unknown, but the
+current authors do hope he is well. We're assuming (or hoping) that life
+did what life does and made continuing untenable.
+
+Passing the torch
+-----------------
+
+In 2014 Martin Albrecht wanted to patch a bug in the PyME code and
+discovered the absence of Igor. Following a discussion on the PyME
+mailing list he became the new maintainer for PyME, releasing version
+0.9.0 in May of that year. He remains the maintainer of the original
+PyME release in Python 2.6 and 2.7 (available via PyPI).
+
+.. _ouroboros:
+
+Coming full circle
+------------------
+
+In 2015 Ben McGinnes approached Martin about a Python 3 version, while
+investigating how complex a task this would be the task ended up being
+completed. A subsequent discussion with Werner Koch led to the decision
+to fold the Python 3 port back into the original GPGME release in the
+languages subdirectory for non-C bindings under the module name of
+``pyme3``.
+
+In 2016 this PyME module was integrated back into the GPGME project by
+Justus Winter. During the course of this work Justus adjusted the port
+to restore limited support for Python 2, but not as many minor point
+releases as the original PyME package supports. During the course of
+this integration the package was renamed to more accurately reflect its
+status as a component of GPGME. The ``pyme3`` module was renamed to
+``gpg`` and adopted by the upstream GnuPG team.
+
+In 2017 Justus departed G10code and the GnuPG team. Following this Ben
+returned to maintain of gpgme Python bindings and continue building them
+from that point.
+
+.. _relics-past:
+
+Relics of the past
+==================
+
+There are a few things, in addition to code specific factors, such as
+SWIG itself, which are worth noting here.
+
+The Annoyances of Git
+---------------------
+
+As anyone who has ever worked with git knows, submodules are horrible
+way to deal with pretty much anything. In the interests of avoiding
+migraines, that was skipped with addition of the PyME code to GPGME.
+
+Instead the files were added to a subdirectory of the ``lang/``
+directory, along with a copy of the entire git log up to that point as a
+separate file within the ``lang/python/docs/`` directory. [2]_ As the
+log for PyME is nearly 100KB and the log for GPGME is approximately 1MB,
+this would cause considerable bloat, as well as some confusion, should
+the two be merged.
+
+Hence the unfortunate, but necessary, step to simply move the files. A
+regular repository version has been maintained should it be possible to
+implement this better in the future.
+
+The Perils of PyPI
+------------------
+
+The early port of the Python 2 ``pyme`` module as ``pyme3`` was never
+added to PyPI while the focus remained on development and testing during
+2015 and early 2016. Later in 2016, however, when Justus completed his
+major integration work and subsequently renamed the module from
+``pyme3`` to ``gpg``, some prior releases were also provided through
+PyPI.
+
+Since these bindings require a matching release of the GPGME libraries
+in order to function, it was determined that there was little benefit in
+also providing a copy through PyPI since anyone obtaining the GPGME
+source code would obtain the Python bindings source code at the same
+time. Whereas there was the potential to sew confusion amongst Python
+users installing the module from PyPI, only to discover that without the
+relevant C files, header files or SWIG compiled binaries, the Python
+module did them little good.
+
+There are only two files on PyPI which might turn up in a search for
+this module or a sample of its content:
+
+#. gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library
+#. pyme (0.9.0) - Python support for GPGME GnuPG cryptography library
+
+.. _pypi-gpgme-180:
+
+GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the most recent version to reach PyPI and is the version of the
+official Pyhon bindings which shipped with GPGME 1.8.0. If you have
+GPGME 1.8.0 installed and *only* 1.8.0 installed, then it is probably
+safe to use this copy from PyPI.
+
+As there have been a lot of changes since the release of GPGME 1.8.0,
+the GnuPG Project recommends not using this version of the module and
+instead installing the current version of GPGME along with the Python
+bindings included with that package.
+
+.. _pypi-gpgme-90:
+
+PyME 0·9·0 - Python support for GPGME GnuPG cryptography library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the last release of the PyME bindings maintained by Martin
+Albrecht and is only compatible with Python 2, it will not work with
+Python 3. This is the version of the software from which the port from
+Python 2 to Python 3 code was made in 2015.
+
+Users of the more recent Python bindings will recognise numerous points
+of similarity, but also significant differences. It is likely that the
+more recent official bindings will feel "more pythonic."
+
+For those using Python 2, there is essentially no harm in using this
+module, but it may lack a number of more recent features added to GPGME.
+
+Footnotes
+=========
+
+.. [1]
+   In all likelihood thos would have been Python 2.2 or possibly Python
+   2.3.
+
+.. [2]
+   The entire PyME git log and other preceding VCS logs are located in
+   the ``gpgme/lang/python/docs/old-commits.log`` file.
diff --git a/lang/python/doc/src/gpgme-python-howto.org b/lang/python/doc/src/gpgme-python-howto.org
new file mode 100644 (file)
index 0000000..caa8e2f
--- /dev/null
@@ -0,0 +1,3043 @@
+#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
+#+AUTHOR: Ben McGinnes
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman}
+#+LATEX_HEADER: \author{Ben McGinnes <ben@gnupg.org>}
+
+
+* Introduction
+  :PROPERTIES:
+  :CUSTOM_ID: intro
+  :END:
+
+| Version:        | 0.1.4                                    |
+| GPGME Version:  | 1.12.0                                   |
+| Author:         | [[https://gnupg.org/people/index.html#sec-1-5][Ben McGinnes]] <ben@gnupg.org>             |
+| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
+| Language:       | Australian English, British English      |
+| xml:lang:       | en-AU, en-GB, en                         |
+
+This document provides basic instruction in how to use the GPGME
+Python bindings to programmatically leverage the GPGME library.
+
+
+** Python 2 versus Python 3
+   :PROPERTIES:
+   :CUSTOM_ID: py2-vs-py3
+   :END:
+
+Though the GPGME Python bindings themselves provide support for both
+Python 2 and 3, the focus is unequivocally on Python 3 and
+specifically from Python 3.4 and above.  As a consequence all the
+examples and instructions in this guide use Python 3 code.
+
+Much of it will work with Python 2, but much of it also deals with
+Python 3 byte literals, particularly when reading and writing data.
+Developers concentrating on Python 2.7, and possibly even 2.6, will
+need to make the appropriate modifications to support the older string
+and unicode types as opposed to bytes.
+
+There are multiple reasons for concentrating on Python 3; some of
+which relate to the immediate integration of these bindings, some of
+which relate to longer term plans for both GPGME and the python
+bindings and some of which relate to the impending EOL period for
+Python 2.7.  Essentially, though, there is little value in tying the
+bindings to a version of the language which is a dead end and the
+advantages offered by Python 3 over Python 2 make handling the data
+types with which GPGME deals considerably easier.
+
+
+** Examples
+   :PROPERTIES:
+   :CUSTOM_ID: howto-python3-examples
+   :END:
+
+All of the examples found in this document can be found as Python 3
+scripts in the =lang/python/examples/howto= directory.
+
+
+** Unofficial Drafts
+   :PROPERTIES:
+   :CUSTOM_ID: unofficial-drafts
+   :END:
+
+In addition to shipping with each release of GPGME, there is a section
+on locations to read or download [[#draft-editions][draft editions]] of this document from
+at the end of it.  These are unofficial versions produced in between
+major releases.
+
+
+** What's New
+   :PROPERTIES:
+   :CUSTOM_ID: new-stuff
+   :END:
+
+The most obviously new point for those reading this guide is this
+section on other new things, but that's hardly important.  Not given
+all the other things which spurred the need for adding this section
+and its subsections.
+
+*** New in GPGME 1·12·0
+    :PROPERTIES:
+    :CUSTOM_ID: new-stuff-1-12-0
+    :END:
+
+There have been quite a number of additions to GPGME and the Python
+bindings to it since the last release of GPGME with versions 1.11.0
+and 1.11.1 in April, 2018.
+
+The bullet points of new additiions are:
+
+- an expanded section on [[#installation][installing]] and [[#snafu][troubleshooting]] the Python
+  bindings.
+- The release of Python 3.7.0; which appears to be working just fine
+  with our bindings, in spite of intermittent reports of problems for
+  many other Python projects with that new release.
+- Python 3.7 has been moved to the head of the specified python
+  versions list in the build process.
+- In order to fix some other issues, there are certain underlying
+  functions which are more exposed through the [[#howto-get-context][gpg.Context()]], but
+  ongoing documentation ought to clarify that or otherwise provide the
+  best means of using the bindings.  Some additions to =gpg.core= and
+  the =Context()=, however, were intended (see below).
+- Continuing work in identifying and confirming the cause of
+  oft-reported [[#snafu-runtime-not-funtime][problems installing the Python bindings on Windows]].
+- GSOC: Google's Surreptitiously Ordered Conscription ... erm ... oh,
+  right; Google's Summer of Code.  Though there were two hopeful
+  candidates this year; only one ended up involved with the GnuPG
+  Project directly, the other concentrated on an unrelated third party
+  project with closer ties to one of the GNU/Linux distributions than
+  to the GnuPG Project.  Thus the Python bindings benefited from GSOC
+  participant Jacob Adams, who added the key_import function; building
+  on prior work by Tobias Mueller.
+- Several new methods functions were added to the gpg.Context(),
+  including: [[#howto-import-key][key_import]], [[#howto-export-key][key_export]], [[#howto-export-public-key][key_export_minimal]] and
+  [[#howto-export-secret-key][key_export_secret]].
+- Importing and exporting examples include versions integrated with
+  Marcel Fest's recently released [[https://github.com/Selfnet/hkp4py][HKP for Python]] module.  Some
+  [[#hkp4py][additional notes on this module]] are included at the end of the HOWTO.
+- Instructions for dealing with semi-walled garden implementations
+  like ProtonMail are also included.  This is intended to make things
+  a little easier when communicating with users of ProtonMail's
+  services and should not be construed as an endorsement of said
+  service.  The GnuPG Project neither favours, nor disfavours
+  ProtonMail and the majority of this deals with interacting with the
+  ProtonMail keyserver.
+- Semi-formalised the location where [[#draft-editions][draft versions]] of this HOWTO may
+  periodically be accessible.  This is both for the reference of
+  others and testing the publishing of the document itself.  Renamed
+  this file at around the same time.
+- The Texinfo documentation build configuration has been replicated
+  from the parent project in order to make to maintain consistency
+  with that project (and actually ship with each release).
+- a reStructuredText (=.rst=) version is also generated for Python
+  developers more used to and comfortable with that format as it is
+  the standard Python documentation format and Python developers may
+  wish to use it with Sphinx.  Please note that there has been no
+  testing of the reStructuredText version with Sphinx at all.  The
+  reST file was generated by the simple expedient of using [[https://pandoc.org/][Pandoc]].
+- Added a new section for [[#advanced-use][advanced or experimental use]].
+- Began the advanced use cases with [[#cython][a section]] on using the module with
+  [[http://cython.org/][Cython]].
+- Added a number of new scripts to the =example/howto/= directory;
+  some of which may be in advance of their planned sections of the
+  HOWTO (and some are just there because it seemed like a good idea at
+  the time).
+- Cleaned up a lot of things under the hood.
+
+
+* GPGME Concepts
+  :PROPERTIES:
+  :CUSTOM_ID: gpgme-concepts
+  :END:
+
+
+** A C API
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-c-api
+   :END:
+
+Unlike many modern APIs with which programmers will be more familiar
+with these days, the GPGME API is a C API.  The API is intended for
+use by C coders who would be able to access its features by including
+the =gpgme.h= header file with their own C source code and then access
+its functions just as they would any other C headers.
+
+This is a very effective method of gaining complete access to the API
+and in the most efficient manner possible.  It does, however, have the
+drawback that it cannot be directly used by other languages without
+some means of providing an interface to those languages.  This is
+where the need for bindings in various languages stems.
+
+
+** Python bindings
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-python-bindings
+   :END:
+
+The Python bindings for GPGME provide a higher level means of
+accessing the complete feature set of GPGME itself.  It also provides
+a more pythonic means of calling these API functions.
+
+The bindings are generated dynamically with SWIG and the copy of
+=gpgme.h= generated when GPGME is compiled.
+
+This means that a version of the Python bindings is fundamentally tied
+to the exact same version of GPGME used to generate that copy of
+=gpgme.h=.
+
+
+** Difference between the Python bindings and other GnuPG Python packages
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-python-bindings-diffs
+   :END:
+
+There have been numerous attempts to add GnuPG support to Python over
+the years.  Some of the most well known are listed here, along with
+what differentiates them.
+
+
+*** The python-gnupg package maintained by Vinay Sajip
+    :PROPERTIES:
+    :CUSTOM_ID: diffs-python-gnupg
+    :END:
+
+This is arguably the most popular means of integrating GPG with
+Python.  The package utilises the =subprocess= module to implement
+wrappers for the =gpg= and =gpg2= executables normally invoked on the
+command line (=gpg.exe= and =gpg2.exe= on Windows).
+
+The popularity of this package stemmed from its ease of use and
+capability in providing the most commonly required features.
+
+Unfortunately it has been beset by a number of security issues in the
+past; most of which stemmed from using unsafe methods of accessing the
+command line via the =subprocess= calls.  While some effort has been
+made over the last two to three years (as of 2018) to mitigate this,
+particularly by no longer providing shell access through those
+subprocess calls, the wrapper is still somewhat limited in the scope
+of its GnuPG features coverage.
+
+The python-gnupg package is available under the MIT license.
+
+
+*** The gnupg package created and maintained by Isis Lovecruft
+    :PROPERTIES:
+    :CUSTOM_ID: diffs-isis-gnupg
+    :END:
+
+In 2015 Isis Lovecruft from the Tor Project forked and then
+re-implemented the python-gnupg package as just gnupg.  This new
+package also relied on subprocess to call the =gpg= or =gpg2=
+binaries, but did so somewhat more securely.
+
+The naming and version numbering selected for this package, however,
+resulted in conflicts with the original python-gnupg and since its
+functions were called in a different manner to python-gnupg, the
+release of this package also resulted in a great deal of consternation
+when people installed what they thought was an upgrade that
+subsequently broke the code relying on it.
+
+The gnupg package is available under the GNU General Public License
+version 3.0 (or any later version).
+
+
+*** The PyME package maintained by Martin Albrecht
+    :PROPERTIES:
+    :CUSTOM_ID: diffs-pyme
+    :END:
+
+This package is the origin of these bindings, though they are somewhat
+different now.  For details of when and how the PyME package was
+folded back into GPGME itself see the [[file:short-history.org][Short History]] document.[fn:1]
+
+The PyME package was first released in 2002 and was also the first
+attempt to implement a low level binding to GPGME.  In doing so it
+provided access to considerably more functionality than either the
+=python-gnupg= or =gnupg= packages.
+
+The PyME package is only available for Python 2.6 and 2.7.
+
+Porting the PyME package to Python 3.4 in 2015 is what resulted in it
+being folded into the GPGME project and the current bindings are the
+end result of that effort.
+
+The PyME package is available under the same dual licensing as GPGME
+itself: the GNU General Public License version 2.0 (or any later
+version) and the GNU Lesser General Public License version 2.1 (or any
+later version).
+
+
+* GPGME Python bindings installation
+  :PROPERTIES:
+  :CUSTOM_ID: gpgme-python-install
+  :END:
+
+
+** No PyPI
+   :PROPERTIES:
+   :CUSTOM_ID: do-not-use-pypi
+   :END:
+
+Most third-party Python packages and modules are available and
+distributed through the Python Package Installer, known as PyPI.
+
+Due to the nature of what these bindings are and how they work, it is
+infeasible to install the GPGME Python bindings in the same way.
+
+This is because the bindings use SWIG to dynamically generate C
+bindings against =gpgme.h= and =gpgme.h= is generated from
+=gpgme.h.in= at compile time when GPGME is built from source.  Thus to
+include a package in PyPI which actually built correctly would require
+either statically built libraries for every architecture bundled with
+it or a full implementation of C for each architecture.
+
+See the additional notes regarding [[#snafu-cffi][CFFI and SWIG]] at the end of this
+section for further details.
+
+
+** Requirements
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-python-requirements
+   :END:
+
+The GPGME Python bindings only have three requirements:
+
+1. A suitable version of Python 2 or Python 3.  With Python 2 that
+   means CPython 2.7 and with Python 3 that means CPython 3.4 or
+   higher.
+2. [[https://www.swig.org][SWIG]].
+3. GPGME itself.  Which also means that all of GPGME's dependencies
+   must be installed too.
+
+
+*** Recommended Additions
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-python-recommendations
+   :END:
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings.  In some cases these
+recommendations refer to which version(s) of CPython to use the
+bindings with, while others refer to third party modules which provide
+a significant advantage in some way.
+
+1. If possible, use Python 3 instead of 2.
+2. Favour a more recent version of Python since even 3.4 is due to
+   reach EOL soon.  In production systems and services, Python 3.6
+   should be robust enough to be relied on.
+3. If possible add the following Python modules which are not part of
+   the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[http://cython.org/][Cython]] and [[https://github.com/Selfnet/hkp4py][hkp4py]].  Chances are
+   quite high that at least the first one and maybe two of those will
+   already be installed.
+
+Note that, as with Cython, some of the planned additions to the
+[[#advanced-use][Advanced]] section, will bring with them additional requirements.  Most
+of these will be fairly well known and commonly installed ones,
+however, which are in many cases likely to have already been installed
+on many systems or be familiar to Python programmers.
+
+
+** Installation
+   :PROPERTIES:
+   :CUSTOM_ID: installation
+   :END:
+
+Installing the Python bindings is effectively achieved by compiling
+and installing GPGME itself.
+
+Once SWIG is installed with Python and all the dependencies for GPGME
+are installed you only need to confirm that the version(s) of Python
+you want the bindings installed for are in your =$PATH=.
+
+By default GPGME will attempt to install the bindings for the most
+recent or highest version number of Python 2 and Python 3 it detects
+in =$PATH=.  It specifically checks for the =python= and =python3=
+executables first and then checks for specific version numbers.
+
+For Python 2 it checks for these executables in this order: =python=,
+=python2= and =python2.7=.
+
+For Python 3 it checks for these executables in this order: =python3=,
+ =python3.7=, =python3.6=, =python3.5= and =python3.4=.[fn:2]
+
+On systems where =python= is actually =python3= and not =python2= it
+may be possible that =python2= may be overlooked, but there have been
+no reports of that actually occurring as yet.
+
+In the three months or so since the release of Python 3.7.0 there has
+been extensive testing and work with these bindings with no issues
+specifically relating to the new version of Python or any of the new
+features of either the language or the bindings.  This has also been
+the case with Python 3.7.1rc1.  With that in mind and given the
+release of Python 3.7.1 is scheduled for around the same time as GPGME
+1.12.0, the order of preferred Python versions has been changed to
+move Python 3.7 ahead of Python 3.6.
+
+
+*** Installing GPGME
+    :PROPERTIES:
+    :CUSTOM_ID: install-gpgme
+    :END:
+
+See the GPGME =README= file for details of how to install GPGME from
+source.
+
+
+** Known Issues
+   :PROPERTIES:
+   :CUSTOM_ID: snafu
+   :END:
+
+There are a few known issues with the current build process and the
+Python bindings.  For the most part these are easily addressed should
+they be encountered.
+
+
+*** Breaking Builds
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-a-swig-of-this-builds-character
+    :END:
+
+Occasionally when installing GPGME with the Python bindings included
+it may be observed that the =make= portion of that process induces a
+large very number of warnings and, eventually errors which end that
+part of the build process.  Yet following that with =make check= and
+=make install= appears to work seamlessly.
+
+The cause of this is related to the way SWIG needs to be called to
+dynamically generate the C bindings for GPGME in the first place.  So
+the entire process will always produce =lang/python/python2-gpg/= and
+=lang/python/python3-gpg/= directories.  These should contain the
+build output generated during compilation, including the complete
+bindings and module installed into =site-packages=.
+
+Occasionally the errors in the early part or some other conflict
+(e.g. not installing as */root/* or */su/*) may result in nothing
+being installed to the relevant =site-packages= directory and the
+build directory missing a lot of expected files.  Even when this
+occurs, the solution is actually quite simple and will always work.
+
+That solution is simply to run the following commands as either the
+*root* user or prepended with =sudo -H=[fn:3] in the =lang/python/=
+directory:
+
+#+BEGIN_SRC shell
+  /path/to/pythonX.Y setup.py build
+  /path/to/pythonX.Y setup.py build
+  /path/to/pythonX.Y setup.py install
+#+END_SRC
+
+Yes, the build command does need to be run twice.  Yes, you still need
+to run the potentially failing or incomplete steps during the
+=configure=, =make= and =make install= steps with installing GPGME.
+This is because those steps generate a lot of essential files needed,
+both by and in order to create, the bindings (including both the
+=setup.py= and =gpgme.h= files).
+
+
+**** IMPORTANT Note
+     :PROPERTIES:
+     :CUSTOM_ID: snafu-swig-build-note
+     :END:
+
+If specifying a selected number of languages to create bindings for,
+try to leave Python last.  Currently the majority of the other
+language bindings are also preceding Python of either version when
+listed alphabetically and so that just happens by default currently.
+
+If Python is set to precede one of the other languages then it is
+possible that the errors described here may interrupt the build
+process before generating bindings for those other languages.  In
+these cases it may be preferable to configure all preferred language
+bindings separately with alternative =configure= steps for GPGME using
+the =--enable-languages=$LANGUAGE= option.
+
+
+*** Reinstalling Responsibly
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-lessons-for-the-lazy
+    :END:
+
+Regardless of whether you're installing for one version of Python or
+several, there will come a point where reinstallation is required.
+With most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about.  Then
+the module is upgraded, the new files are copied over the old and
+that's the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant =site-packages= directory for the version of Python in
+question and removing the =gpg/= directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on
+the system, but the same is true of installing the module in the first
+place.
+
+
+*** Multiple installations
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-the-full-monty
+    :END:
+
+For a veriety of reasons it may be either necessary or just preferable
+to install the bindings to alternative installed Python versions which
+meet the requirements of these bindings.
+
+On POSIX systems this will generally be most simply achieved by
+running the manual installation commands (build, build, install) as
+described in the previous section for each Python installation the
+bindings need to be installed to.
+
+As per the SWIG documentation: the compilers, libraries and runtime
+used to build GPGME and the Python Bindings *must* match those used to
+compile Python itself, including the version number(s) (at least going
+by major version numbers and probably minor numbers too).
+
+On most POSIX systems, including OS X, this will very likely be the
+case in most, if not all, cases.
+
+
+*** Won't Work With Windows
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-runtime-not-funtime
+    :END:
+
+There are semi-regular reports of Windows users having considerable
+difficulty in installing and using the Python bindings at all.  Very
+often, possibly even always, these reports come from Cygwin users
+and/or MinGW users and/or Msys2 users.  Though not all of them have
+been confirmed, it appears that these reports have also come from
+people who installed Python using the Windows installer files from the
+[[https://python.org][Python website]] (i.e. mostly MSI installers, sometimes self-extracting
+=.exe= files).
+
+The Windows versions of Python are not built using Cygwin, MinGW or
+Msys2; they're built using Microsoft Visual Studio.  Furthermore the
+version used is /considerably/ more advanced than the version which
+MinGW obtained a small number of files from many years ago in order to
+be able to compile anything at all.  Not only that, but there are
+changes to the version of Visual Studio between some micro releases,
+though that is is particularly the case with Python 2.7, since it has
+been kept around far longer than it should have been.
+
+There are two theoretical solutions to this issue:
+
+ 1. Compile and install the GnuPG stack, including GPGME and the
+    Python bibdings using the same version of Microsoft Visual Studio
+    used by the Python Foundation to compile the version of Python
+    installed.
+
+    If there are multiple versions of Python then this will need to be
+    done with each different version of Visual Studio used.
+
+ 2. Compile and install Python using the same tools used by choice,
+    such as MinGW or Msys2.
+
+Do *not* use the official Windows installer for Python unless
+following the first method.
+
+In this type of situation it may even be for the best to accept that
+there are less limitations on permissive software than free software
+and simply opt to use a recent version of the Community Edition of
+Microsoft Visual Studio to compile and build all of it, no matter
+what.
+
+Investigations into the extent or the limitations of this issue are
+ongoing.
+
+
+*** CFFI is the Best™ and GPGME should use it instead of SWIG
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-cffi
+    :END:
+
+There are many reasons for favouring [[https://cffi.readthedocs.io/en/latest/overview.html][CFFI]] and proponents of it are
+quite happy to repeat these things as if all it would take to switch
+from SWIG to CFFI is repeating that list as if it were a new concept.
+
+The fact is that there are things which Python's CFFI implementation
+cannot handle in the GPGME C code.  Beyond that there are features of
+SWIG which are simply not available with CFFI at all.  SWIG generates
+the bindings to Python using the =gpgme.h= file, but that file is not
+a single version shipped with each release, it too is generated when
+GPGME is compiled.
+
+CFFI is currently unable to adapt to such a potentially mutable
+codebase.  If there were some means of applying SWIG's dynamic code
+generation to produce the Python/CFFI API modes of accessing the GPGME
+libraries (or the source source code directly), but such a thing does
+not exist yet either and it currently appears that work is needed in
+at least one of CFFI's dependencies before any of this can be
+addressed.
+
+So if you're a massive fan of CFFI; that's great, but if you want this
+project to switch to CFFI then rather than just insisting that it
+should, I'd suggest you volunteer to bring CFFI up to the level this
+project needs.
+
+If you're actually seriously considering doing so, then I'd suggest
+taking the =gpgme-tool.c= file in the GPGME =src/= directory and
+getting that to work with any of the CFFI API methods (not the ABI
+methods, they'll work with pretty much anything).  When you start
+running into trouble with "ifdefs" then you'll know what sort of
+things are lacking.  That doesn't even take into account the amount of
+work saved via SWIG's code generation techniques either.
+
+
+*** Virtualised Environments
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-venv
+    :END:
+
+It is fairly common practice amongst Python developers to, as much as
+possible, use packages like virtualenv to keep various things that are
+to be installed from interfering with each other.  Given how much of
+the GPGME bindings is often at odds with the usual pythonic way of
+doing things, it stands to reason that this would be called into
+question too.
+
+As it happens the answer as to whether or not the bindings can be used
+with virtualenv, the answer is both yes and no.
+
+In general we recommend installing to the relevant path and matching
+prefix of GPGME itself.  Which means that when GPGME, and ideally the
+rest of the GnuPG stack, is installed to a prefix like =/usr/local= or
+=/opt/local= then the bindings would need to be installed to the main
+Python installation and not a virtualised abstraction.  Attempts to
+separate the two in the past have been known to cause weird and
+intermittent errors ranging from minor annoyances to complete failures
+in the build process.
+
+As a consequence we only recommend building with and installing to the
+main Python installations within the same prefix as GPGME is installed
+to or which are found by GPGME's configuration stage immediately prior
+to running the make commands.  Which is exactly what the compiling and
+installing process of GPGME does by default.
+
+Once that is done, however, it appears that a copy the compiled module
+may be installed into a virtualenv of the same major and minor version
+matching the build.  Alternatively it is possible to utilise a
+=sites.pth= file in the =site-packages/= directory of a viertualenv
+installation, which links back to the system installations
+corresponding directory in order to import anything installed system
+wide.  This may or may not be appropriate on a case by case basis.
+
+Though extensive testing of either of these options is not yet
+complete, preliminary testing of them indicates that both are viable
+as long as the main installation is complete.  Which means that
+certain other options normally restricted to virtual environments are
+also available, including integration with pythonic test suites
+(e.g. [[https://docs.pytest.org/en/latest/index.html][pytest]]) and other large projects.
+
+That said, it is worth reiterating the warning regarding non-standard
+installations.  If one were to attempt to install the bindings only to
+a virtual environment without somehow also including the full GnuPG
+stack (or enough of it as to include GPGME) then it is highly likely
+that errors would be encountered at some point and more than a little
+likely that the build process itself would break.
+
+If a degree of separation from the main operating system is still
+required in spite of these warnings, then consider other forms of
+virtualisation.  Either a virtual machine (e.g. [[https://www.virtualbox.org/][VirtualBox]]), a
+hardware emulation layer (e.g. [[https://www.qemu.org/][QEMU]]) or an application container
+(e.g. [[https://www.docker.com/why-docker][Docker]]).
+
+Finally it should be noted that the limited tests conducted thus far
+have been using the =virtualenv= command in a new directory to create
+the virtual python environment.  As opposed to the standard =python3
+-m venv= and it is possible that this will make a difference depending
+on the system and version of Python in use.  Another option is to run
+the command =python3 -m virtualenv /path/to/install/virtual/thingy=
+instead.
+
+
+* Fundamentals
+  :PROPERTIES:
+  :CUSTOM_ID: howto-fund-a-mental
+  :END:
+
+Before we can get to the fun stuff, there are a few matters regarding
+GPGME's design which hold true whether you're dealing with the C code
+directly or these Python bindings.
+
+
+** No REST
+   :PROPERTIES:
+   :CUSTOM_ID: no-rest-for-the-wicked
+   :END:
+
+The first part of which is or will be fairly blatantly obvious upon
+viewing the first example, but it's worth reiterating anyway.  That
+being that this API is /*not*/ a REST API.  Nor indeed could it ever
+be one.
+
+Most, if not all, Python programmers (and not just Python programmers)
+know how easy it is to work with a RESTful API.  In fact they've
+become so popular that many other APIs attempt to emulate REST-like
+behaviour as much as they are able.  Right down to the use of JSON
+formatted output to facilitate the use of their API without having to
+retrain developers.
+
+This API does not do that.  It would not be able to do that and also
+provide access to the entire C API on which it's built.  It does,
+however, provide a very pythonic interface on top of the direct
+bindings and it's this pythonic layer that this HOWTO deals with.
+
+
+** Context
+   :PROPERTIES:
+   :CUSTOM_ID: howto-get-context
+   :END:
+
+One of the reasons which prevents this API from being RESTful is that
+most operations require more than one instruction to the API to
+perform the task.  Sure, there are certain functions which can be
+performed simultaneously, particularly if the result known or strongly
+anticipated (e.g. selecting and encrypting to a key known to be in the
+public keybox).
+
+There are many more, however, which cannot be manipulated so readily:
+they must be performed in a specific sequence and the result of one
+operation has a direct bearing on the outcome of subsequent
+operations.  Not merely by generating an error either.
+
+When dealing with this type of persistent state on the web, full of
+both the RESTful and REST-like, it's most commonly referred to as a
+session.  In GPGME, however, it is called a context and every
+operation type has one.
+
+
+* Working with keys
+  :PROPERTIES:
+  :CUSTOM_ID: howto-keys
+  :END:
+
+
+** Key selection
+   :PROPERTIES:
+   :CUSTOM_ID: howto-keys-selection
+   :END:
+
+Selecting keys to encrypt to or to sign with will be a common
+occurrence when working with GPGMe and the means available for doing
+so are quite simple.
+
+They do depend on utilising a Context; however once the data is
+recorded in another variable, that Context does not need to be the
+same one which subsequent operations are performed.
+
+The easiest way to select a specific key is by searching for that
+key's key ID or fingerprint, preferably the full fingerprint without
+any spaces in it.  A long key ID will probably be okay, but is not
+advised and short key IDs are already a problem with some being
+generated to match specific patterns.  It does not matter whether the
+pattern is upper or lower case.
+
+So this is the best method:
+
+#+BEGIN_SRC python -i
+import gpg
+
+k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+keys = list(k)
+#+END_SRC
+
+This is passable and very likely to be common:
+
+#+BEGIN_SRC python -i
+import gpg
+
+k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+keys = list(k)
+#+END_SRC
+
+And this is a really bad idea:
+
+#+BEGIN_SRC python -i
+import gpg
+
+k = gpg.Context().keylist(pattern="0xDEADBEEF")
+keys = list(k)
+#+END_SRC
+
+Alternatively it may be that the intention is to create a list of keys
+which all match a particular search string.  For instance all the
+addresses at a particular domain, like this:
+
+#+BEGIN_SRC python -i
+import gpg
+
+ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+nsa = list(ncsc)
+#+END_SRC
+
+
+*** Counting keys
+    :PROPERTIES:
+    :CUSTOM_ID: howto-keys-counting
+    :END:
+
+Counting the number of keys in your public keybox (=pubring.kbx=), the
+format which has superseded the old keyring format (=pubring.gpg= and
+=secring.gpg=), or the number of secret keys is a very simple task.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+seckeys = c.keylist(pattern=None, secret=True)
+pubkeys = c.keylist(pattern=None, secret=False)
+
+seclist = list(seckeys)
+secnum = len(seclist)
+
+publist = list(pubkeys)
+pubnum = len(publist)
+
+print("""
+  Number of secret keys:  {0}
+  Number of public keys:  {1}
+""".format(secnum, pubnum))
+#+END_SRC
+
+NOTE: The [[#cython][Cython]] introduction in the [[#advanced-use][Advanced and Experimental]]
+section uses this same key counting code with Cython to demonstrate
+some areas where Cython can improve performance even with the
+bindings.  Users with large public keyrings or keyboxes, for instance,
+should consider these options if they are comfortable with using
+Cython.
+
+
+** Get key
+   :PROPERTIES:
+   :CUSTOM_ID: howto-get-key
+   :END:
+
+An alternative method of getting a single key via its fingerprint is
+available directly within a Context with =Context().get_key=.  This is
+the preferred method of selecting a key in order to modify it, sign or
+certify it and for obtaining relevant data about a single key as a
+part of other functions; when verifying a signature made by that key,
+for instance.
+
+By default this method will select public keys, but it can select
+secret keys as well.
+
+This first example demonstrates selecting the current key of Werner
+Koch, which is due to expire at the end of 2018:
+
+#+BEGIN_SRC python -i
+import gpg
+
+fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+key = gpg.Context().get_key(fingerprint)
+#+END_SRC
+
+Whereas this example demonstrates selecting the author's current key
+with the =secret= key word argument set to =True=:
+
+#+BEGIN_SRC python -i
+import gpg
+
+fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+key = gpg.Context().get_key(fingerprint, secret=True)
+#+END_SRC
+
+It is, of course, quite possible to select expired, disabled and
+revoked keys with this function, but only to effectively display
+information about those keys.
+
+It is also possible to use both unicode or string literals and byte
+literals with the fingerprint when getting a key in this way.
+
+
+** Importing keys
+   :PROPERTIES:
+   :CUSTOM_ID: howto-import-key
+   :END:
+
+Importing keys is possible with the =key_import()= method and takes
+one argument which is a bytes literal object containing either the
+binary or ASCII armoured key data for one or more keys.
+
+The following example retrieves one or more keys from the SKS
+keyservers via the web using the requests module.  Since requests
+returns the content as a bytes literal object, we can then use that
+directly to import the resulting data into our keybox.
+
+#+BEGIN_SRC python -i
+import gpg
+import os.path
+import requests
+
+c = gpg.Context()
+url = "https://sks-keyservers.net/pks/lookup"
+pattern = input("Enter the pattern to search for key or user IDs: ")
+payload = {"op": "get", "search": pattern}
+
+r = requests.get(url, verify=True, params=payload)
+result = c.key_import(r.content)
+
+if result is not None and hasattr(result, "considered") is False:
+    print(result)
+elif result is not None and hasattr(result, "considered") is True:
+    num_keys = len(result.imports)
+    new_revs = result.new_revocations
+    new_sigs = result.new_signatures
+    new_subs = result.new_sub_keys
+    new_uids = result.new_user_ids
+    new_scrt = result.secret_imported
+    nochange = result.unchanged
+    print("""
+  The total number of keys considered for import was:  {0}
+
+     Number of keys revoked:  {1}
+   Number of new signatures:  {2}
+      Number of new subkeys:  {3}
+     Number of new user IDs:  {4}
+  Number of new secret keys:  {5}
+   Number of unchanged keys:  {6}
+
+  The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+    for i in range(num_keys):
+        print("{0}\n".format(result.imports[i].fpr))
+else:
+    pass
+#+END_SRC
+
+NOTE: When searching for a key ID of any length or a fingerprint
+(without spaces), the SKS servers require the the leading =0x=
+indicative of hexadecimal be included.  Also note that the old short
+key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the
+relative ease by which such key IDs can be reproduced, as demonstrated
+by the Evil32 Project in 2014 (which was subsequently exploited in
+2016).
+
+
+*** Working with ProtonMail
+    :PROPERTIES:
+    :CUSTOM_ID: import-protonmail
+    :END:
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+#+BEGIN_SRC python -i
+import gpg
+import requests
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+""")
+
+c = gpg.Context(armor=True)
+url = "https://api.protonmail.ch/pks/lookup"
+ksearch = []
+
+if len(sys.argv) >= 2:
+    keyterm = sys.argv[1]
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+
+if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+    ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+    ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+    ksearch.append("{0}@pm.me".format(keyterm[1:]))
+elif keyterm.count("@") == 0:
+    ksearch.append("{0}@protonmail.com".format(keyterm))
+    ksearch.append("{0}@protonmail.ch".format(keyterm))
+    ksearch.append("{0}@pm.me".format(keyterm))
+elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+    uidlist = keyterm.split("@")
+    for uid in uidlist:
+        ksearch.append("{0}@protonmail.com".format(uid))
+        ksearch.append("{0}@protonmail.ch".format(uid))
+        ksearch.append("{0}@pm.me".format(uid))
+elif keyterm.count("@") > 2:
+    uidlist = keyterm.split("@")
+    for uid in uidlist:
+        ksearch.append("{0}@protonmail.com".format(uid))
+        ksearch.append("{0}@protonmail.ch".format(uid))
+        ksearch.append("{0}@pm.me".format(uid))
+else:
+    ksearch.append(keyterm)
+
+for k in ksearch:
+    payload = {"op": "get", "search": k}
+    try:
+        r = requests.get(url, verify=True, params=payload)
+        if r.ok is True:
+            result = c.key_import(r.content)
+        elif r.ok is False:
+            result = r.content
+    except Exception as e:
+        result = None
+
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        print(e)
+#+END_SRC
+
+Both the above example, [[../examples/howto/pmkey-import.py][pmkey-import.py]], and a version which prompts
+for an alternative GnuPG home directory, [[../examples/howto/pmkey-import-alt.py][pmkey-import-alt.py]], are
+available with the other examples and are executable scripts.
+
+Note that while the ProtonMail servers are based on the SKS servers,
+their server is related more to their API and is not feature complete
+by comparison to the servers in the SKS pool.  One notable difference
+being that the ProtonMail server does not permit non ProtonMail users
+to update their own keys, which could be a vector for attacking
+ProtonMail users who may not receive a key's revocation if it had been
+compromised.
+
+
+*** Importing with HKP for Python
+    :PROPERTIES:
+    :CUSTOM_ID: import-hkp4py
+    :END:
+
+Performing the same tasks with the [[https://github.com/Selfnet/hkp4py][hkp4py module]] (available via PyPI)
+is not too much different, but does provide a number of options of
+benefit to end users.  Not least of which being the ability to perform
+some checks on a key before importing it or not.  For instance it may
+be the policy of a site or project to only import keys which have not
+been revoked.  The hkp4py module permits such checks prior to the
+importing of the keys found.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import sys
+
+c = gpg.Context()
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+results = []
+
+if len(sys.argv) > 2:
+    pattern = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    pattern = sys.argv[1]
+else:
+    pattern = input("Enter the pattern to search for keys or user IDs: ")
+
+try:
+    keys = server.search(pattern)
+    print("Found {0} key(s).".format(len(keys)))
+except Exception as e:
+    keys = []
+    for logrus in pattern.split():
+        if logrus.startswith("0x") is True:
+            key = server.search(logrus)
+        else:
+            key = server.search("0x{0}".format(logrus))
+        keys.append(key[0])
+    print("Found {0} key(s).".format(len(keys)))
+
+for key in keys:
+    import_result = c.key_import(key.key_blob)
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print(result)
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+   Number of keys revoked:  {1}
+ Number of new signatures:  {2}
+    Number of new subkeys:  {3}
+   Number of new user IDs:  {4}
+Number of new secret keys:  {5}
+ Number of unchanged keys:  {6}
+
+The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    else:
+        pass
+#+END_SRC
+
+Since the hkp4py module handles multiple keys just as effectively as
+one (=keys= is a list of responses per matching key), the example
+above is able to do a little bit more with the returned data before
+anything is actually imported.
+
+
+*** Importing from ProtonMail with HKP for Python
+    :PROPERTIES:
+    :CUSTOM_ID: import-protonmail-hkp4py
+    :END:
+
+Though this can provide certain benefits even when working with
+ProtonMail, the scope is somewhat constrained there due to the
+limitations of the ProtonMail keyserver.
+
+For instance, searching the SKS keyserver pool for the term "gnupg"
+produces hundreds of results from any time the word appears in any
+part of a user ID.  Performing the same search on the ProtonMail
+keyserver returns zero results, even though there are at least two
+test accounts which include it as part of the username.
+
+The cause of this discrepancy is the deliberate configuration of that
+server by ProtonMail to require an exact match of the full email
+address of the ProtonMail user whose key is being requested.
+Presumably this is intended to reduce breaches of privacy of their
+users as an email address must already be known before a key for that
+address can be obtained.
+
+
+**** Import from ProtonMail via HKP for Python Example no. 1
+     :PROPERTIES:
+     :CUSTOM_ID: import-hkp4py-pm1
+     :END:
+
+The following script is avalable with the rest of the examples under
+the somewhat less than original name, =pmkey-import-hkp.py=.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+
+Usage:  pmkey-import-hkp.py [search strings]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 2:
+    keyterms = sys.argv[1:]
+elif len(sys.argv) == 2:
+    keyterm = sys.argv[1]
+    keyterms.append(keyterm)
+else:
+    key_term = input("Enter the key ID, UID or search string: ")
+    keyterms = key_term.split()
+
+for keyterm in keyterms:
+    if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+    elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+        ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+        ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+        ksearch.append("{0}@pm.me".format(keyterm[1:]))
+    elif keyterm.count("@") == 0:
+        ksearch.append("{0}@protonmail.com".format(keyterm))
+        ksearch.append("{0}@protonmail.ch".format(keyterm))
+        ksearch.append("{0}@pm.me".format(keyterm))
+    elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    elif keyterm.count("@") > 2:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    else:
+        ksearch.append(keyterm)
+
+for k in ksearch:
+    print("Checking for key for: {0}".format(k))
+    try:
+        keys = server.search(k)
+        if isinstance(keys, list) is True:
+            for key in keys:
+                allkeys.append(key)
+                try:
+                    import_result = c.key_import(key.key_blob)
+                except Exception as e:
+                    import_result = c.key_import(key.key)
+        else:
+            paradox.append(keys)
+            import_result = None
+    except Exception as e:
+        import_result = None
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        pass
+#+END_SRC
+
+
+**** Import from ProtonMail via HKP for Python Example no. 2
+     :PROPERTIES:
+     :CUSTOM_ID: import-hkp4py-pm2
+     :END:
+
+Like its counterpart above, this script can also be found with the
+rest of the examples, by the name pmkey-import-hkp-alt.py.
+
+With this script a modicum of effort has been made to treat anything
+passed as a =homedir= which either does not exist or which is not a
+directory, as also being a pssible user ID to check for.  It's not
+guaranteed to pick up on all such cases, but it should cover most of
+them.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.  Optionally enables specifying a different GnuPG home directory.
+
+Usage:  pmkey-import-hkp.py [homedir] [search string]
+   or:  pmkey-import-hkp.py [search string]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 3:
+    homedir = sys.argv[1]
+    keyterms = sys.argv[2:]
+elif len(sys.argv) == 3:
+    homedir = sys.argv[1]
+    keyterm = sys.argv[2]
+    keyterms.append(keyterm)
+elif len(sys.argv) == 2:
+    homedir = ""
+    keyterm = sys.argv[1]
+    keyterms.append(keyterm)
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+    keyterms.append(keyterm)
+
+if len(homedir) == 0:
+    homedir = None
+    homeless = False
+
+if homedir is not None:
+    if homedir.startswith("~"):
+        if os.path.exists(os.path.expanduser(homedir)) is True:
+            if os.path.isdir(os.path.expanduser(homedir)) is True:
+                c.home_dir = os.path.realpath(os.path.expanduser(homedir))
+            else:
+                homeless = True
+        else:
+            homeless = True
+    elif os.path.exists(os.path.realpath(homedir)) is True:
+        if os.path.isdir(os.path.realpath(homedir)) is True:
+            c.home_dir = os.path.realpath(homedir)
+        else:
+            homeless = True
+    else:
+        homeless = True
+
+# First check to see if the homedir really is a homedir and if not, treat it as
+# a search string.
+if homeless is True:
+    keyterms.append(homedir)
+    c.home_dir = None
+else:
+    pass
+
+for keyterm in keyterms:
+    if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+    elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+        ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+        ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+        ksearch.append("{0}@pm.me".format(keyterm[1:]))
+    elif keyterm.count("@") == 0:
+        ksearch.append("{0}@protonmail.com".format(keyterm))
+        ksearch.append("{0}@protonmail.ch".format(keyterm))
+        ksearch.append("{0}@pm.me".format(keyterm))
+    elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    elif keyterm.count("@") > 2:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    else:
+        ksearch.append(keyterm)
+
+for k in ksearch:
+    print("Checking for key for: {0}".format(k))
+    try:
+        keys = server.search(k)
+        if isinstance(keys, list) is True:
+            for key in keys:
+                allkeys.append(key)
+                try:
+                    import_result = c.key_import(key.key_blob)
+                except Exception as e:
+                    import_result = c.key_import(key.key)
+        else:
+            paradox.append(keys)
+            import_result = None
+    except Exception as e:
+        import_result = None
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        pass
+#+END_SRC
+
+
+** Exporting keys
+   :PROPERTIES:
+   :CUSTOM_ID: howto-export-key
+   :END:
+
+Exporting keys remains a reasonably simple task, but has been
+separated into three different functions for the OpenPGP cryptographic
+engine.  Two of those functions are for exporting public keys and the
+third is for exporting secret keys.
+
+
+*** Exporting public keys
+    :PROPERTIES:
+    :CUSTOM_ID: howto-export-public-key
+    :END:
+
+There are two methods of exporting public keys, both of which are very
+similar to the other.  The default method, =key_export()=, will export
+a public key or keys matching a specified pattern as normal.  The
+alternative, the =key_export_minimal()= method, will do the same thing
+except producing a minimised output with extra signatures and third
+party signatures or certifications removed.
+
+#+BEGIN_SRC python -i
+import gpg
+import os.path
+import sys
+
+print("""
+This script exports one or more public keys.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+    if os.path.exists(os.path.expanduser(homedir)) is True:
+        c.home_dir = os.path.expanduser(homedir)
+    else:
+        pass
+elif os.path.exists(homedir) is True:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export(pattern=logrus)
+except:
+    result = c.key_export(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+else:
+    pass
+#+END_SRC
+
+It should be noted that the result will only return =None= when a
+search pattern has been entered, but has not matched any keys.  When
+the search pattern itself is set to =None= this triggers the exporting
+of the entire public keybox.
+
+#+BEGIN_SRC python -i
+import gpg
+import os.path
+import sys
+
+print("""
+This script exports one or more public keys in minimised form.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+    if os.path.exists(os.path.expanduser(homedir)) is True:
+        c.home_dir = os.path.expanduser(homedir)
+    else:
+        pass
+elif os.path.exists(homedir) is True:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export_minimal(pattern=logrus)
+except:
+    result = c.key_export_minimal(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+else:
+    pass
+#+END_SRC
+
+
+*** Exporting secret keys
+    :PROPERTIES:
+    :CUSTOM_ID: howto-export-secret-key
+    :END:
+
+Exporting secret keys is, functionally, very similar to exporting
+public keys; save for the invocation of =pinentry= via =gpg-agent= in
+order to securely enter the key's passphrase and authorise the export.
+
+The following example exports the secret key to a file which is then
+set with the same permissions as the output files created by the
+command line secret key export options.
+
+#+BEGIN_SRC python -i
+import gpg
+import os
+import os.path
+import sys
+
+print("""
+This script exports one or more secret keys.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export_secret(pattern=logrus)
+except:
+    result = c.key_export_secret(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+    os.chmod(keyfile, 0o600)
+else:
+    pass
+#+END_SRC
+
+Alternatively the approach of the following script can be used.  This
+longer example saves the exported secret key(s) in files in the GnuPG
+home directory, in addition to setting the file permissions as only
+readable and writable by the user.  It also exports the secret key(s)
+twice in order to output both GPG binary (=.gpg=) and ASCII armoured
+(=.asc=) files.
+
+#+BEGIN_SRC python -i
+import gpg
+import os
+import os.path
+import subprocess
+import sys
+
+print("""
+This script exports one or more secret keys as both ASCII armored and binary
+file formats, saved in files within the user's GPG home directory.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+else:
+    gpgconfcmd = "gpgconf --list-dirs homedir"
+
+a = gpg.Context(armor=True)
+b = gpg.Context()
+c = gpg.Context()
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+if c.home_dir is not None:
+    if c.home_dir.endswith("/"):
+        gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
+        ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
+    else:
+        gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
+        ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
+else:
+    if os.path.exists(os.environ["GNUPGHOME"]) is True:
+        hd = os.environ["GNUPGHOME"]
+    else:
+        try:
+            hd = subprocess.getoutput(gpgconfcmd)
+        except:
+            process = subprocess.Popen(gpgconfcmd.split(),
+                                       stdout=subprocess.PIPE)
+            procom = process.communicate()
+            if sys.version_info[0] == 2:
+                hd = procom[0].strip()
+            else:
+                hd = procom[0].decode().strip()
+    gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
+    ascfile = "{0}/{1}.asc".format(hd, keyfile)
+
+try:
+    a_result = a.key_export_secret(pattern=logrus)
+    b_result = b.key_export_secret(pattern=logrus)
+except:
+    a_result = a.key_export_secret(pattern=None)
+    b_result = b.key_export_secret(pattern=None)
+
+if a_result is not None:
+    with open(ascfile, "wb") as f:
+        f.write(a_result)
+    os.chmod(ascfile, 0o600)
+else:
+    pass
+
+if b_result is not None:
+    with open(gpgfile, "wb") as f:
+        f.write(b_result)
+    os.chmod(gpgfile, 0o600)
+else:
+    pass
+#+END_SRC
+
+
+*** Sending public keys to the SKS Keyservers
+    :PROPERTIES:
+    :CUSTOM_ID: howto-send-public-key
+    :END:
+
+As with the previous section on importing keys, the =hkp4py= module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+#+BEGIN_SRC python -i
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) > 2:
+    logrus = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    logrus = sys.argv[1]
+else:
+    logrus = input("Enter the UID matching the key(s) to send: ")
+
+if len(logrus) > 0:
+    try:
+        export_result = c.key_export(pattern=logrus)
+    except Exception as e:
+        print(e)
+        export_result = None
+else:
+    export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+    try:
+        try:
+            send_result = server.add(export_result)
+        except:
+            send_result = server.add(export_result.decode())
+        if send_result is not None:
+            print(send_result)
+        else:
+            pass
+    except Exception as e:
+        print(e)
+else:
+    pass
+#+END_SRC
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples
+directory as =send-key-to-keyserver.py=.
+
+The =hkp4py= module appears to handle both string and byte literal text
+data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
+
+* Basic Functions
+  :PROPERTIES:
+  :CUSTOM_ID: howto-the-basics
+  :END:
+
+The most frequently called features of any cryptographic library will
+be the most fundamental tasks for encryption software.  In this
+section we will look at how to programmatically encrypt data, decrypt
+it, sign it and verify signatures.
+
+
+** Encryption
+   :PROPERTIES:
+   :CUSTOM_ID: howto-basic-encryption
+   :END:
+
+Encrypting is very straight forward.  In the first example below the
+message, =text=, is encrypted to a single recipient's key.  In the
+second example the message will be encrypted to multiple recipients.
+
+
+*** Encrypting to one key
+    :PROPERTIES:
+    :CUSTOM_ID: howto-basic-encryption-single
+    :END:
+
+Once the the Context is set the main issues with encrypting data is
+essentially reduced to key selection and the keyword arguments
+specified in the =gpg.Context().encrypt()= method.
+
+Those keyword arguments are: =recipients=, a list of keys encrypted to
+(covered in greater detail in the following section); =sign=, whether
+or not to sign the plaintext data, see subsequent sections on signing
+and verifying signatures below (defaults to =True=); =sink=, to write
+results or partial results to a secure sink instead of returning it
+(defaults to =None=); =passphrase=, only used when utilising symmetric
+encryption (defaults to =None=); =always_trust=, used to override the
+trust model settings for recipient keys (defaults to =False=);
+=add_encrypt_to=, utilises any preconfigured =encrypt-to= or
+=default-key= settings in the user's =gpg.conf= file (defaults to
+=False=); =prepare=, prepare for encryption (defaults to =False=);
+=expect_sign=, prepare for signing (defaults to =False=); =compress=,
+compresses the plaintext prior to encryption (defaults to =True=).
+
+#+BEGIN_SRC python -i
+import gpg
+
+a_key = "0x12345678DEADBEEF"
+text = b"""Some text to test with.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data format.
+"""
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+    afile.write(ciphertext)
+#+END_SRC
+
+Though this is even more likely to be used like this; with the
+plaintext input read from a file, the recipient keys used for
+encryption regardless of key trust status and the encrypted output
+also encrypted to any preconfigured keys set in the =gpg.conf= file:
+
+#+BEGIN_SRC python -i
+import gpg
+
+a_key = "0x12345678DEADBEEF"
+
+with open("secret_plans.txt", "rb") as afile:
+    text = afile.read()
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
+                                            always_trust=True,
+                                            add_encrypt_to=True)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+    afile.write(ciphertext)
+#+END_SRC
+
+If the =recipients= paramater is empty then the plaintext is encrypted
+symmetrically.  If no =passphrase= is supplied as a parameter or via a
+callback registered with the =Context()= then an out-of-band prompt
+for the passphrase via pinentry will be invoked.
+
+
+*** Encrypting to multiple keys
+    :PROPERTIES:
+    :CUSTOM_ID: howto-basic-encryption-multiple
+    :END:
+
+Encrypting to multiple keys essentially just expands upon the key
+selection process and the recipients from the previous examples.
+
+The following example encrypts a message (=text=) to everyone with an
+email address on the =gnupg.org= domain,[fn:4] but does /not/ encrypt
+to a default key or other key which is configured to normally encrypt
+to.
+
+#+BEGIN_SRC python -i
+import gpg
+
+text = b"""Oh look, another test message.
+
+The same rules apply as with the previous example and more likely
+than not, the message will actually be drawn from reading the
+contents of a file or, maybe, from entering data at an input()
+prompt.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data
+format.
+"""
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+    if rpattern[i].can_encrypt == 1:
+        logrus.append(rpattern[i])
+
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                            sign=False, always_trust=True)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+    afile.write(ciphertext)
+#+END_SRC
+
+All it would take to change the above example to sign the message
+and also encrypt the message to any configured default keys would
+be to change the =c.encrypt= line to this:
+
+#+BEGIN_SRC python -i
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                            always_trust=True,
+                                            add_encrypt_to=True)
+#+END_SRC
+
+The only keyword arguments requiring modification are those for which
+the default values are changing.  The default value of =sign= is
+=True=, the default of =always_trust= is =False=, the default of
+=add_encrypt_to= is =False=.
+
+If =always_trust= is not set to =True= and any of the recipient keys
+are not trusted (e.g. not signed or locally signed) then the
+encryption will raise an error.  It is possible to mitigate this
+somewhat with something more like this:
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("secret_plans.txt.asc", "rb") as afile:
+    text = afile.read()
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+    if rpattern[i].can_encrypt == 1:
+        logrus.append(rpattern[i])
+
+    try:
+        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                    add_encrypt_to=True)
+    except gpg.errors.InvalidRecipients as e:
+        for i in range(len(e.recipients)):
+            for n in range(len(logrus)):
+                if logrus[n].fpr == e.recipients[i].fpr:
+                    logrus.remove(logrus[n])
+                else:
+                    pass
+        try:
+            ciphertext, result, sign_result = c.encrypt(text,
+                                                        recipients=logrus,
+                                                        add_encrypt_to=True)
+            with open("secret_plans.txt.asc", "wb") as afile:
+                afile.write(ciphertext)
+        except:
+            pass
+#+END_SRC
+
+This will attempt to encrypt to all the keys searched for, then remove
+invalid recipients if it fails and try again.
+
+
+** Decryption
+   :PROPERTIES:
+   :CUSTOM_ID: howto-basic-decryption
+   :END:
+
+Decrypting something encrypted to a key in one's secret keyring is
+fairly straight forward.
+
+In this example code, however, preconfiguring either =gpg.Context()=
+or =gpg.core.Context()= as =c= is unnecessary because there is no need
+to modify the Context prior to conducting the decryption and since the
+Context is only used once, setting it to =c= simply adds lines for no
+gain.
+
+#+BEGIN_SRC python -i
+import gpg
+
+ciphertext = input("Enter path and filename of encrypted file: ")
+newfile = input("Enter path and filename of file to save decrypted data to: ")
+
+with open(ciphertext, "rb") as cfile:
+    try:
+        plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+    except gpg.errors.GPGMEError as e:
+        plaintext = None
+        print(e)
+
+if plaintext is not None:
+    with open(newfile, "wb") as nfile:
+           nfile.write(plaintext)
+    else:
+        pass
+#+END_SRC
+
+The data available in =plaintext= in this example is the decrypted
+content as a byte object, the recipient key IDs and algorithms in
+=result= and the results of verifying any signatures of the data in
+=verify_result=.
+
+
+** Signing text and files
+   :PROPERTIES:
+   :CUSTOM_ID: howto-basic-signing
+   :END:
+
+The following sections demonstrate how to specify keys to sign with.
+
+
+*** Signing key selection
+    :PROPERTIES:
+    :CUSTOM_ID: howto-basic-signing-signers
+    :END:
+
+By default GPGME and the Python bindings will use the default key
+configured for the user invoking the GPGME API.  If there is no
+default key specified and there is more than one secret key available
+it may be necessary to specify the key or keys with which to sign
+messages and files.
+
+#+BEGIN_SRC python -i
+import gpg
+
+logrus = input("Enter the email address or string to match signing keys to: ")
+hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+sig_src = list(hancock)
+#+END_SRC
+
+The signing examples in the following sections include the explicitly
+designated =signers= parameter in two of the five examples; once where
+the resulting signature would be ASCII armoured and once where it
+would not be armoured.
+
+While it would be possible to enter a key ID or fingerprint here to
+match a specific key, it is not possible to enter two fingerprints and
+match two keys since the patten expects a string, bytes or None and
+not a list.  A string with two fingerprints won't match any single
+key.
+
+
+*** Normal or default signing messages or files
+    :PROPERTIES:
+    :CUSTOM_ID: howto-basic-signing-normal
+    :END:
+
+The normal or default signing process is essentially the same as is
+most often invoked when also encrypting a message or file.  So when
+the encryption component is not utilised, the result is to produce an
+encoded and signed output which may or may not be ASCII armoured and
+which may or may not also be compressed.
+
+By default compression will be used unless GnuPG detects that the
+plaintext is already compressed.  ASCII armouring will be determined
+according to the value of =gpg.Context().armor=.
+
+The compression algorithm is selected in much the same way as the
+symmetric encryption algorithm or the hash digest algorithm is when
+multiple keys are involved; from the preferences saved into the key
+itself or by comparison with the preferences with all other keys
+involved.
+
+#+BEGIN_SRC python -i
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True, signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+    afile.write(signed_data.decode())
+#+END_SRC
+
+Though everything in this example is accurate, it is more likely that
+reading the input data from another file and writing the result to a
+new file will be performed more like the way it is done in the next
+example.  Even if the output format is ASCII armoured.
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+    text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+    afile.write(signed_data)
+#+END_SRC
+
+
+*** Detached signing messages and files
+    :PROPERTIES:
+    :CUSTOM_ID: howto-basic-signing-detached
+    :END:
+
+Detached signatures will often be needed in programmatic uses of
+GPGME, either for signing files (e.g. tarballs of code releases) or as
+a component of message signing (e.g. PGP/MIME encoded email).
+
+#+BEGIN_SRC python -i
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+    afile.write(signed_data.decode())
+#+END_SRC
+
+As with normal signatures, detached signatures are best handled as
+byte literals, even when the output is ASCII armoured.
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+    text = tfile.read()
+
+c = gpg.Context(signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+    afile.write(signed_data)
+#+END_SRC
+
+
+*** Clearsigning messages or text
+    :PROPERTIES:
+    :CUSTOM_ID: howto-basic-signing-clear
+    :END:
+
+Though PGP/in-line messages are no longer encouraged in favour of
+PGP/MIME, there is still sometimes value in utilising in-line
+signatures.  This is where clear-signed messages or text is of value.
+
+#+BEGIN_SRC python -i
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+    afile.write(signed_data.decode())
+#+END_SRC
+
+In spite of the appearance of a clear-signed message, the data handled
+by GPGME in signing it must still be byte literals.
+
+#+BEGIN_SRC python -i
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+    text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "wb") as afile:
+    afile.write(signed_data)
+#+END_SRC
+
+
+** Signature verification
+   :PROPERTIES:
+   :CUSTOM_ID: howto-basic-verification
+   :END:
+
+Essentially there are two principal methods of verification of a
+signature.  The first of these is for use with the normal or default
+signing method and for clear-signed messages.  The second is for use
+with files and data with detached signatures.
+
+The following example is intended for use with the default signing
+method where the file was not ASCII armoured:
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+gpg_file = "statement.txt.gpg"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(gpg_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+#+END_SRC
+
+Whereas this next example, which is almost identical would work with
+normal ASCII armoured files and with clear-signed files:
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(asc_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+#+END_SRC
+
+In both of the previous examples it is also possible to compare the
+original data that was signed against the signed data in =data= to see
+if it matches with something like this:
+
+#+BEGIN_SRC python -i
+with open(filename, "rb") as afile:
+    text = afile.read()
+
+if text == data:
+    print("Good signature.")
+else:
+    pass
+#+END_SRC
+
+The following two examples, however, deal with detached signatures.
+With his method of verification the data that was signed does not get
+returned since it is already being explicitly referenced in the first
+argument of =c.verify=.  So =data= is =None= and only the information
+in =result= is available.
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+sig_file = "statement.txt.sig"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(filename), open(sig_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+#+END_SRC
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(filename), open(asc_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+{0}
+with key {1}
+made at {2}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+#+END_SRC
+
+
+* Creating keys and subkeys
+  :PROPERTIES:
+  :CUSTOM_ID: key-generation
+  :END:
+
+The one thing, aside from GnuPG itself, that GPGME depends on, of
+course, is the keys themselves.  So it is necessary to be able to
+generate them and modify them by adding subkeys, revoking or disabling
+them, sometimes deleting them and doing the same for user IDs.
+
+In the following examples a key will be created for the world's
+greatest secret agent, Danger Mouse.  Since Danger Mouse is a secret
+agent he needs to be able to protect information to =SECRET= level
+clearance, so his keys will be 3072-bit keys.
+
+The pre-configured =gpg.conf= file which sets cipher, digest and other
+preferences contains the following configuration parameters:
+
+#+BEGIN_SRC conf
+  expert
+  allow-freeform-uid
+  allow-secret-key-import
+  trust-model tofu+pgp
+  tofu-default-policy unknown
+  enable-large-rsa
+  enable-dsa2
+  cert-digest-algo SHA512
+  default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+  personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+  personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+  personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+#+END_SRC
+
+
+** Primary key
+   :PROPERTIES:
+   :CUSTOM_ID: keygen-primary
+   :END:
+
+Generating a primary key uses the =create_key= method in a Context.
+It contains multiple arguments and keyword arguments, including:
+=userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=,
+=certify=, =authenticate=, =passphrase= and =force=.  The defaults for
+all of those except =userid=, =algorithm=, =expires_in=, =expires= and
+=passphrase= is =False=.  The defaults for =algorithm= and
+=passphrase= is =None=.  The default for =expires_in= is =0=.  The
+default for =expires= is =True=.  There is no default for =userid=.
+
+If =passphrase= is left as =None= then the key will not be generated
+with a passphrase, if =passphrase= is set to a string then that will
+be the passphrase and if =passphrase= is set to =True= then gpg-agent
+will launch pinentry to prompt for a passphrase.  For the sake of
+convenience, these examples will keep =passphrase= set to =None=.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+
+c.home_dir = "~/.gnupg-dm"
+userid = "Danger Mouse <dm@secret.example.net>"
+
+dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
+                     sign=True, certify=True)
+#+END_SRC
+
+One thing to note here is the use of setting the =c.home_dir=
+parameter.  This enables generating the key or keys in a different
+location.  In this case to keep the new key data created for this
+example in a separate location rather than adding it to existing and
+active key store data.  As with the default directory, =~/.gnupg=, any
+temporary or separate directory needs the permissions set to only
+permit access by the directory owner.  On posix systems this means
+setting the directory permissions to 700.
+
+The =temp-homedir-config.py= script in the HOWTO examples directory
+will create an alternative homedir with these configuration options
+already set and the correct directory and file permissions.
+
+The successful generation of the key can be confirmed via the returned
+=GenkeyResult= object, which includes the following data:
+
+#+BEGIN_SRC python -i
+print("""
+ Fingerprint:  {0}
+ Primary Key:  {1}
+  Public Key:  {2}
+  Secret Key:  {3}
+ Sub Key:  {4}
+User IDs:  {5}
+""".format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
+           dmkey.uid))
+#+END_SRC
+
+Alternatively the information can be confirmed using the command line
+program:
+
+#+BEGIN_SRC shell
+  bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+  ~/.gnupg-dm/pubring.kbx
+  ----------------------
+  sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+       177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+  uid           [ultimate] Danger Mouse <dm@secret.example.net>
+
+  bash-4.4$
+#+END_SRC
+
+As with generating keys manually, to preconfigure expanded preferences
+for the cipher, digest and compression algorithms, the =gpg.conf= file
+must contain those details in the home directory in which the new key
+is being generated.  I used a cut down version of my own =gpg.conf=
+file in order to be able to generate this:
+
+#+BEGIN_SRC shell
+  bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
+  Secret key is available.
+
+  sec  rsa3072/026D2F19E99E63AA
+       created: 2018-03-15  expires: 2019-03-15  usage: SC
+       trust: ultimate      validity: ultimate
+  [ultimate] (1). Danger Mouse <dm@secret.example.net>
+
+  [ultimate] (1). Danger Mouse <dm@secret.example.net>
+       Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
+       Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
+       Compression: ZLIB, BZIP2, ZIP, Uncompressed
+       Features: MDC, Keyserver no-modify
+
+  bash-4.4$
+#+END_SRC
+
+
+** Subkeys
+   :PROPERTIES:
+   :CUSTOM_ID: keygen-subkeys
+   :END:
+
+Adding subkeys to a primary key is fairly similar to creating the
+primary key with the =create_subkey= method.  Most of the arguments
+are the same, but not quite all.  Instead of the =userid= argument
+there is now a =key= argument for selecting which primary key to add
+the subkey to.
+
+In the following example an encryption subkey will be added to the
+primary key.  Since Danger Mouse is a security conscious secret agent,
+this subkey will only be valid for about six months, half the length
+of the primary key.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+c.home_dir = "~/.gnupg-dm"
+
+key = c.get_key(dmkey.fpr, secret=True)
+dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
+                        encrypt=True)
+#+END_SRC
+
+As with the primary key, the results here can be checked with:
+
+#+BEGIN_SRC python -i
+print("""
+ Fingerprint:  {0}
+ Primary Key:  {1}
+  Public Key:  {2}
+  Secret Key:  {3}
+ Sub Key:  {4}
+User IDs:  {5}
+""".format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
+           dmsub.uid))
+#+END_SRC
+
+As well as on the command line with:
+
+#+BEGIN_SRC shell
+  bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+  ~/.gnupg-dm/pubring.kbx
+  ----------------------
+  sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+       177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+  uid           [ultimate] Danger Mouse <dm@secret.example.net>
+  ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+  bash-4.4$
+#+END_SRC
+
+
+** User IDs
+   :PROPERTIES:
+   :CUSTOM_ID: keygen-uids
+   :END:
+
+
+*** Adding User IDs
+    :PROPERTIES:
+    :CUSTOM_ID: keygen-uids-add
+    :END:
+
+By comparison to creating primary keys and subkeys, adding a new user
+ID to an existing key is much simpler.  The method used to do this is
+=key_add_uid= and the only arguments it takes are for the =key= and
+the new =uid=.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+c.home_dir = "~/.gnupg-dm"
+
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+key = c.get_key(dmfpr, secret=True)
+uid = "Danger Mouse <danger.mouse@secret.example.net>"
+
+c.key_add_uid(key, uid)
+#+END_SRC
+
+Unsurprisingly the result of this is:
+
+#+BEGIN_SRC shell
+  bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+  ~/.gnupg-dm/pubring.kbx
+  ----------------------
+  sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+       177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+  uid           [ultimate] Danger Mouse <danger.mouse@secret.example.net>
+  uid           [ultimate] Danger Mouse <dm@secret.example.net>
+  ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+  bash-4.4$
+#+END_SRC
+
+
+*** Revokinging User IDs
+    :PROPERTIES:
+    :CUSTOM_ID: keygen-uids-revoke
+    :END:
+
+Revoking a user ID is a fairly similar process, except that it uses
+the =key_revoke_uid= method.
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+c.home_dir = "~/.gnupg-dm"
+
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+key = c.get_key(dmfpr, secret=True)
+uid = "Danger Mouse <danger.mouse@secret.example.net>"
+
+c.key_revoke_uid(key, uid)
+#+END_SRC
+
+
+** Key certification
+   :PROPERTIES:
+   :CUSTOM_ID: key-sign
+   :END:
+
+Since key certification is more frequently referred to as key signing,
+the method used to perform this function is =key_sign=.
+
+The =key_sign= method takes four arguments: =key=, =uids=,
+=expires_in= and =local=.  The default value of =uids= is =None= and
+which results in all user IDs being selected.  The default value of
+both =expires_in= and =local= is =False=; which results in the
+signature never expiring and being able to be exported.
+
+The =key= is the key being signed rather than the key doing the
+signing.  To change the key doing the signing refer to the signing key
+selection above for signing messages and files.
+
+If the =uids= value is not =None= then it must either be a string to
+match a single user ID or a list of strings to match multiple user
+IDs.  In this case the matching of those strings must be precise and
+it is case sensitive.
+
+To sign Danger Mouse's key for just the initial user ID with a
+signature which will last a little over a month, do this:
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+uid = "Danger Mouse <dm@secret.example.net>"
+
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+key = c.get_key(dmfpr, secret=True)
+c.key_sign(key, uids=uid, expires_in=2764800)
+#+END_SRC
+
+
+* Advanced or Experimental Use Cases
+  :PROPERTIES:
+  :CUSTOM_ID: advanced-use
+  :END:
+
+
+** C plus Python plus SWIG plus Cython
+   :PROPERTIES:
+   :CUSTOM_ID: cython
+   :END:
+
+In spite of the apparent incongruence of using Python bindings to a C
+interface only to generate more C from the Python; it is in fact quite
+possible to use the GPGME bindings with [[http://docs.cython.org/en/latest/index.html][Cython]].  Though in many cases
+the benefits may not be obvious since the most computationally
+intensive work never leaves the level of the C code with which GPGME
+itself is interacting with.
+
+Nevertheless, there are some situations where the benefits are
+demonstrable.  One of the better and easier examples being the one of
+the early examples in this HOWTO, the [[#howto-keys-counting][key counting]] code.  Running that
+example as an executable Python script, =keycount.py= (available in
+the =examples/howto/= directory), will take a noticable amount of time
+to run on most systems where the public keybox or keyring contains a
+few thousand public keys.
+
+Earlier in the evening, prior to starting this section, I ran that
+script on my laptop; as I tend to do periodically and timed it using
+=time= utility, with the following results:
+
+#+BEGIN_SRC shell
+  bash-4.4$ time keycount.py
+
+  Number of secret keys:  23
+  Number of public keys:  12112
+
+
+  real 11m52.945s
+  user 0m0.913s
+  sys  0m0.752s
+
+  bash-4.4$
+#+END_SRC
+
+Sometime after that I imported another key and followed it with a
+little test of Cython.  This test was kept fairly basic, essentially
+lifting the material from the [[http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html][Cython Basic Tutorial]] to demonstrate
+compiling Python code to C.  The first step was to take the example
+key counting code quoted previously, essentially from the importing of
+the =gpg= module to the end of the script:
+
+#+BEGIN_SRC python -i
+import gpg
+
+c = gpg.Context()
+seckeys = c.keylist(pattern=None, secret=True)
+pubkeys = c.keylist(pattern=None, secret=False)
+
+seclist = list(seckeys)
+secnum = len(seclist)
+
+publist = list(pubkeys)
+pubnum = len(publist)
+
+print("""
+    Number of secret keys:  {0}
+    Number of public keys:  {1}
+
+""".format(secnum, pubnum))
+#+END_SRC
+
+Save that into a file called =keycount.pyx= and then create a
+=setup.py= file which contains this:
+
+#+BEGIN_SRC python -i
+from distutils.core import setup
+from Cython.Build import cythonize
+
+setup(
+    ext_modules = cythonize("keycount.pyx")
+)
+#+END_SRC
+
+Compile it:
+
+#+BEGIN_SRC shell
+  bash-4.4$ python setup.py build_ext --inplace
+  bash-4.4$
+#+END_SRC
+
+Then run it in a similar manner to =keycount.py=:
+
+#+BEGIN_SRC shell
+  bash-4.4$ time python3.7 -c "import keycount"
+
+  Number of secret keys:  23
+  Number of public keys:  12113
+
+
+  real 6m47.905s
+  user 0m0.785s
+  sys  0m0.331s
+
+  bash-4.4$
+#+END_SRC
+
+Cython turned =keycount.pyx= into an 81KB =keycount.o= file in the
+=build/= directory, a 24KB =keycount.cpython-37m-darwin.so= file to be
+imported into Python 3.7 and a 113KB =keycount.c= generated C source
+code file of nearly three thousand lines.  Quite a bit bigger than the
+314 bytes of the =keycount.pyx= file or the full 1,452 bytes of the
+full executable =keycount.py= example script.
+
+On the other hand it ran in nearly half the time; taking 6 minutes and
+47.905 seconds to run.  As opposed to the 11 minutes and 52.945 seconds
+which the CPython script alone took.
+
+The =keycount.pyx= and =setup.py= files used to generate this example
+have been added to the =examples/howto/advanced/cython/= directory
+The example versions include some additional options to annotate the
+existing code and to detect Cython's use.  The latter comes from the
+[[http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd][Magic Attributes]] section of the Cython documentation.
+
+
+* Miscellaneous extras and work-arounds
+  :PROPERTIES:
+  :CUSTOM_ID: cheats-and-hacks
+  :END:
+
+Most of the things in the following sections are here simply because
+there was no better place to put them, even though some are only
+peripherally related to the GPGME Python bindings.  Some are also
+workarounds for functions not integrated with GPGME as yet.  This is
+especially true of the first of these, dealing with [[#group-lines][group lines]].
+
+
+** Group lines
+   :PROPERTIES:
+   :CUSTOM_ID: group-lines
+   :END:
+
+There is not yet an easy way to access groups configured in the
+gpg.conf file from within GPGME.  As a consequence these central
+groupings of keys cannot be shared amongst multiple programs, such as
+MUAs readily.
+
+The following code, however, provides a work-around for obtaining this
+information in Python.
+
+#+BEGIN_SRC python -i
+import subprocess
+import sys
+
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-options gpg"
+else:
+    gpgconfcmd = "gpgconf --list-options gpg"
+
+try:
+    lines = subprocess.getoutput(gpgconfcmd).splitlines()
+except:
+    process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+    procom = process.communicate()
+    if sys.version_info[0] == 2:
+        lines = procom[0].splitlines()
+    else:
+        lines = procom[0].decode().splitlines()
+
+for i in range(len(lines)):
+    if lines[i].startswith("group") is True:
+        line = lines[i]
+    else:
+        pass
+
+groups = line.split(":")[-1].replace('"', '').split(',')
+
+group_lines = []
+group_lists = []
+
+for i in range(len(groups)):
+    group_lines.append(groups[i].split("="))
+    group_lists.append(groups[i].split("="))
+
+for i in range(len(group_lists)):
+    group_lists[i][1] = group_lists[i][1].split()
+#+END_SRC
+
+The result of that code is that =group_lines= is a list of lists where
+=group_lines[i][0]= is the name of the group and =group_lines[i][1]=
+is the key IDs of the group as a string.
+
+The =group_lists= result is very similar in that it is a list of
+lists.  The first part, =group_lists[i][0]= matches
+=group_lines[i][0]= as the name of the group, but =group_lists[i][1]=
+is the key IDs of the group as a string.
+
+A demonstration of using the =groups.py= module is also available in
+the form of the executable =mutt-groups.py= script.  This second
+script reads all the group entries in a user's =gpg.conf= file and
+converts them into crypt-hooks suitable for use with the Mutt and
+Neomutt mail clients.
+
+
+** Keyserver access for Python
+   :PROPERTIES:
+   :CUSTOM_ID: hkp4py
+   :END:
+
+The [[https://github.com/Selfnet/hkp4py][hkp4py]] module by Marcel Fest was originally a port of the old
+[[https://github.com/dgladkov/python-hkp][python-hkp]] module from Python 2 to Python 3 and updated to use the
+[[http://docs.python-requests.org/en/latest/index.html][requests]] module instead.  It has since been modified to provide
+support for Python 2.7 as well and is available via PyPI.
+
+Since it rewrites the =hkp= protocol prefix as =http= and =hkps= as
+=https=, the module is able to be used even with servers which do not
+support the full scope of keyserver functions.[fn:5]  It also works quite
+readily when incorporated into a [[#cython][Cython]] generated and compiled version
+of any code.
+
+
+*** Key import format
+    :PROPERTIES:
+    :CUSTOM_ID: hkp4py-strings
+    :END:
+
+The hkp4py module returns key data via requests as string literals
+(=r.text=) instead of byte literals (=r.content=).  This means that
+the retrurned key data must be encoded to UTF-8 when importing that
+key material using a =gpg.Context().key_import()= method.
+
+For this reason an alternative method has been added to the =search=
+function of =hkp4py.KeyServer()= which returns the key in the correct
+format as expected by =key_import=.  When importing using this module,
+it is now possible to import with this:
+
+#+BEGIN_SRC python -i
+for key in keys:
+    if key.revoked is False:
+        gpg.Context().key_import(key.key_blob)
+    else:
+        pass
+#+END_SRC
+
+Without that recent addition it would have been necessary to encode
+the contents of each =hkp4py.KeyServer().search()[i].key= in
+=hkp4py.KeyServer().search()= before trying to import it.
+
+An example of this is included in the [[#howto-import-key][Importing Keys]] section of this
+HOWTO and the corresponding executable version of that example is
+available in the =lang/python/examples/howto= directory as normal; the
+executable version is the =import-keys-hkp.py= file.
+
+
+* Copyright and Licensing
+  :PROPERTIES:
+  :CUSTOM_ID: copyright-and-license
+  :END:
+
+
+** Copyright
+   :PROPERTIES:
+   :CUSTOM_ID: copyright
+   :END:
+
+Copyright © The GnuPG Project, 2018.
+
+Copyright (C) The GnuPG Project, 2018.
+
+
+** Draft Editions of this HOWTO
+   :PROPERTIES:
+   :CUSTOM_ID: draft-editions
+   :END:
+
+Draft editions of this HOWTO may be periodically available directly
+from the author at any of the following URLs:
+
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 no SSL)]]
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no SSL)]]
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 no SSL)]]
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 no SSL)]]
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no SSL)]]
+
+All of these draft versions except for one have been generated from
+this document via Emacs [[https://orgmode.org/][Org mode]] and [[https://www.gnu.org/software/texinfo/][GNU Texinfo]].  Though it is likely
+that the specific [[https://files.au.adversary.org/crypto/gpgme-python-howto.org][file]] [[http://files.au.adversary.org/crypto/gpgme-python-howto.org][version]] used will be on the same server with
+the generated output formats.
+
+The one exception is the reStructuredText version, which was converted
+using the latest version of Pandoc from the Org mode source file using
+the following command:
+
+#+BEGIN_SRC shell
+  pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org
+#+END_SRC
+
+In addition to these there is a significantly less frequently updated
+version as a HTML [[https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html][WebHelp site]] (AWS S3 SSL); generated from DITA XML
+source files, which can be found in [[https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/][an alternative branch]] of the GPGME
+git repository.
+
+These draft editions are not official documents and the version of
+documentation in the master branch or which ships with released
+versions is the only official documentation.  Nevertheless, these
+draft editions may occasionally be of use by providing more accessible
+web versions which are updated between releases.  They are provided on
+the understanding that they may contain errors or may contain content
+subject to change prior to an official release.
+
+
+** License GPL compatible
+   :PROPERTIES:
+   :CUSTOM_ID: license
+   :END:
+
+This file is free software; as a special exception the author gives
+unlimited permission to copy and/or distribute it, with or without
+modifications, as long as this notice is preserved.
+
+This file is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.
+
+
+* Footnotes
+
+[fn:1] =short-history.org= and/or =short-history.html=.
+
+[fn:2] With no issues reported specific to Python 3.7, the release of
+Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing
+with Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of
+3.6 now.  Production environments with more conservative requirements
+will always enforce their own policies anyway and installation to each
+supported minor release is quite possible too.
+
+[fn:3] Yes, even if you use virtualenv with everything you do in
+Python.  If you want to install this module as just your user account
+then you will need to manually configure, compile and install the
+/entire/ GnuPG stack as that user as well.  This includes libraries
+which are not often installed that way.  It can be done and there are
+circumstances under which it is worthwhile, but generally only on
+POSIX systems which utilise single user mode (some even require it).
+
+[fn:4] You probably don't really want to do this.  Searching the
+keyservers for "gnupg.org" produces over 400 results, the majority of
+which aren't actually at the gnupg.org domain, but just included a
+comment regarding the project in their key somewhere.
+
+[fn:5] Such as with ProtonMail servers.  This also means that
+restricted servers which only advertise either HTTP or HTTPS end
+points and not HKP or HKPS end points must still be identified as as
+HKP or HKPS within the Python Code.  The =hkp4py= module will rewrite
+these appropriately when the connection is made to the server.
diff --git a/lang/python/doc/src/index.org b/lang/python/doc/src/index.org
new file mode 100644 (file)
index 0000000..701d986
--- /dev/null
@@ -0,0 +1,25 @@
+#+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings
+#+AUTHOR: Ben McGinnes
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Times New Roman}
+#+LATEX_HEADER: \author{Ben McGinnes <ben@gnupg.org>}
+
+
+* GPGME Python Bindings
+  :PROPERTIES:
+  :CUSTOM_ID: index
+  :END:
+
+
+** Contents
+   :PROPERTIES:
+   :CUSTOM_ID: index-contents
+   :END:
+
+
+- [[file:short-history.org][A short history of the project]]
+- [[file:gpgme-python-howto.org][GPGME Python Bindings HOWTO]]
diff --git a/lang/python/doc/src/short-history.org b/lang/python/doc/src/short-history.org
new file mode 100644 (file)
index 0000000..587cb9f
--- /dev/null
@@ -0,0 +1,172 @@
+#+TITLE: A Short History of the GPGME bindings for Python
+#+LATEX_COMPILER: xelatex
+#+LATEX_CLASS: article
+#+LATEX_CLASS_OPTIONS: [12pt]
+#+LATEX_HEADER: \usepackage{xltxtra}
+#+LATEX_HEADER: \usepackage[margin=1in]{geometry}
+#+LATEX_HEADER: \setmainfont[Ligatures={Common}]{Latin Modern Roman}
+
+* Overview
+  :PROPERTIES:
+  :CUSTOM_ID: overview
+  :END:
+
+The GPGME Python bindings passed through many hands and numerous
+phases before, after a fifteen year journey, coming full circle to
+return to the source.  This is a short explanation of that journey.
+
+** In the beginning
+   :PROPERTIES:
+   :CUSTOM_ID: in-the-begining
+   :END:
+
+   In 2002 John Goerzen released PyME; Python bindings for the GPGME
+   module which utilised the current release of Python of the time and
+   SWIG.[fn:1]  Shortly after creating it and ensuring it worked he stopped
+   supporting it, though he left his work available on his Gopher
+   site.
+
+** Keeping the flame alive
+   :PROPERTIES:
+   :CUSTOM_ID: keeping-the-flame-alive
+   :END:
+
+   A couple of years later the project was picked up by Igor Belyi and
+   actively developed and maintained by him from 2004 to 2008.  Igor's
+   whereabouts at the time of this document's creation are unknown,
+   but the current authors do hope he is well.  We're assuming (or
+   hoping) that life did what life does and made continuing untenable.
+
+** Passing the torch
+   :PROPERTIES:
+   :CUSTOM_ID: passing-the-torch
+   :END:
+
+   In 2014 Martin Albrecht wanted to patch a bug in the PyME code and
+   discovered the absence of Igor.  Following a discussion on the PyME
+   mailing list he became the new maintainer for PyME, releasing
+   version 0.9.0 in May of that year.  He remains the maintainer of
+   the original PyME release in Python 2.6 and 2.7 (available via
+   PyPI).
+
+** Coming full circle
+   :PROPERTIES:
+   :CUSTOM_ID: ouroboros
+   :END:
+
+   In 2015 Ben McGinnes approached Martin about a Python 3 version,
+   while investigating how complex a task this would be the task ended
+   up being completed.  A subsequent discussion with Werner Koch led
+   to the decision to fold the Python 3 port back into the original
+   GPGME release in the languages subdirectory for non-C bindings
+   under the module name of =pyme3=.
+
+   In 2016 this PyME module was integrated back into the GPGME project
+   by Justus Winter.  During the course of this work Justus adjusted
+   the port to restore limited support for Python 2, but not as many
+   minor point releases as the original PyME package supports.  During
+   the course of this integration the package was renamed to more
+   accurately reflect its status as a component of GPGME.  The =pyme3=
+   module was renamed to =gpg= and adopted by the upstream GnuPG team.
+
+   In 2017 Justus departed G10code and the GnuPG team.  Following this
+   Ben returned to maintain of gpgme Python bindings and continue
+   building them from that point.
+
+* Relics of the past
+  :PROPERTIES:
+  :CUSTOM_ID: relics-past
+  :END:
+
+There are a few things, in addition to code specific factors, such as
+SWIG itself, which are worth noting here.
+
+** The Annoyances of Git
+   :PROPERTIES:
+   :CUSTOM_ID: the-annoyances-of-git
+   :END:
+
+   As anyone who has ever worked with git knows, submodules are
+   horrible way to deal with pretty much anything.  In the interests
+   of avoiding migraines, that was skipped with addition of the PyME
+   code to GPGME.
+
+   Instead the files were added to a subdirectory of the =lang/=
+   directory, along with a copy of the entire git log up to that point
+   as a separate file within the =lang/python/docs/= directory.[fn:2]
+   As the log for PyME is nearly 100KB and the log for GPGME is
+   approximately 1MB, this would cause considerable bloat, as well as
+   some confusion, should the two be merged.
+
+   Hence the unfortunate, but necessary, step to simply move the
+   files.  A regular repository version has been maintained should it
+   be possible to implement this better in the future.
+
+** The Perils of PyPI
+   :PROPERTIES:
+   :CUSTOM_ID: the-perils-of-pypi
+   :END:
+
+   The early port of the Python 2 =pyme= module as =pyme3= was never
+   added to PyPI while the focus remained on development and testing
+   during 2015 and early 2016.  Later in 2016, however, when Justus
+   completed his major integration work and subsequently renamed the
+   module from =pyme3= to =gpg=, some prior releases were also
+   provided through PyPI.
+
+   Since these bindings require a matching release of the GPGME
+   libraries in order to function, it was determined that there was
+   little benefit in also providing a copy through PyPI since anyone
+   obtaining the GPGME source code would obtain the Python bindings
+   source code at the same time.  Whereas there was the potential to
+   sew confusion amongst Python users installing the module from PyPI,
+   only to discover that without the relevant C files, header files or
+   SWIG compiled binaries, the Python module did them little good.
+
+   There are only two files on PyPI which might turn up in a search
+   for this module or a sample of its content:
+
+   1. gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library
+   2. pyme (0.9.0) - Python support for GPGME GnuPG cryptography library
+
+*** GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library
+    :PROPERTIES:
+    :CUSTOM_ID: pypi-gpgme-180
+    :END:
+
+    This is the most recent version to reach PyPI and is the version
+    of the official Pyhon bindings which shipped with GPGME 1.8.0.  If
+    you have GPGME 1.8.0 installed and /only/ 1.8.0 installed, then it
+    is probably safe to use this copy from PyPI.
+
+    As there have been a lot of changes since the release of GPGME
+    1.8.0, the GnuPG Project recommends not using this version of the
+    module and instead installing the current version of GPGME along
+    with the Python bindings included with that package.
+
+*** PyME 0·9·0 - Python support for GPGME GnuPG cryptography library
+    :PROPERTIES:
+    :CUSTOM_ID: pypi-gpgme-90
+    :END:
+
+    This is the last release of the PyME bindings maintained by Martin
+    Albrecht and is only compatible with Python 2, it will not work
+    with Python 3.  This is the version of the software from which the
+    port from Python 2 to Python 3 code was made in 2015.
+
+    Users of the more recent Python bindings will recognise numerous
+    points of similarity, but also significant differences.  It is
+    likely that the more recent official bindings will feel "more
+    pythonic."
+
+    For those using Python 2, there is essentially no harm in using
+    this module, but it may lack a number of more recent features
+    added to GPGME.
+
+* Footnotes
+
+[fn:1] In all likelihood thos would have been Python 2.2 or possibly
+Python 2.3.
+
+[fn:2] The entire PyME git log and other preceding VCS logs are
+located in the =gpgme/lang/python/docs/old-commits.log= file.
diff --git a/lang/python/doc/texinfo/gpgme-python-howto.texi b/lang/python/doc/texinfo/gpgme-python-howto.texi
new file mode 100644 (file)
index 0000000..40beb7a
--- /dev/null
@@ -0,0 +1,3155 @@
+\input texinfo    @c -*- texinfo -*-
+@c %**start of header
+@setfilename gpgme-python-howto.info
+@settitle GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
+@documentencoding UTF-8
+@documentlanguage en
+@c %**end of header
+
+@finalout
+@titlepage
+@title GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
+@author Ben McGinnes
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top
+@top GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
+@end ifnottex
+
+@menu
+* Introduction::
+* GPGME Concepts::
+* GPGME Python bindings installation::
+* Fundamentals::
+* Working with keys::
+* Basic Functions::
+* Creating keys and subkeys::
+* Advanced or Experimental Use Cases::
+* Miscellaneous extras and work-arounds::
+* Copyright and Licensing::
+
+@detailmenu
+--- The Detailed Node Listing ---
+
+Introduction
+
+* Python 2 versus Python 3::
+* Examples::
+* Unofficial Drafts::
+* What's New::
+
+What's New
+
+* New in GPGME 1·12·0::
+
+GPGME Concepts
+
+* A C API::
+* Python bindings::
+* Difference between the Python bindings and other GnuPG Python packages::
+
+Difference between the Python bindings and other GnuPG Python packages
+
+* The python-gnupg package maintained by Vinay Sajip::
+* The gnupg package created and maintained by Isis Lovecruft::
+* The PyME package maintained by Martin Albrecht::
+
+GPGME Python bindings installation
+
+* No PyPI::
+* Requirements::
+* Installation::
+* Known Issues::
+
+Requirements
+
+* Recommended Additions::
+
+Installation
+
+* Installing GPGME::
+
+Known Issues
+
+* Breaking Builds::
+* Reinstalling Responsibly::
+* Multiple installations::
+* Won't Work With Windows::
+* CFFI is the Best™ and GPGME should use it instead of SWIG::
+* Virtualised Environments::
+
+Fundamentals
+
+* No REST::
+* Context::
+
+Working with keys
+
+* Key selection::
+* Get key::
+* Importing keys::
+* Exporting keys::
+
+Key selection
+
+* Counting keys::
+
+Importing keys
+
+* Working with ProtonMail::
+* Importing with HKP for Python::
+* Importing from ProtonMail with HKP for Python::
+
+Exporting keys
+
+* Exporting public keys::
+* Exporting secret keys::
+* Sending public keys to the SKS Keyservers::
+
+Basic Functions
+
+* Encryption::
+* Decryption::
+* Signing text and files::
+* Signature verification::
+
+Encryption
+
+* Encrypting to one key::
+* Encrypting to multiple keys::
+
+Signing text and files
+
+* Signing key selection::
+* Normal or default signing messages or files::
+* Detached signing messages and files::
+* Clearsigning messages or text::
+
+Creating keys and subkeys
+
+* Primary key::
+* Subkeys::
+* User IDs::
+* Key certification::
+
+User IDs
+
+* Adding User IDs::
+* Revokinging User IDs::
+
+Advanced or Experimental Use Cases
+
+* C plus Python plus SWIG plus Cython::
+
+Miscellaneous extras and work-arounds
+
+* Group lines::
+* Keyserver access for Python::
+
+Keyserver access for Python
+
+* Key import format::
+
+Copyright and Licensing
+
+* Copyright::
+* Draft Editions of this HOWTO::
+* License GPL compatible::
+
+@end detailmenu
+@end menu
+
+@node Introduction
+@chapter Introduction
+
+@multitable {aaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}
+@item Version:
+@tab 0.1.4
+@item GPGME Version:
+@tab 1.12.0
+@item Author:
+@tab @uref{https://gnupg.org/people/index.html#sec-1-5, Ben McGinnes} <ben@@gnupg.org>
+@item Author GPG Key:
+@tab DB4724E6FA4286C92B4E55C4321E4E2373590E5D
+@item Language:
+@tab Australian English, British English
+@item xml:lang:
+@tab en-AU, en-GB, en
+@end multitable
+
+This document provides basic instruction in how to use the GPGME
+Python bindings to programmatically leverage the GPGME library.
+
+@menu
+* Python 2 versus Python 3::
+* Examples::
+* Unofficial Drafts::
+* What's New::
+@end menu
+
+@node Python 2 versus Python 3
+@section Python 2 versus Python 3
+
+Though the GPGME Python bindings themselves provide support for both
+Python 2 and 3, the focus is unequivocally on Python 3 and
+specifically from Python 3.4 and above.  As a consequence all the
+examples and instructions in this guide use Python 3 code.
+
+Much of it will work with Python 2, but much of it also deals with
+Python 3 byte literals, particularly when reading and writing data.
+Developers concentrating on Python 2.7, and possibly even 2.6, will
+need to make the appropriate modifications to support the older string
+and unicode types as opposed to bytes.
+
+There are multiple reasons for concentrating on Python 3; some of
+which relate to the immediate integration of these bindings, some of
+which relate to longer term plans for both GPGME and the python
+bindings and some of which relate to the impending EOL period for
+Python 2.7.  Essentially, though, there is little value in tying the
+bindings to a version of the language which is a dead end and the
+advantages offered by Python 3 over Python 2 make handling the data
+types with which GPGME deals considerably easier.
+
+@node Examples
+@section Examples
+
+All of the examples found in this document can be found as Python 3
+scripts in the @samp{lang/python/examples/howto} directory.
+
+@node Unofficial Drafts
+@section Unofficial Drafts
+
+In addition to shipping with each release of GPGME, there is a section
+on locations to read or download @ref{Draft Editions of this HOWTO, , draft editions} of this document from
+at the end of it.  These are unofficial versions produced in between
+major releases.
+
+@node What's New
+@section What's New
+
+The most obviously new point for those reading this guide is this
+section on other new things, but that's hardly important.  Not given
+all the other things which spurred the need for adding this section
+and its subsections.
+
+@menu
+* New in GPGME 1·12·0::
+@end menu
+
+@node New in GPGME 1·12·0
+@subsection New in GPGME 1·12·0
+
+There have been quite a number of additions to GPGME and the Python
+bindings to it since the last release of GPGME with versions 1.11.0
+and 1.11.1 in April, 2018.
+
+The bullet points of new additiions are:
+
+@itemize
+@item
+an expanded section on @ref{Installation, , installing} and @ref{Known Issues, , troubleshooting} the Python
+bindings.
+@item
+The release of Python 3.7.0; which appears to be working just fine
+with our bindings, in spite of intermittent reports of problems for
+many other Python projects with that new release.
+@item
+Python 3.7 has been moved to the head of the specified python
+versions list in the build process.
+@item
+In order to fix some other issues, there are certain underlying
+functions which are more exposed through the @ref{Context, , gpg.Context()}, but
+ongoing documentation ought to clarify that or otherwise provide the
+best means of using the bindings.  Some additions to @samp{gpg.core} and
+the @samp{Context()}, however, were intended (see below).
+@item
+Continuing work in identifying and confirming the cause of
+oft-reported @ref{Won't Work With Windows, , problems installing the Python bindings on Windows}.
+@item
+GSOC: Google's Surreptitiously Ordered Conscription @dots{} erm @dots{} oh,
+right; Google's Summer of Code.  Though there were two hopeful
+candidates this year; only one ended up involved with the GnuPG
+Project directly, the other concentrated on an unrelated third party
+project with closer ties to one of the GNU/Linux distributions than
+to the GnuPG Project.  Thus the Python bindings benefited from GSOC
+participant Jacob Adams, who added the key@math{_import} function; building
+on prior work by Tobias Mueller.
+@item
+Several new methods functions were added to the gpg.Context(),
+including: @ref{Importing keys, , key@math{_import}}, @ref{Exporting keys, , key@math{_export}}, @ref{Exporting public keys, , key@math{_export}@math{_minimal}} and
+@ref{Exporting secret keys, , key@math{_export}@math{_secret}}.
+@item
+Importing and exporting examples include versions integrated with
+Marcel Fest's recently released @uref{https://github.com/Selfnet/hkp4py, HKP for Python} module.  Some
+@ref{Keyserver access for Python, , additional notes on this module} are included at the end of the HOWTO.
+@item
+Instructions for dealing with semi-walled garden implementations
+like ProtonMail are also included.  This is intended to make things
+a little easier when communicating with users of ProtonMail's
+services and should not be construed as an endorsement of said
+service.  The GnuPG Project neither favours, nor disfavours
+ProtonMail and the majority of this deals with interacting with the
+ProtonMail keyserver.
+@item
+Semi-formalised the location where @ref{Draft Editions of this HOWTO, , draft versions} of this HOWTO may
+periodically be accessible.  This is both for the reference of
+others and testing the publishing of the document itself.  Renamed
+this file at around the same time.
+@item
+The Texinfo documentation build configuration has been replicated
+from the parent project in order to make to maintain consistency
+with that project (and actually ship with each release).
+@item
+a reStructuredText (@samp{.rst}) version is also generated for Python
+developers more used to and comfortable with that format as it is
+the standard Python documentation format and Python developers may
+wish to use it with Sphinx.  Please note that there has been no
+testing of the reStructuredText version with Sphinx at all.  The
+reST file was generated by the simple expedient of using @uref{https://pandoc.org/, Pandoc}.
+@item
+Added a new section for @ref{Advanced or Experimental Use Cases, , advanced or experimental use}.
+@item
+Began the advanced use cases with @ref{C plus Python plus SWIG plus Cython, , a section} on using the module with
+@uref{http://cython.org/, Cython}.
+@item
+Added a number of new scripts to the @samp{example/howto/} directory;
+some of which may be in advance of their planned sections of the
+HOWTO (and some are just there because it seemed like a good idea at
+the time).
+@item
+Cleaned up a lot of things under the hood.
+@end itemize
+
+@node GPGME Concepts
+@chapter GPGME Concepts
+
+@menu
+* A C API::
+* Python bindings::
+* Difference between the Python bindings and other GnuPG Python packages::
+@end menu
+
+@node A C API
+@section A C API
+
+Unlike many modern APIs with which programmers will be more familiar
+with these days, the GPGME API is a C API.  The API is intended for
+use by C coders who would be able to access its features by including
+the @samp{gpgme.h} header file with their own C source code and then access
+its functions just as they would any other C headers.
+
+This is a very effective method of gaining complete access to the API
+and in the most efficient manner possible.  It does, however, have the
+drawback that it cannot be directly used by other languages without
+some means of providing an interface to those languages.  This is
+where the need for bindings in various languages stems.
+
+@node Python bindings
+@section Python bindings
+
+The Python bindings for GPGME provide a higher level means of
+accessing the complete feature set of GPGME itself.  It also provides
+a more pythonic means of calling these API functions.
+
+The bindings are generated dynamically with SWIG and the copy of
+@samp{gpgme.h} generated when GPGME is compiled.
+
+This means that a version of the Python bindings is fundamentally tied
+to the exact same version of GPGME used to generate that copy of
+@samp{gpgme.h}.
+
+@node Difference between the Python bindings and other GnuPG Python packages
+@section Difference between the Python bindings and other GnuPG Python packages
+
+There have been numerous attempts to add GnuPG support to Python over
+the years.  Some of the most well known are listed here, along with
+what differentiates them.
+
+@menu
+* The python-gnupg package maintained by Vinay Sajip::
+* The gnupg package created and maintained by Isis Lovecruft::
+* The PyME package maintained by Martin Albrecht::
+@end menu
+
+@node The python-gnupg package maintained by Vinay Sajip
+@subsection The python-gnupg package maintained by Vinay Sajip
+
+This is arguably the most popular means of integrating GPG with
+Python.  The package utilises the @samp{subprocess} module to implement
+wrappers for the @samp{gpg} and @samp{gpg2} executables normally invoked on the
+command line (@samp{gpg.exe} and @samp{gpg2.exe} on Windows).
+
+The popularity of this package stemmed from its ease of use and
+capability in providing the most commonly required features.
+
+Unfortunately it has been beset by a number of security issues in the
+past; most of which stemmed from using unsafe methods of accessing the
+command line via the @samp{subprocess} calls.  While some effort has been
+made over the last two to three years (as of 2018) to mitigate this,
+particularly by no longer providing shell access through those
+subprocess calls, the wrapper is still somewhat limited in the scope
+of its GnuPG features coverage.
+
+The python-gnupg package is available under the MIT license.
+
+@node The gnupg package created and maintained by Isis Lovecruft
+@subsection The gnupg package created and maintained by Isis Lovecruft
+
+In 2015 Isis Lovecruft from the Tor Project forked and then
+re-implemented the python-gnupg package as just gnupg.  This new
+package also relied on subprocess to call the @samp{gpg} or @samp{gpg2}
+binaries, but did so somewhat more securely.
+
+The naming and version numbering selected for this package, however,
+resulted in conflicts with the original python-gnupg and since its
+functions were called in a different manner to python-gnupg, the
+release of this package also resulted in a great deal of consternation
+when people installed what they thought was an upgrade that
+subsequently broke the code relying on it.
+
+The gnupg package is available under the GNU General Public License
+version 3.0 (or any later version).
+
+@node The PyME package maintained by Martin Albrecht
+@subsection The PyME package maintained by Martin Albrecht
+
+This package is the origin of these bindings, though they are somewhat
+different now.  For details of when and how the PyME package was
+folded back into GPGME itself see the @uref{short-history.org, Short History} document.@footnote{@samp{short-history.org} and/or @samp{short-history.html}.}
+
+The PyME package was first released in 2002 and was also the first
+attempt to implement a low level binding to GPGME.  In doing so it
+provided access to considerably more functionality than either the
+@samp{python-gnupg} or @samp{gnupg} packages.
+
+The PyME package is only available for Python 2.6 and 2.7.
+
+Porting the PyME package to Python 3.4 in 2015 is what resulted in it
+being folded into the GPGME project and the current bindings are the
+end result of that effort.
+
+The PyME package is available under the same dual licensing as GPGME
+itself: the GNU General Public License version 2.0 (or any later
+version) and the GNU Lesser General Public License version 2.1 (or any
+later version).
+
+@node GPGME Python bindings installation
+@chapter GPGME Python bindings installation
+
+@menu
+* No PyPI::
+* Requirements::
+* Installation::
+* Known Issues::
+@end menu
+
+@node No PyPI
+@section No PyPI
+
+Most third-party Python packages and modules are available and
+distributed through the Python Package Installer, known as PyPI.
+
+Due to the nature of what these bindings are and how they work, it is
+infeasible to install the GPGME Python bindings in the same way.
+
+This is because the bindings use SWIG to dynamically generate C
+bindings against @samp{gpgme.h} and @samp{gpgme.h} is generated from
+@samp{gpgme.h.in} at compile time when GPGME is built from source.  Thus to
+include a package in PyPI which actually built correctly would require
+either statically built libraries for every architecture bundled with
+it or a full implementation of C for each architecture.
+
+See the additional notes regarding @ref{CFFI is the Best™ and GPGME should use it instead of SWIG, , CFFI and SWIG} at the end of this
+section for further details.
+
+@node Requirements
+@section Requirements
+
+The GPGME Python bindings only have three requirements:
+
+@enumerate
+@item
+A suitable version of Python 2 or Python 3.  With Python 2 that
+means CPython 2.7 and with Python 3 that means CPython 3.4 or
+higher.
+@item
+@uref{https://www.swig.org, SWIG}.
+@item
+GPGME itself.  Which also means that all of GPGME's dependencies
+must be installed too.
+@end enumerate
+
+@menu
+* Recommended Additions::
+@end menu
+
+@node Recommended Additions
+@subsection Recommended Additions
+
+Though none of the following are absolute requirements, they are all
+recommended for use with the Python bindings.  In some cases these
+recommendations refer to which version(s) of CPython to use the
+bindings with, while others refer to third party modules which provide
+a significant advantage in some way.
+
+@enumerate
+@item
+If possible, use Python 3 instead of 2.
+@item
+Favour a more recent version of Python since even 3.4 is due to
+reach EOL soon.  In production systems and services, Python 3.6
+should be robust enough to be relied on.
+@item
+If possible add the following Python modules which are not part of
+the standard library: @uref{http://docs.python-requests.org/en/latest/index.html, Requests}, @uref{http://cython.org/, Cython} and @uref{https://github.com/Selfnet/hkp4py, hkp4py}.  Chances are
+quite high that at least the first one and maybe two of those will
+already be installed.
+@end enumerate
+
+Note that, as with Cython, some of the planned additions to the
+@ref{Advanced or Experimental Use Cases, , Advanced} section, will bring with them additional requirements.  Most
+of these will be fairly well known and commonly installed ones,
+however, which are in many cases likely to have already been installed
+on many systems or be familiar to Python programmers.
+
+@node Installation
+@section Installation
+
+Installing the Python bindings is effectively achieved by compiling
+and installing GPGME itself.
+
+Once SWIG is installed with Python and all the dependencies for GPGME
+are installed you only need to confirm that the version(s) of Python
+you want the bindings installed for are in your @samp{$PATH}.
+
+By default GPGME will attempt to install the bindings for the most
+recent or highest version number of Python 2 and Python 3 it detects
+in @samp{$PATH}.  It specifically checks for the @samp{python} and @samp{python3}
+executables first and then checks for specific version numbers.
+
+For Python 2 it checks for these executables in this order: @samp{python},
+@samp{python2} and @samp{python2.7}.
+
+For Python 3 it checks for these executables in this order: @samp{python3},
+ @samp{python3.7}, @samp{python3.6}, @samp{python3.5} and @samp{python3.4}.@footnote{With no issues reported specific to Python 3.7, the release of
+Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing
+with Python 3.7.1rc1, there is no reason to delay moving 3.7 ahead of
+3.6 now.  Production environments with more conservative requirements
+will always enforce their own policies anyway and installation to each
+supported minor release is quite possible too.}
+
+On systems where @samp{python} is actually @samp{python3} and not @samp{python2} it
+may be possible that @samp{python2} may be overlooked, but there have been
+no reports of that actually occurring as yet.
+
+In the three months or so since the release of Python 3.7.0 there has
+been extensive testing and work with these bindings with no issues
+specifically relating to the new version of Python or any of the new
+features of either the language or the bindings.  This has also been
+the case with Python 3.7.1rc1.  With that in mind and given the
+release of Python 3.7.1 is scheduled for around the same time as GPGME
+1.12.0, the order of preferred Python versions has been changed to
+move Python 3.7 ahead of Python 3.6.
+
+@menu
+* Installing GPGME::
+@end menu
+
+@node Installing GPGME
+@subsection Installing GPGME
+
+See the GPGME @samp{README} file for details of how to install GPGME from
+source.
+
+@node Known Issues
+@section Known Issues
+
+There are a few known issues with the current build process and the
+Python bindings.  For the most part these are easily addressed should
+they be encountered.
+
+@menu
+* Breaking Builds::
+* Reinstalling Responsibly::
+* Multiple installations::
+* Won't Work With Windows::
+* CFFI is the Best™ and GPGME should use it instead of SWIG::
+* Virtualised Environments::
+@end menu
+
+@node Breaking Builds
+@subsection Breaking Builds
+
+Occasionally when installing GPGME with the Python bindings included
+it may be observed that the @samp{make} portion of that process induces a
+large very number of warnings and, eventually errors which end that
+part of the build process.  Yet following that with @samp{make check} and
+@samp{make install} appears to work seamlessly.
+
+The cause of this is related to the way SWIG needs to be called to
+dynamically generate the C bindings for GPGME in the first place.  So
+the entire process will always produce @samp{lang/python/python2-gpg/} and
+@samp{lang/python/python3-gpg/} directories.  These should contain the
+build output generated during compilation, including the complete
+bindings and module installed into @samp{site-packages}.
+
+Occasionally the errors in the early part or some other conflict
+(e.g. not installing as @strong{@emph{root}} or @strong{@emph{su}}) may result in nothing
+being installed to the relevant @samp{site-packages} directory and the
+build directory missing a lot of expected files.  Even when this
+occurs, the solution is actually quite simple and will always work.
+
+That solution is simply to run the following commands as either the
+@strong{root} user or prepended with @samp{sudo -H}@footnote{Yes, even if you use virtualenv with everything you do in
+Python.  If you want to install this module as just your user account
+then you will need to manually configure, compile and install the
+@emph{entire} GnuPG stack as that user as well.  This includes libraries
+which are not often installed that way.  It can be done and there are
+circumstances under which it is worthwhile, but generally only on
+POSIX systems which utilise single user mode (some even require it).} in the @samp{lang/python/}
+directory:
+
+@example
+/path/to/pythonX.Y setup.py build
+/path/to/pythonX.Y setup.py build
+/path/to/pythonX.Y setup.py install
+@end example
+
+Yes, the build command does need to be run twice.  Yes, you still need
+to run the potentially failing or incomplete steps during the
+@samp{configure}, @samp{make} and @samp{make install} steps with installing GPGME.
+This is because those steps generate a lot of essential files needed,
+both by and in order to create, the bindings (including both the
+@samp{setup.py} and @samp{gpgme.h} files).
+
+@enumerate
+@item
+IMPORTANT Note
+
+
+If specifying a selected number of languages to create bindings for,
+try to leave Python last.  Currently the majority of the other
+language bindings are also preceding Python of either version when
+listed alphabetically and so that just happens by default currently.
+
+If Python is set to precede one of the other languages then it is
+possible that the errors described here may interrupt the build
+process before generating bindings for those other languages.  In
+these cases it may be preferable to configure all preferred language
+bindings separately with alternative @samp{configure} steps for GPGME using
+the @samp{--enable-languages=$LANGUAGE} option.
+@end enumerate
+
+@node Reinstalling Responsibly
+@subsection Reinstalling Responsibly
+
+Regardless of whether you're installing for one version of Python or
+several, there will come a point where reinstallation is required.
+With most Python module installations, the installed files go into the
+relevant site-packages directory and are then forgotten about.  Then
+the module is upgraded, the new files are copied over the old and
+that's the end of the matter.
+
+While the same is true of these bindings, there have been intermittent
+issues observed on some platforms which have benefited significantly
+from removing all the previous installations of the bindings before
+installing the updated versions.
+
+Removing the previous version(s) is simply a matter of changing to the
+relevant @samp{site-packages} directory for the version of Python in
+question and removing the @samp{gpg/} directory and any accompanying
+egg-info files for that module.
+
+In most cases this will require root or administration privileges on
+the system, but the same is true of installing the module in the first
+place.
+
+@node Multiple installations
+@subsection Multiple installations
+
+For a veriety of reasons it may be either necessary or just preferable
+to install the bindings to alternative installed Python versions which
+meet the requirements of these bindings.
+
+On POSIX systems this will generally be most simply achieved by
+running the manual installation commands (build, build, install) as
+described in the previous section for each Python installation the
+bindings need to be installed to.
+
+As per the SWIG documentation: the compilers, libraries and runtime
+used to build GPGME and the Python Bindings @strong{must} match those used to
+compile Python itself, including the version number(s) (at least going
+by major version numbers and probably minor numbers too).
+
+On most POSIX systems, including OS X, this will very likely be the
+case in most, if not all, cases.
+
+@node Won't Work With Windows
+@subsection Won't Work With Windows
+
+There are semi-regular reports of Windows users having considerable
+difficulty in installing and using the Python bindings at all.  Very
+often, possibly even always, these reports come from Cygwin users
+and/or MinGW users and/or Msys2 users.  Though not all of them have
+been confirmed, it appears that these reports have also come from
+people who installed Python using the Windows installer files from the
+@uref{https://python.org, Python website} (i.e. mostly MSI installers, sometimes self-extracting
+@samp{.exe} files).
+
+The Windows versions of Python are not built using Cygwin, MinGW or
+Msys2; they're built using Microsoft Visual Studio.  Furthermore the
+version used is @emph{considerably} more advanced than the version which
+MinGW obtained a small number of files from many years ago in order to
+be able to compile anything at all.  Not only that, but there are
+changes to the version of Visual Studio between some micro releases,
+though that is is particularly the case with Python 2.7, since it has
+been kept around far longer than it should have been.
+
+There are two theoretical solutions to this issue:
+
+@enumerate
+@item
+Compile and install the GnuPG stack, including GPGME and the
+Python bibdings using the same version of Microsoft Visual Studio
+used by the Python Foundation to compile the version of Python
+installed.
+
+If there are multiple versions of Python then this will need to be
+done with each different version of Visual Studio used.
+
+@item
+Compile and install Python using the same tools used by choice,
+such as MinGW or Msys2.
+@end enumerate
+
+Do @strong{not} use the official Windows installer for Python unless
+following the first method.
+
+In this type of situation it may even be for the best to accept that
+there are less limitations on permissive software than free software
+and simply opt to use a recent version of the Community Edition of
+Microsoft Visual Studio to compile and build all of it, no matter
+what.
+
+Investigations into the extent or the limitations of this issue are
+ongoing.
+
+@node CFFI is the Best™ and GPGME should use it instead of SWIG
+@subsection CFFI is the Best™ and GPGME should use it instead of SWIG
+
+There are many reasons for favouring @uref{https://cffi.readthedocs.io/en/latest/overview.html, CFFI} and proponents of it are
+quite happy to repeat these things as if all it would take to switch
+from SWIG to CFFI is repeating that list as if it were a new concept.
+
+The fact is that there are things which Python's CFFI implementation
+cannot handle in the GPGME C code.  Beyond that there are features of
+SWIG which are simply not available with CFFI at all.  SWIG generates
+the bindings to Python using the @samp{gpgme.h} file, but that file is not
+a single version shipped with each release, it too is generated when
+GPGME is compiled.
+
+CFFI is currently unable to adapt to such a potentially mutable
+codebase.  If there were some means of applying SWIG's dynamic code
+generation to produce the Python/CFFI API modes of accessing the GPGME
+libraries (or the source source code directly), but such a thing does
+not exist yet either and it currently appears that work is needed in
+at least one of CFFI's dependencies before any of this can be
+addressed.
+
+So if you're a massive fan of CFFI; that's great, but if you want this
+project to switch to CFFI then rather than just insisting that it
+should, I'd suggest you volunteer to bring CFFI up to the level this
+project needs.
+
+If you're actually seriously considering doing so, then I'd suggest
+taking the @samp{gpgme-tool.c} file in the GPGME @samp{src/} directory and
+getting that to work with any of the CFFI API methods (not the ABI
+methods, they'll work with pretty much anything).  When you start
+running into trouble with "ifdefs" then you'll know what sort of
+things are lacking.  That doesn't even take into account the amount of
+work saved via SWIG's code generation techniques either.
+
+@node Virtualised Environments
+@subsection Virtualised Environments
+
+It is fairly common practice amongst Python developers to, as much as
+possible, use packages like virtualenv to keep various things that are
+to be installed from interfering with each other.  Given how much of
+the GPGME bindings is often at odds with the usual pythonic way of
+doing things, it stands to reason that this would be called into
+question too.
+
+As it happens the answer as to whether or not the bindings can be used
+with virtualenv, the answer is both yes and no.
+
+In general we recommend installing to the relevant path and matching
+prefix of GPGME itself.  Which means that when GPGME, and ideally the
+rest of the GnuPG stack, is installed to a prefix like @samp{/usr/local} or
+@samp{/opt/local} then the bindings would need to be installed to the main
+Python installation and not a virtualised abstraction.  Attempts to
+separate the two in the past have been known to cause weird and
+intermittent errors ranging from minor annoyances to complete failures
+in the build process.
+
+As a consequence we only recommend building with and installing to the
+main Python installations within the same prefix as GPGME is installed
+to or which are found by GPGME's configuration stage immediately prior
+to running the make commands.  Which is exactly what the compiling and
+installing process of GPGME does by default.
+
+Once that is done, however, it appears that a copy the compiled module
+may be installed into a virtualenv of the same major and minor version
+matching the build.  Alternatively it is possible to utilise a
+@samp{sites.pth} file in the @samp{site-packages/} directory of a viertualenv
+installation, which links back to the system installations
+corresponding directory in order to import anything installed system
+wide.  This may or may not be appropriate on a case by case basis.
+
+Though extensive testing of either of these options is not yet
+complete, preliminary testing of them indicates that both are viable
+as long as the main installation is complete.  Which means that
+certain other options normally restricted to virtual environments are
+also available, including integration with pythonic test suites
+(e.g. @uref{https://docs.pytest.org/en/latest/index.html, pytest}) and other large projects.
+
+That said, it is worth reiterating the warning regarding non-standard
+installations.  If one were to attempt to install the bindings only to
+a virtual environment without somehow also including the full GnuPG
+stack (or enough of it as to include GPGME) then it is highly likely
+that errors would be encountered at some point and more than a little
+likely that the build process itself would break.
+
+If a degree of separation from the main operating system is still
+required in spite of these warnings, then consider other forms of
+virtualisation.  Either a virtual machine (e.g. @uref{https://www.virtualbox.org/, VirtualBox}), a
+hardware emulation layer (e.g. @uref{https://www.qemu.org/, QEMU}) or an application container
+(e.g. @uref{https://www.docker.com/why-docker, Docker}).
+
+Finally it should be noted that the limited tests conducted thus far
+have been using the @samp{virtualenv} command in a new directory to create
+the virtual python environment.  As opposed to the standard @samp{python3
+-m venv} and it is possible that this will make a difference depending
+on the system and version of Python in use.  Another option is to run
+the command @samp{python3 -m virtualenv /path/to/install/virtual/thingy}
+instead.
+
+@node Fundamentals
+@chapter Fundamentals
+
+Before we can get to the fun stuff, there are a few matters regarding
+GPGME's design which hold true whether you're dealing with the C code
+directly or these Python bindings.
+
+@menu
+* No REST::
+* Context::
+@end menu
+
+@node No REST
+@section No REST
+
+The first part of which is or will be fairly blatantly obvious upon
+viewing the first example, but it's worth reiterating anyway.  That
+being that this API is @emph{@strong{not}} a REST API.  Nor indeed could it ever
+be one.
+
+Most, if not all, Python programmers (and not just Python programmers)
+know how easy it is to work with a RESTful API.  In fact they've
+become so popular that many other APIs attempt to emulate REST-like
+behaviour as much as they are able.  Right down to the use of JSON
+formatted output to facilitate the use of their API without having to
+retrain developers.
+
+This API does not do that.  It would not be able to do that and also
+provide access to the entire C API on which it's built.  It does,
+however, provide a very pythonic interface on top of the direct
+bindings and it's this pythonic layer that this HOWTO deals with.
+
+@node Context
+@section Context
+
+One of the reasons which prevents this API from being RESTful is that
+most operations require more than one instruction to the API to
+perform the task.  Sure, there are certain functions which can be
+performed simultaneously, particularly if the result known or strongly
+anticipated (e.g. selecting and encrypting to a key known to be in the
+public keybox).
+
+There are many more, however, which cannot be manipulated so readily:
+they must be performed in a specific sequence and the result of one
+operation has a direct bearing on the outcome of subsequent
+operations.  Not merely by generating an error either.
+
+When dealing with this type of persistent state on the web, full of
+both the RESTful and REST-like, it's most commonly referred to as a
+session.  In GPGME, however, it is called a context and every
+operation type has one.
+
+@node Working with keys
+@chapter Working with keys
+
+@menu
+* Key selection::
+* Get key::
+* Importing keys::
+* Exporting keys::
+@end menu
+
+@node Key selection
+@section Key selection
+
+Selecting keys to encrypt to or to sign with will be a common
+occurrence when working with GPGMe and the means available for doing
+so are quite simple.
+
+They do depend on utilising a Context; however once the data is
+recorded in another variable, that Context does not need to be the
+same one which subsequent operations are performed.
+
+The easiest way to select a specific key is by searching for that
+key's key ID or fingerprint, preferably the full fingerprint without
+any spaces in it.  A long key ID will probably be okay, but is not
+advised and short key IDs are already a problem with some being
+generated to match specific patterns.  It does not matter whether the
+pattern is upper or lower case.
+
+So this is the best method:
+
+@example
+import gpg
+
+k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+keys = list(k)
+@end example
+
+This is passable and very likely to be common:
+
+@example
+import gpg
+
+k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+keys = list(k)
+@end example
+
+And this is a really bad idea:
+
+@example
+import gpg
+
+k = gpg.Context().keylist(pattern="0xDEADBEEF")
+keys = list(k)
+@end example
+
+Alternatively it may be that the intention is to create a list of keys
+which all match a particular search string.  For instance all the
+addresses at a particular domain, like this:
+
+@example
+import gpg
+
+ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+nsa = list(ncsc)
+@end example
+
+@menu
+* Counting keys::
+@end menu
+
+@node Counting keys
+@subsection Counting keys
+
+Counting the number of keys in your public keybox (@samp{pubring.kbx}), the
+format which has superseded the old keyring format (@samp{pubring.gpg} and
+@samp{secring.gpg}), or the number of secret keys is a very simple task.
+
+@example
+import gpg
+
+c = gpg.Context()
+seckeys = c.keylist(pattern=None, secret=True)
+pubkeys = c.keylist(pattern=None, secret=False)
+
+seclist = list(seckeys)
+secnum = len(seclist)
+
+publist = list(pubkeys)
+pubnum = len(publist)
+
+print("""
+  Number of secret keys:  @{0@}
+  Number of public keys:  @{1@}
+""".format(secnum, pubnum))
+@end example
+
+NOTE: The @ref{C plus Python plus SWIG plus Cython, , Cython} introduction in the @ref{Advanced or Experimental Use Cases, , Advanced and Experimental}
+section uses this same key counting code with Cython to demonstrate
+some areas where Cython can improve performance even with the
+bindings.  Users with large public keyrings or keyboxes, for instance,
+should consider these options if they are comfortable with using
+Cython.
+
+@node Get key
+@section Get key
+
+An alternative method of getting a single key via its fingerprint is
+available directly within a Context with @samp{Context().get_key}.  This is
+the preferred method of selecting a key in order to modify it, sign or
+certify it and for obtaining relevant data about a single key as a
+part of other functions; when verifying a signature made by that key,
+for instance.
+
+By default this method will select public keys, but it can select
+secret keys as well.
+
+This first example demonstrates selecting the current key of Werner
+Koch, which is due to expire at the end of 2018:
+
+@example
+import gpg
+
+fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+key = gpg.Context().get_key(fingerprint)
+@end example
+
+Whereas this example demonstrates selecting the author's current key
+with the @samp{secret} key word argument set to @samp{True}:
+
+@example
+import gpg
+
+fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+key = gpg.Context().get_key(fingerprint, secret=True)
+@end example
+
+It is, of course, quite possible to select expired, disabled and
+revoked keys with this function, but only to effectively display
+information about those keys.
+
+It is also possible to use both unicode or string literals and byte
+literals with the fingerprint when getting a key in this way.
+
+@node Importing keys
+@section Importing keys
+
+Importing keys is possible with the @samp{key_import()} method and takes
+one argument which is a bytes literal object containing either the
+binary or ASCII armoured key data for one or more keys.
+
+The following example retrieves one or more keys from the SKS
+keyservers via the web using the requests module.  Since requests
+returns the content as a bytes literal object, we can then use that
+directly to import the resulting data into our keybox.
+
+@example
+import gpg
+import os.path
+import requests
+
+c = gpg.Context()
+url = "https://sks-keyservers.net/pks/lookup"
+pattern = input("Enter the pattern to search for key or user IDs: ")
+payload = @{"op": "get", "search": pattern@}
+
+r = requests.get(url, verify=True, params=payload)
+result = c.key_import(r.content)
+
+if result is not None and hasattr(result, "considered") is False:
+    print(result)
+elif result is not None and hasattr(result, "considered") is True:
+    num_keys = len(result.imports)
+    new_revs = result.new_revocations
+    new_sigs = result.new_signatures
+    new_subs = result.new_sub_keys
+    new_uids = result.new_user_ids
+    new_scrt = result.secret_imported
+    nochange = result.unchanged
+    print("""
+  The total number of keys considered for import was:  @{0@}
+
+     Number of keys revoked:  @{1@}
+   Number of new signatures:  @{2@}
+      Number of new subkeys:  @{3@}
+     Number of new user IDs:  @{4@}
+  Number of new secret keys:  @{5@}
+   Number of unchanged keys:  @{6@}
+
+  The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+    for i in range(num_keys):
+        print("@{0@}\n".format(result.imports[i].fpr))
+else:
+    pass
+@end example
+
+NOTE: When searching for a key ID of any length or a fingerprint
+(without spaces), the SKS servers require the the leading @samp{0x}
+indicative of hexadecimal be included.  Also note that the old short
+key IDs (e.g. @samp{0xDEADBEEF}) should no longer be used due to the
+relative ease by which such key IDs can be reproduced, as demonstrated
+by the Evil32 Project in 2014 (which was subsequently exploited in
+2016).
+
+@menu
+* Working with ProtonMail::
+* Importing with HKP for Python::
+* Importing from ProtonMail with HKP for Python::
+@end menu
+
+@node Working with ProtonMail
+@subsection Working with ProtonMail
+
+Here is a variation on the example above which checks the constrained
+ProtonMail keyserver for ProtonMail public keys.
+
+@example
+import gpg
+import requests
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+""")
+
+c = gpg.Context(armor=True)
+url = "https://api.protonmail.ch/pks/lookup"
+ksearch = []
+
+if len(sys.argv) >= 2:
+    keyterm = sys.argv[1]
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+
+if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True:
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True:
+    ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:]))
+    ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:]))
+    ksearch.append("@{0@}@@pm.me".format(keyterm[1:]))
+elif keyterm.count("@@") == 0:
+    ksearch.append("@{0@}@@protonmail.com".format(keyterm))
+    ksearch.append("@{0@}@@protonmail.ch".format(keyterm))
+    ksearch.append("@{0@}@@pm.me".format(keyterm))
+elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False:
+    uidlist = keyterm.split("@@")
+    for uid in uidlist:
+        ksearch.append("@{0@}@@protonmail.com".format(uid))
+        ksearch.append("@{0@}@@protonmail.ch".format(uid))
+        ksearch.append("@{0@}@@pm.me".format(uid))
+elif keyterm.count("@@") > 2:
+    uidlist = keyterm.split("@@")
+    for uid in uidlist:
+        ksearch.append("@{0@}@@protonmail.com".format(uid))
+        ksearch.append("@{0@}@@protonmail.ch".format(uid))
+        ksearch.append("@{0@}@@pm.me".format(uid))
+else:
+    ksearch.append(keyterm)
+
+for k in ksearch:
+    payload = @{"op": "get", "search": k@}
+    try:
+        r = requests.get(url, verify=True, params=payload)
+        if r.ok is True:
+            result = c.key_import(r.content)
+        elif r.ok is False:
+            result = r.content
+    except Exception as e:
+        result = None
+
+    if result is not None and hasattr(result, "considered") is False:
+        print("@{0@} for @{1@}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  @{0@}
+
+With UIDs wholely or partially matching the following string:
+
+        @{1@}
+
+   Number of keys revoked:  @{2@}
+ Number of new signatures:  @{3@}
+    Number of new subkeys:  @{4@}
+   Number of new user IDs:  @{5@}
+Number of new secret keys:  @{6@}
+ Number of unchanged keys:  @{7@}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        print(e)
+@end example
+
+Both the above example, @uref{../examples/howto/pmkey-import.py, pmkey-import.py}, and a version which prompts
+for an alternative GnuPG home directory, @uref{../examples/howto/pmkey-import-alt.py, pmkey-import-alt.py}, are
+available with the other examples and are executable scripts.
+
+Note that while the ProtonMail servers are based on the SKS servers,
+their server is related more to their API and is not feature complete
+by comparison to the servers in the SKS pool.  One notable difference
+being that the ProtonMail server does not permit non ProtonMail users
+to update their own keys, which could be a vector for attacking
+ProtonMail users who may not receive a key's revocation if it had been
+compromised.
+
+@node Importing with HKP for Python
+@subsection Importing with HKP for Python
+
+Performing the same tasks with the @uref{https://github.com/Selfnet/hkp4py, hkp4py module} (available via PyPI)
+is not too much different, but does provide a number of options of
+benefit to end users.  Not least of which being the ability to perform
+some checks on a key before importing it or not.  For instance it may
+be the policy of a site or project to only import keys which have not
+been revoked.  The hkp4py module permits such checks prior to the
+importing of the keys found.
+
+@example
+import gpg
+import hkp4py
+import sys
+
+c = gpg.Context()
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+results = []
+
+if len(sys.argv) > 2:
+    pattern = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    pattern = sys.argv[1]
+else:
+    pattern = input("Enter the pattern to search for keys or user IDs: ")
+
+try:
+    keys = server.search(pattern)
+    print("Found @{0@} key(s).".format(len(keys)))
+except Exception as e:
+    keys = []
+    for logrus in pattern.split():
+        if logrus.startswith("0x") is True:
+            key = server.search(logrus)
+        else:
+            key = server.search("0x@{0@}".format(logrus))
+        keys.append(key[0])
+    print("Found @{0@} key(s).".format(len(keys)))
+
+for key in keys:
+    import_result = c.key_import(key.key_blob)
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print(result)
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  @{0@}
+
+   Number of keys revoked:  @{1@}
+ Number of new signatures:  @{2@}
+    Number of new subkeys:  @{3@}
+   Number of new user IDs:  @{4@}
+Number of new secret keys:  @{5@}
+ Number of unchanged keys:  @{6@}
+
+The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    else:
+        pass
+@end example
+
+Since the hkp4py module handles multiple keys just as effectively as
+one (@samp{keys} is a list of responses per matching key), the example
+above is able to do a little bit more with the returned data before
+anything is actually imported.
+
+@node Importing from ProtonMail with HKP for Python
+@subsection Importing from ProtonMail with HKP for Python
+
+Though this can provide certain benefits even when working with
+ProtonMail, the scope is somewhat constrained there due to the
+limitations of the ProtonMail keyserver.
+
+For instance, searching the SKS keyserver pool for the term "gnupg"
+produces hundreds of results from any time the word appears in any
+part of a user ID.  Performing the same search on the ProtonMail
+keyserver returns zero results, even though there are at least two
+test accounts which include it as part of the username.
+
+The cause of this discrepancy is the deliberate configuration of that
+server by ProtonMail to require an exact match of the full email
+address of the ProtonMail user whose key is being requested.
+Presumably this is intended to reduce breaches of privacy of their
+users as an email address must already be known before a key for that
+address can be obtained.
+
+@enumerate
+@item
+Import from ProtonMail via HKP for Python Example no. 1
+
+
+The following script is avalable with the rest of the examples under
+the somewhat less than original name, @samp{pmkey-import-hkp.py}.
+
+@example
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+
+Usage:  pmkey-import-hkp.py [search strings]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 2:
+    keyterms = sys.argv[1:]
+elif len(sys.argv) == 2:
+    keyterm = sys.argv[1]
+    keyterms.append(keyterm)
+else:
+    key_term = input("Enter the key ID, UID or search string: ")
+    keyterms = key_term.split()
+
+for keyterm in keyterms:
+    if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True:
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+    elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True:
+        ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:]))
+        ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:]))
+        ksearch.append("@{0@}@@pm.me".format(keyterm[1:]))
+    elif keyterm.count("@@") == 0:
+        ksearch.append("@{0@}@@protonmail.com".format(keyterm))
+        ksearch.append("@{0@}@@protonmail.ch".format(keyterm))
+        ksearch.append("@{0@}@@pm.me".format(keyterm))
+    elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False:
+        uidlist = keyterm.split("@@")
+        for uid in uidlist:
+            ksearch.append("@{0@}@@protonmail.com".format(uid))
+            ksearch.append("@{0@}@@protonmail.ch".format(uid))
+            ksearch.append("@{0@}@@pm.me".format(uid))
+    elif keyterm.count("@@") > 2:
+        uidlist = keyterm.split("@@")
+        for uid in uidlist:
+            ksearch.append("@{0@}@@protonmail.com".format(uid))
+            ksearch.append("@{0@}@@protonmail.ch".format(uid))
+            ksearch.append("@{0@}@@pm.me".format(uid))
+    else:
+        ksearch.append(keyterm)
+
+for k in ksearch:
+    print("Checking for key for: @{0@}".format(k))
+    try:
+        keys = server.search(k)
+        if isinstance(keys, list) is True:
+            for key in keys:
+                allkeys.append(key)
+                try:
+                    import_result = c.key_import(key.key_blob)
+                except Exception as e:
+                    import_result = c.key_import(key.key)
+        else:
+            paradox.append(keys)
+            import_result = None
+    except Exception as e:
+        import_result = None
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print("@{0@} for @{1@}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  @{0@}
+
+With UIDs wholely or partially matching the following string:
+
+        @{1@}
+
+   Number of keys revoked:  @{2@}
+ Number of new signatures:  @{3@}
+    Number of new subkeys:  @{4@}
+   Number of new user IDs:  @{5@}
+Number of new secret keys:  @{6@}
+ Number of unchanged keys:  @{7@}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        pass
+@end example
+
+@item
+Import from ProtonMail via HKP for Python Example no. 2
+
+
+Like its counterpart above, this script can also be found with the
+rest of the examples, by the name pmkey-import-hkp-alt.py.
+
+With this script a modicum of effort has been made to treat anything
+passed as a @samp{homedir} which either does not exist or which is not a
+directory, as also being a pssible user ID to check for.  It's not
+guaranteed to pick up on all such cases, but it should cover most of
+them.
+
+@example
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.  Optionally enables specifying a different GnuPG home directory.
+
+Usage:  pmkey-import-hkp.py [homedir] [search string]
+   or:  pmkey-import-hkp.py [search string]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 3:
+    homedir = sys.argv[1]
+    keyterms = sys.argv[2:]
+elif len(sys.argv) == 3:
+    homedir = sys.argv[1]
+    keyterm = sys.argv[2]
+    keyterms.append(keyterm)
+elif len(sys.argv) == 2:
+    homedir = ""
+    keyterm = sys.argv[1]
+    keyterms.append(keyterm)
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+    keyterms.append(keyterm)
+
+if len(homedir) == 0:
+    homedir = None
+    homeless = False
+
+if homedir is not None:
+    if homedir.startswith("~"):
+        if os.path.exists(os.path.expanduser(homedir)) is True:
+            if os.path.isdir(os.path.expanduser(homedir)) is True:
+                c.home_dir = os.path.realpath(os.path.expanduser(homedir))
+            else:
+                homeless = True
+        else:
+            homeless = True
+    elif os.path.exists(os.path.realpath(homedir)) is True:
+        if os.path.isdir(os.path.realpath(homedir)) is True:
+            c.home_dir = os.path.realpath(homedir)
+        else:
+            homeless = True
+    else:
+        homeless = True
+
+# First check to see if the homedir really is a homedir and if not, treat it as
+# a search string.
+if homeless is True:
+    keyterms.append(homedir)
+    c.home_dir = None
+else:
+    pass
+
+for keyterm in keyterms:
+    if keyterm.count("@@") == 2 and keyterm.startswith("@@") is True:
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+    elif keyterm.count("@@") == 1 and keyterm.startswith("@@") is True:
+        ksearch.append("@{0@}@@protonmail.com".format(keyterm[1:]))
+        ksearch.append("@{0@}@@protonmail.ch".format(keyterm[1:]))
+        ksearch.append("@{0@}@@pm.me".format(keyterm[1:]))
+    elif keyterm.count("@@") == 0:
+        ksearch.append("@{0@}@@protonmail.com".format(keyterm))
+        ksearch.append("@{0@}@@protonmail.ch".format(keyterm))
+        ksearch.append("@{0@}@@pm.me".format(keyterm))
+    elif keyterm.count("@@") == 2 and keyterm.startswith("@@") is False:
+        uidlist = keyterm.split("@@")
+        for uid in uidlist:
+            ksearch.append("@{0@}@@protonmail.com".format(uid))
+            ksearch.append("@{0@}@@protonmail.ch".format(uid))
+            ksearch.append("@{0@}@@pm.me".format(uid))
+    elif keyterm.count("@@") > 2:
+        uidlist = keyterm.split("@@")
+        for uid in uidlist:
+            ksearch.append("@{0@}@@protonmail.com".format(uid))
+            ksearch.append("@{0@}@@protonmail.ch".format(uid))
+            ksearch.append("@{0@}@@pm.me".format(uid))
+    else:
+        ksearch.append(keyterm)
+
+for k in ksearch:
+    print("Checking for key for: @{0@}".format(k))
+    try:
+        keys = server.search(k)
+        if isinstance(keys, list) is True:
+            for key in keys:
+                allkeys.append(key)
+                try:
+                    import_result = c.key_import(key.key_blob)
+                except Exception as e:
+                    import_result = c.key_import(key.key)
+        else:
+            paradox.append(keys)
+            import_result = None
+    except Exception as e:
+        import_result = None
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print("@{0@} for @{1@}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  @{0@}
+
+With UIDs wholely or partially matching the following string:
+
+        @{1@}
+
+   Number of keys revoked:  @{2@}
+ Number of new signatures:  @{3@}
+    Number of new subkeys:  @{4@}
+   Number of new user IDs:  @{5@}
+Number of new secret keys:  @{6@}
+ Number of unchanged keys:  @{7@}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        pass
+@end example
+@end enumerate
+
+@node Exporting keys
+@section Exporting keys
+
+Exporting keys remains a reasonably simple task, but has been
+separated into three different functions for the OpenPGP cryptographic
+engine.  Two of those functions are for exporting public keys and the
+third is for exporting secret keys.
+
+@menu
+* Exporting public keys::
+* Exporting secret keys::
+* Sending public keys to the SKS Keyservers::
+@end menu
+
+@node Exporting public keys
+@subsection Exporting public keys
+
+There are two methods of exporting public keys, both of which are very
+similar to the other.  The default method, @samp{key_export()}, will export
+a public key or keys matching a specified pattern as normal.  The
+alternative, the @samp{key_export_minimal()} method, will do the same thing
+except producing a minimised output with extra signatures and third
+party signatures or certifications removed.
+
+@example
+import gpg
+import os.path
+import sys
+
+print("""
+This script exports one or more public keys.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+    if os.path.exists(os.path.expanduser(homedir)) is True:
+        c.home_dir = os.path.expanduser(homedir)
+    else:
+        pass
+elif os.path.exists(homedir) is True:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export(pattern=logrus)
+except:
+    result = c.key_export(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+else:
+    pass
+@end example
+
+It should be noted that the result will only return @samp{None} when a
+search pattern has been entered, but has not matched any keys.  When
+the search pattern itself is set to @samp{None} this triggers the exporting
+of the entire public keybox.
+
+@example
+import gpg
+import os.path
+import sys
+
+print("""
+This script exports one or more public keys in minimised form.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+    if os.path.exists(os.path.expanduser(homedir)) is True:
+        c.home_dir = os.path.expanduser(homedir)
+    else:
+        pass
+elif os.path.exists(homedir) is True:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export_minimal(pattern=logrus)
+except:
+    result = c.key_export_minimal(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+else:
+    pass
+@end example
+
+@node Exporting secret keys
+@subsection Exporting secret keys
+
+Exporting secret keys is, functionally, very similar to exporting
+public keys; save for the invocation of @samp{pinentry} via @samp{gpg-agent} in
+order to securely enter the key's passphrase and authorise the export.
+
+The following example exports the secret key to a file which is then
+set with the same permissions as the output files created by the
+command line secret key export options.
+
+@example
+import gpg
+import os
+import os.path
+import sys
+
+print("""
+This script exports one or more secret keys.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export_secret(pattern=logrus)
+except:
+    result = c.key_export_secret(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+    os.chmod(keyfile, 0o600)
+else:
+    pass
+@end example
+
+Alternatively the approach of the following script can be used.  This
+longer example saves the exported secret key(s) in files in the GnuPG
+home directory, in addition to setting the file permissions as only
+readable and writable by the user.  It also exports the secret key(s)
+twice in order to output both GPG binary (@samp{.gpg}) and ASCII armoured
+(@samp{.asc}) files.
+
+@example
+import gpg
+import os
+import os.path
+import subprocess
+import sys
+
+print("""
+This script exports one or more secret keys as both ASCII armored and binary
+file formats, saved in files within the user's GPG home directory.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+else:
+    gpgconfcmd = "gpgconf --list-dirs homedir"
+
+a = gpg.Context(armor=True)
+b = gpg.Context()
+c = gpg.Context()
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if os.path.exists(homedir) is False:
+    homedir = None
+else:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+if c.home_dir is not None:
+    if c.home_dir.endswith("/"):
+        gpgfile = "@{0@}@{1@}.gpg".format(c.home_dir, keyfile)
+        ascfile = "@{0@}@{1@}.asc".format(c.home_dir, keyfile)
+    else:
+        gpgfile = "@{0@}/@{1@}.gpg".format(c.home_dir, keyfile)
+        ascfile = "@{0@}/@{1@}.asc".format(c.home_dir, keyfile)
+else:
+    if os.path.exists(os.environ["GNUPGHOME"]) is True:
+        hd = os.environ["GNUPGHOME"]
+    else:
+        try:
+            hd = subprocess.getoutput(gpgconfcmd)
+        except:
+            process = subprocess.Popen(gpgconfcmd.split(),
+                                       stdout=subprocess.PIPE)
+            procom = process.communicate()
+            if sys.version_info[0] == 2:
+                hd = procom[0].strip()
+            else:
+                hd = procom[0].decode().strip()
+    gpgfile = "@{0@}/@{1@}.gpg".format(hd, keyfile)
+    ascfile = "@{0@}/@{1@}.asc".format(hd, keyfile)
+
+try:
+    a_result = a.key_export_secret(pattern=logrus)
+    b_result = b.key_export_secret(pattern=logrus)
+except:
+    a_result = a.key_export_secret(pattern=None)
+    b_result = b.key_export_secret(pattern=None)
+
+if a_result is not None:
+    with open(ascfile, "wb") as f:
+        f.write(a_result)
+    os.chmod(ascfile, 0o600)
+else:
+    pass
+
+if b_result is not None:
+    with open(gpgfile, "wb") as f:
+        f.write(b_result)
+    os.chmod(gpgfile, 0o600)
+else:
+    pass
+@end example
+
+@node Sending public keys to the SKS Keyservers
+@subsection Sending public keys to the SKS Keyservers
+
+As with the previous section on importing keys, the @samp{hkp4py} module
+adds another option with exporting keys in order to send them to the
+public keyservers.
+
+The following example demonstrates how this may be done.
+
+@example
+import gpg
+import hkp4py
+import os.path
+import sys
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) > 2:
+    logrus = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    logrus = sys.argv[1]
+else:
+    logrus = input("Enter the UID matching the key(s) to send: ")
+
+if len(logrus) > 0:
+    try:
+        export_result = c.key_export(pattern=logrus)
+    except Exception as e:
+        print(e)
+        export_result = None
+else:
+    export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+    try:
+        try:
+            send_result = server.add(export_result)
+        except:
+            send_result = server.add(export_result.decode())
+        if send_result is not None:
+            print(send_result)
+        else:
+            pass
+    except Exception as e:
+        print(e)
+else:
+    pass
+@end example
+
+An expanded version of this script with additional functions for
+specifying an alternative homedir location is in the examples
+directory as @samp{send-key-to-keyserver.py}.
+
+The @samp{hkp4py} module appears to handle both string and byte literal text
+data equally well, but the GPGME bindings deal primarily with byte
+literal data only and so this script sends in that format first, then
+tries the string literal form.
+
+@node Basic Functions
+@chapter Basic Functions
+
+The most frequently called features of any cryptographic library will
+be the most fundamental tasks for encryption software.  In this
+section we will look at how to programmatically encrypt data, decrypt
+it, sign it and verify signatures.
+
+@menu
+* Encryption::
+* Decryption::
+* Signing text and files::
+* Signature verification::
+@end menu
+
+@node Encryption
+@section Encryption
+
+Encrypting is very straight forward.  In the first example below the
+message, @samp{text}, is encrypted to a single recipient's key.  In the
+second example the message will be encrypted to multiple recipients.
+
+@menu
+* Encrypting to one key::
+* Encrypting to multiple keys::
+@end menu
+
+@node Encrypting to one key
+@subsection Encrypting to one key
+
+Once the the Context is set the main issues with encrypting data is
+essentially reduced to key selection and the keyword arguments
+specified in the @samp{gpg.Context().encrypt()} method.
+
+Those keyword arguments are: @samp{recipients}, a list of keys encrypted to
+(covered in greater detail in the following section); @samp{sign}, whether
+or not to sign the plaintext data, see subsequent sections on signing
+and verifying signatures below (defaults to @samp{True}); @samp{sink}, to write
+results or partial results to a secure sink instead of returning it
+(defaults to @samp{None}); @samp{passphrase}, only used when utilising symmetric
+encryption (defaults to @samp{None}); @samp{always_trust}, used to override the
+trust model settings for recipient keys (defaults to @samp{False});
+@samp{add_encrypt_to}, utilises any preconfigured @samp{encrypt-to} or
+@samp{default-key} settings in the user's @samp{gpg.conf} file (defaults to
+@samp{False}); @samp{prepare}, prepare for encryption (defaults to @samp{False});
+@samp{expect_sign}, prepare for signing (defaults to @samp{False}); @samp{compress},
+compresses the plaintext prior to encryption (defaults to @samp{True}).
+
+@example
+import gpg
+
+a_key = "0x12345678DEADBEEF"
+text = b"""Some text to test with.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data format.
+"""
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+    afile.write(ciphertext)
+@end example
+
+Though this is even more likely to be used like this; with the
+plaintext input read from a file, the recipient keys used for
+encryption regardless of key trust status and the encrypted output
+also encrypted to any preconfigured keys set in the @samp{gpg.conf} file:
+
+@example
+import gpg
+
+a_key = "0x12345678DEADBEEF"
+
+with open("secret_plans.txt", "rb") as afile:
+    text = afile.read()
+
+c = gpg.Context(armor=True)
+rkey = list(c.keylist(pattern=a_key, secret=False))
+ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
+                                            always_trust=True,
+                                            add_encrypt_to=True)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+    afile.write(ciphertext)
+@end example
+
+If the @samp{recipients} paramater is empty then the plaintext is encrypted
+symmetrically.  If no @samp{passphrase} is supplied as a parameter or via a
+callback registered with the @samp{Context()} then an out-of-band prompt
+for the passphrase via pinentry will be invoked.
+
+@node Encrypting to multiple keys
+@subsection Encrypting to multiple keys
+
+Encrypting to multiple keys essentially just expands upon the key
+selection process and the recipients from the previous examples.
+
+The following example encrypts a message (@samp{text}) to everyone with an
+email address on the @samp{gnupg.org} domain,@footnote{You probably don't really want to do this.  Searching the
+keyservers for "gnupg.org" produces over 400 results, the majority of
+which aren't actually at the gnupg.org domain, but just included a
+comment regarding the project in their key somewhere.} but does @emph{not} encrypt
+to a default key or other key which is configured to normally encrypt
+to.
+
+@example
+import gpg
+
+text = b"""Oh look, another test message.
+
+The same rules apply as with the previous example and more likely
+than not, the message will actually be drawn from reading the
+contents of a file or, maybe, from entering data at an input()
+prompt.
+
+Since the text in this case must be bytes, it is most likely that
+the input form will be a separate file which is opened with "rb"
+as this is the simplest method of obtaining the correct data
+format.
+"""
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+    if rpattern[i].can_encrypt == 1:
+        logrus.append(rpattern[i])
+
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                            sign=False, always_trust=True)
+
+with open("secret_plans.txt.asc", "wb") as afile:
+    afile.write(ciphertext)
+@end example
+
+All it would take to change the above example to sign the message
+and also encrypt the message to any configured default keys would
+be to change the @samp{c.encrypt} line to this:
+
+@example
+ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                            always_trust=True,
+                                            add_encrypt_to=True)
+@end example
+
+The only keyword arguments requiring modification are those for which
+the default values are changing.  The default value of @samp{sign} is
+@samp{True}, the default of @samp{always_trust} is @samp{False}, the default of
+@samp{add_encrypt_to} is @samp{False}.
+
+If @samp{always_trust} is not set to @samp{True} and any of the recipient keys
+are not trusted (e.g. not signed or locally signed) then the
+encryption will raise an error.  It is possible to mitigate this
+somewhat with something more like this:
+
+@example
+import gpg
+
+with open("secret_plans.txt.asc", "rb") as afile:
+    text = afile.read()
+
+c = gpg.Context(armor=True)
+rpattern = list(c.keylist(pattern="@@gnupg.org", secret=False))
+logrus = []
+
+for i in range(len(rpattern)):
+    if rpattern[i].can_encrypt == 1:
+        logrus.append(rpattern[i])
+
+    try:
+        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                    add_encrypt_to=True)
+    except gpg.errors.InvalidRecipients as e:
+        for i in range(len(e.recipients)):
+            for n in range(len(logrus)):
+                if logrus[n].fpr == e.recipients[i].fpr:
+                    logrus.remove(logrus[n])
+                else:
+                    pass
+        try:
+            ciphertext, result, sign_result = c.encrypt(text,
+                                                        recipients=logrus,
+                                                        add_encrypt_to=True)
+            with open("secret_plans.txt.asc", "wb") as afile:
+                afile.write(ciphertext)
+        except:
+            pass
+@end example
+
+This will attempt to encrypt to all the keys searched for, then remove
+invalid recipients if it fails and try again.
+
+@node Decryption
+@section Decryption
+
+Decrypting something encrypted to a key in one's secret keyring is
+fairly straight forward.
+
+In this example code, however, preconfiguring either @samp{gpg.Context()}
+or @samp{gpg.core.Context()} as @samp{c} is unnecessary because there is no need
+to modify the Context prior to conducting the decryption and since the
+Context is only used once, setting it to @samp{c} simply adds lines for no
+gain.
+
+@example
+import gpg
+
+ciphertext = input("Enter path and filename of encrypted file: ")
+newfile = input("Enter path and filename of file to save decrypted data to: ")
+
+with open(ciphertext, "rb") as cfile:
+    try:
+        plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+    except gpg.errors.GPGMEError as e:
+        plaintext = None
+        print(e)
+
+if plaintext is not None:
+    with open(newfile, "wb") as nfile:
+            nfile.write(plaintext)
+    else:
+        pass
+@end example
+
+The data available in @samp{plaintext} in this example is the decrypted
+content as a byte object, the recipient key IDs and algorithms in
+@samp{result} and the results of verifying any signatures of the data in
+@samp{verify_result}.
+
+@node Signing text and files
+@section Signing text and files
+
+The following sections demonstrate how to specify keys to sign with.
+
+@menu
+* Signing key selection::
+* Normal or default signing messages or files::
+* Detached signing messages and files::
+* Clearsigning messages or text::
+@end menu
+
+@node Signing key selection
+@subsection Signing key selection
+
+By default GPGME and the Python bindings will use the default key
+configured for the user invoking the GPGME API.  If there is no
+default key specified and there is more than one secret key available
+it may be necessary to specify the key or keys with which to sign
+messages and files.
+
+@example
+import gpg
+
+logrus = input("Enter the email address or string to match signing keys to: ")
+hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+sig_src = list(hancock)
+@end example
+
+The signing examples in the following sections include the explicitly
+designated @samp{signers} parameter in two of the five examples; once where
+the resulting signature would be ASCII armoured and once where it
+would not be armoured.
+
+While it would be possible to enter a key ID or fingerprint here to
+match a specific key, it is not possible to enter two fingerprints and
+match two keys since the patten expects a string, bytes or None and
+not a list.  A string with two fingerprints won't match any single
+key.
+
+@node Normal or default signing messages or files
+@subsection Normal or default signing messages or files
+
+The normal or default signing process is essentially the same as is
+most often invoked when also encrypting a message or file.  So when
+the encryption component is not utilised, the result is to produce an
+encoded and signed output which may or may not be ASCII armoured and
+which may or may not also be compressed.
+
+By default compression will be used unless GnuPG detects that the
+plaintext is already compressed.  ASCII armouring will be determined
+according to the value of @samp{gpg.Context().armor}.
+
+The compression algorithm is selected in much the same way as the
+symmetric encryption algorithm or the hash digest algorithm is when
+multiple keys are involved; from the preferences saved into the key
+itself or by comparison with the preferences with all other keys
+involved.
+
+@example
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True, signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+    afile.write(signed_data.decode())
+@end example
+
+Though everything in this example is accurate, it is more likely that
+reading the input data from another file and writing the result to a
+new file will be performed more like the way it is done in the next
+example.  Even if the output format is ASCII armoured.
+
+@example
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+    text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+    afile.write(signed_data)
+@end example
+
+@node Detached signing messages and files
+@subsection Detached signing messages and files
+
+Detached signatures will often be needed in programmatic uses of
+GPGME, either for signing files (e.g. tarballs of code releases) or as
+a component of message signing (e.g. PGP/MIME encoded email).
+
+@example
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context(armor=True)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+    afile.write(signed_data.decode())
+@end example
+
+As with normal signatures, detached signatures are best handled as
+byte literals, even when the output is ASCII armoured.
+
+@example
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+    text = tfile.read()
+
+c = gpg.Context(signers=sig_src)
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+
+with open("/path/to/statement.txt.sig", "wb") as afile:
+    afile.write(signed_data)
+@end example
+
+@node Clearsigning messages or text
+@subsection Clearsigning messages or text
+
+Though PGP/in-line messages are no longer encouraged in favour of
+PGP/MIME, there is still sometimes value in utilising in-line
+signatures.  This is where clear-signed messages or text is of value.
+
+@example
+import gpg
+
+text0 = """Declaration of ... something.
+
+"""
+text = text0.encode()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "w") as afile:
+    afile.write(signed_data.decode())
+@end example
+
+In spite of the appearance of a clear-signed message, the data handled
+by GPGME in signing it must still be byte literals.
+
+@example
+import gpg
+
+with open("/path/to/statement.txt", "rb") as tfile:
+    text = tfile.read()
+
+c = gpg.Context()
+signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+
+with open("/path/to/statement.txt.asc", "wb") as afile:
+    afile.write(signed_data)
+@end example
+
+@node Signature verification
+@section Signature verification
+
+Essentially there are two principal methods of verification of a
+signature.  The first of these is for use with the normal or default
+signing method and for clear-signed messages.  The second is for use
+with files and data with detached signatures.
+
+The following example is intended for use with the default signing
+method where the file was not ASCII armoured:
+
+@example
+import gpg
+import time
+
+filename = "statement.txt"
+gpg_file = "statement.txt.gpg"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(gpg_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+@{0@}
+with key @{1@}
+made at @{2@}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+@end example
+
+Whereas this next example, which is almost identical would work with
+normal ASCII armoured files and with clear-signed files:
+
+@example
+import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(asc_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+@{0@}
+with key @{1@}
+made at @{2@}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+@end example
+
+In both of the previous examples it is also possible to compare the
+original data that was signed against the signed data in @samp{data} to see
+if it matches with something like this:
+
+@example
+with open(filename, "rb") as afile:
+    text = afile.read()
+
+if text == data:
+    print("Good signature.")
+else:
+    pass
+@end example
+
+The following two examples, however, deal with detached signatures.
+With his method of verification the data that was signed does not get
+returned since it is already being explicitly referenced in the first
+argument of @samp{c.verify}.  So @samp{data} is @samp{None} and only the information
+in @samp{result} is available.
+
+@example
+import gpg
+import time
+
+filename = "statement.txt"
+sig_file = "statement.txt.sig"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(filename), open(sig_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+@{0@}
+with key @{1@}
+made at @{2@}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+@end example
+
+@example
+import gpg
+import time
+
+filename = "statement.txt"
+asc_file = "statement.txt.asc"
+
+c = gpg.Context()
+
+try:
+    data, result = c.verify(open(filename), open(asc_file))
+    verified = True
+except gpg.errors.BadSignatures as e:
+    verified = False
+    print(e)
+
+if verified is True:
+    for i in range(len(result.signatures)):
+        sign = result.signatures[i]
+        print("""Good signature from:
+@{0@}
+with key @{1@}
+made at @{2@}
+""".format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+           time.ctime(sign.timestamp)))
+else:
+    pass
+@end example
+
+@node Creating keys and subkeys
+@chapter Creating keys and subkeys
+
+The one thing, aside from GnuPG itself, that GPGME depends on, of
+course, is the keys themselves.  So it is necessary to be able to
+generate them and modify them by adding subkeys, revoking or disabling
+them, sometimes deleting them and doing the same for user IDs.
+
+In the following examples a key will be created for the world's
+greatest secret agent, Danger Mouse.  Since Danger Mouse is a secret
+agent he needs to be able to protect information to @samp{SECRET} level
+clearance, so his keys will be 3072-bit keys.
+
+The pre-configured @samp{gpg.conf} file which sets cipher, digest and other
+preferences contains the following configuration parameters:
+
+@example
+expert
+allow-freeform-uid
+allow-secret-key-import
+trust-model tofu+pgp
+tofu-default-policy unknown
+enable-large-rsa
+enable-dsa2
+cert-digest-algo SHA512
+default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+@end example
+
+@menu
+* Primary key::
+* Subkeys::
+* User IDs::
+* Key certification::
+@end menu
+
+@node Primary key
+@section Primary key
+
+Generating a primary key uses the @samp{create_key} method in a Context.
+It contains multiple arguments and keyword arguments, including:
+@samp{userid}, @samp{algorithm}, @samp{expires_in}, @samp{expires}, @samp{sign}, @samp{encrypt},
+@samp{certify}, @samp{authenticate}, @samp{passphrase} and @samp{force}.  The defaults for
+all of those except @samp{userid}, @samp{algorithm}, @samp{expires_in}, @samp{expires} and
+@samp{passphrase} is @samp{False}.  The defaults for @samp{algorithm} and
+@samp{passphrase} is @samp{None}.  The default for @samp{expires_in} is @samp{0}.  The
+default for @samp{expires} is @samp{True}.  There is no default for @samp{userid}.
+
+If @samp{passphrase} is left as @samp{None} then the key will not be generated
+with a passphrase, if @samp{passphrase} is set to a string then that will
+be the passphrase and if @samp{passphrase} is set to @samp{True} then gpg-agent
+will launch pinentry to prompt for a passphrase.  For the sake of
+convenience, these examples will keep @samp{passphrase} set to @samp{None}.
+
+@example
+import gpg
+
+c = gpg.Context()
+
+c.home_dir = "~/.gnupg-dm"
+userid = "Danger Mouse <dm@@secret.example.net>"
+
+dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
+                     sign=True, certify=True)
+@end example
+
+One thing to note here is the use of setting the @samp{c.home_dir}
+parameter.  This enables generating the key or keys in a different
+location.  In this case to keep the new key data created for this
+example in a separate location rather than adding it to existing and
+active key store data.  As with the default directory, @samp{~/.gnupg}, any
+temporary or separate directory needs the permissions set to only
+permit access by the directory owner.  On posix systems this means
+setting the directory permissions to 700.
+
+The @samp{temp-homedir-config.py} script in the HOWTO examples directory
+will create an alternative homedir with these configuration options
+already set and the correct directory and file permissions.
+
+The successful generation of the key can be confirmed via the returned
+@samp{GenkeyResult} object, which includes the following data:
+
+@example
+print("""
+ Fingerprint:  @{0@}
+ Primary Key:  @{1@}
+  Public Key:  @{2@}
+  Secret Key:  @{3@}
+ Sub Key:  @{4@}
+User IDs:  @{5@}
+""".format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
+           dmkey.uid))
+@end example
+
+Alternatively the information can be confirmed using the command line
+program:
+
+@example
+bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+~/.gnupg-dm/pubring.kbx
+----------------------
+sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+      177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+uid           [ultimate] Danger Mouse <dm@@secret.example.net>
+
+bash-4.4$
+@end example
+
+As with generating keys manually, to preconfigure expanded preferences
+for the cipher, digest and compression algorithms, the @samp{gpg.conf} file
+must contain those details in the home directory in which the new key
+is being generated.  I used a cut down version of my own @samp{gpg.conf}
+file in order to be able to generate this:
+
+@example
+bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
+Secret key is available.
+
+sec  rsa3072/026D2F19E99E63AA
+     created: 2018-03-15  expires: 2019-03-15  usage: SC
+     trust: ultimate      validity: ultimate
+[ultimate] (1). Danger Mouse <dm@@secret.example.net>
+
+[ultimate] (1). Danger Mouse <dm@@secret.example.net>
+     Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
+     Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
+     Compression: ZLIB, BZIP2, ZIP, Uncompressed
+     Features: MDC, Keyserver no-modify
+
+bash-4.4$
+@end example
+
+@node Subkeys
+@section Subkeys
+
+Adding subkeys to a primary key is fairly similar to creating the
+primary key with the @samp{create_subkey} method.  Most of the arguments
+are the same, but not quite all.  Instead of the @samp{userid} argument
+there is now a @samp{key} argument for selecting which primary key to add
+the subkey to.
+
+In the following example an encryption subkey will be added to the
+primary key.  Since Danger Mouse is a security conscious secret agent,
+this subkey will only be valid for about six months, half the length
+of the primary key.
+
+@example
+import gpg
+
+c = gpg.Context()
+c.home_dir = "~/.gnupg-dm"
+
+key = c.get_key(dmkey.fpr, secret=True)
+dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
+                        encrypt=True)
+@end example
+
+As with the primary key, the results here can be checked with:
+
+@example
+print("""
+ Fingerprint:  @{0@}
+ Primary Key:  @{1@}
+  Public Key:  @{2@}
+  Secret Key:  @{3@}
+ Sub Key:  @{4@}
+User IDs:  @{5@}
+""".format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
+           dmsub.uid))
+@end example
+
+As well as on the command line with:
+
+@example
+bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+~/.gnupg-dm/pubring.kbx
+----------------------
+sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+      177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+uid           [ultimate] Danger Mouse <dm@@secret.example.net>
+ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+bash-4.4$
+@end example
+
+@node User IDs
+@section User IDs
+
+@menu
+* Adding User IDs::
+* Revokinging User IDs::
+@end menu
+
+@node Adding User IDs
+@subsection Adding User IDs
+
+By comparison to creating primary keys and subkeys, adding a new user
+ID to an existing key is much simpler.  The method used to do this is
+@samp{key_add_uid} and the only arguments it takes are for the @samp{key} and
+the new @samp{uid}.
+
+@example
+import gpg
+
+c = gpg.Context()
+c.home_dir = "~/.gnupg-dm"
+
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+key = c.get_key(dmfpr, secret=True)
+uid = "Danger Mouse <danger.mouse@@secret.example.net>"
+
+c.key_add_uid(key, uid)
+@end example
+
+Unsurprisingly the result of this is:
+
+@example
+bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+~/.gnupg-dm/pubring.kbx
+----------------------
+sec   rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+      177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+uid           [ultimate] Danger Mouse <danger.mouse@@secret.example.net>
+uid           [ultimate] Danger Mouse <dm@@secret.example.net>
+ssb   rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+bash-4.4$
+@end example
+
+@node Revokinging User IDs
+@subsection Revokinging User IDs
+
+Revoking a user ID is a fairly similar process, except that it uses
+the @samp{key_revoke_uid} method.
+
+@example
+import gpg
+
+c = gpg.Context()
+c.home_dir = "~/.gnupg-dm"
+
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+key = c.get_key(dmfpr, secret=True)
+uid = "Danger Mouse <danger.mouse@@secret.example.net>"
+
+c.key_revoke_uid(key, uid)
+@end example
+
+@node Key certification
+@section Key certification
+
+Since key certification is more frequently referred to as key signing,
+the method used to perform this function is @samp{key_sign}.
+
+The @samp{key_sign} method takes four arguments: @samp{key}, @samp{uids},
+@samp{expires_in} and @samp{local}.  The default value of @samp{uids} is @samp{None} and
+which results in all user IDs being selected.  The default value of
+both @samp{expires_in} and @samp{local} is @samp{False}; which results in the
+signature never expiring and being able to be exported.
+
+The @samp{key} is the key being signed rather than the key doing the
+signing.  To change the key doing the signing refer to the signing key
+selection above for signing messages and files.
+
+If the @samp{uids} value is not @samp{None} then it must either be a string to
+match a single user ID or a list of strings to match multiple user
+IDs.  In this case the matching of those strings must be precise and
+it is case sensitive.
+
+To sign Danger Mouse's key for just the initial user ID with a
+signature which will last a little over a month, do this:
+
+@example
+import gpg
+
+c = gpg.Context()
+uid = "Danger Mouse <dm@@secret.example.net>"
+
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+key = c.get_key(dmfpr, secret=True)
+c.key_sign(key, uids=uid, expires_in=2764800)
+@end example
+
+@node Advanced or Experimental Use Cases
+@chapter Advanced or Experimental Use Cases
+
+@menu
+* C plus Python plus SWIG plus Cython::
+@end menu
+
+@node C plus Python plus SWIG plus Cython
+@section C plus Python plus SWIG plus Cython
+
+In spite of the apparent incongruence of using Python bindings to a C
+interface only to generate more C from the Python; it is in fact quite
+possible to use the GPGME bindings with @uref{http://docs.cython.org/en/latest/index.html, Cython}.  Though in many cases
+the benefits may not be obvious since the most computationally
+intensive work never leaves the level of the C code with which GPGME
+itself is interacting with.
+
+Nevertheless, there are some situations where the benefits are
+demonstrable.  One of the better and easier examples being the one of
+the early examples in this HOWTO, the @ref{Counting keys, , key counting} code.  Running that
+example as an executable Python script, @samp{keycount.py} (available in
+the @samp{examples/howto/} directory), will take a noticable amount of time
+to run on most systems where the public keybox or keyring contains a
+few thousand public keys.
+
+Earlier in the evening, prior to starting this section, I ran that
+script on my laptop; as I tend to do periodically and timed it using
+@samp{time} utility, with the following results:
+
+@example
+bash-4.4$ time keycount.py
+
+Number of secret keys:  23
+Number of public keys:  12112
+
+
+real        11m52.945s
+user        0m0.913s
+sys        0m0.752s
+
+bash-4.4$
+@end example
+
+Sometime after that I imported another key and followed it with a
+little test of Cython.  This test was kept fairly basic, essentially
+lifting the material from the @uref{http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html, Cython Basic Tutorial} to demonstrate
+compiling Python code to C.  The first step was to take the example
+key counting code quoted previously, essentially from the importing of
+the @samp{gpg} module to the end of the script:
+
+@example
+import gpg
+
+c = gpg.Context()
+seckeys = c.keylist(pattern=None, secret=True)
+pubkeys = c.keylist(pattern=None, secret=False)
+
+seclist = list(seckeys)
+secnum = len(seclist)
+
+publist = list(pubkeys)
+pubnum = len(publist)
+
+print("""
+    Number of secret keys:  @{0@}
+    Number of public keys:  @{1@}
+
+""".format(secnum, pubnum))
+@end example
+
+Save that into a file called @samp{keycount.pyx} and then create a
+@samp{setup.py} file which contains this:
+
+@example
+from distutils.core import setup
+from Cython.Build import cythonize
+
+setup(
+    ext_modules = cythonize("keycount.pyx")
+)
+@end example
+
+Compile it:
+
+@example
+bash-4.4$ python setup.py build_ext --inplace
+bash-4.4$
+@end example
+
+Then run it in a similar manner to @samp{keycount.py}:
+
+@example
+bash-4.4$ time python3.7 -c "import keycount"
+
+Number of secret keys:  23
+Number of public keys:  12113
+
+
+real        6m47.905s
+user        0m0.785s
+sys        0m0.331s
+
+bash-4.4$
+@end example
+
+Cython turned @samp{keycount.pyx} into an 81KB @samp{keycount.o} file in the
+@samp{build/} directory, a 24KB @samp{keycount.cpython-37m-darwin.so} file to be
+imported into Python 3.7 and a 113KB @samp{keycount.c} generated C source
+code file of nearly three thousand lines.  Quite a bit bigger than the
+314 bytes of the @samp{keycount.pyx} file or the full 1,452 bytes of the
+full executable @samp{keycount.py} example script.
+
+On the other hand it ran in nearly half the time; taking 6 minutes and
+47.905 seconds to run.  As opposed to the 11 minutes and 52.945 seconds
+which the CPython script alone took.
+
+The @samp{keycount.pyx} and @samp{setup.py} files used to generate this example
+have been added to the @samp{examples/howto/advanced/cython/} directory
+The example versions include some additional options to annotate the
+existing code and to detect Cython's use.  The latter comes from the
+@uref{http://docs.cython.org/en/latest/src/tutorial/pure.html#magic-attributes-within-the-pxd, Magic Attributes} section of the Cython documentation.
+
+@node Miscellaneous extras and work-arounds
+@chapter Miscellaneous extras and work-arounds
+
+Most of the things in the following sections are here simply because
+there was no better place to put them, even though some are only
+peripherally related to the GPGME Python bindings.  Some are also
+workarounds for functions not integrated with GPGME as yet.  This is
+especially true of the first of these, dealing with @ref{Group lines, , group lines}.
+
+@menu
+* Group lines::
+* Keyserver access for Python::
+@end menu
+
+@node Group lines
+@section Group lines
+
+There is not yet an easy way to access groups configured in the
+gpg.conf file from within GPGME.  As a consequence these central
+groupings of keys cannot be shared amongst multiple programs, such as
+MUAs readily.
+
+The following code, however, provides a work-around for obtaining this
+information in Python.
+
+@example
+import subprocess
+import sys
+
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-options gpg"
+else:
+    gpgconfcmd = "gpgconf --list-options gpg"
+
+try:
+    lines = subprocess.getoutput(gpgconfcmd).splitlines()
+except:
+    process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+    procom = process.communicate()
+    if sys.version_info[0] == 2:
+        lines = procom[0].splitlines()
+    else:
+        lines = procom[0].decode().splitlines()
+
+for i in range(len(lines)):
+    if lines[i].startswith("group") is True:
+        line = lines[i]
+    else:
+        pass
+
+groups = line.split(":")[-1].replace('"', '').split(',')
+
+group_lines = []
+group_lists = []
+
+for i in range(len(groups)):
+    group_lines.append(groups[i].split("="))
+    group_lists.append(groups[i].split("="))
+
+for i in range(len(group_lists)):
+    group_lists[i][1] = group_lists[i][1].split()
+@end example
+
+The result of that code is that @samp{group_lines} is a list of lists where
+@samp{group_lines[i][0]} is the name of the group and @samp{group_lines[i][1]}
+is the key IDs of the group as a string.
+
+The @samp{group_lists} result is very similar in that it is a list of
+lists.  The first part, @samp{group_lists[i][0]} matches
+@samp{group_lines[i][0]} as the name of the group, but @samp{group_lists[i][1]}
+is the key IDs of the group as a string.
+
+A demonstration of using the @samp{groups.py} module is also available in
+the form of the executable @samp{mutt-groups.py} script.  This second
+script reads all the group entries in a user's @samp{gpg.conf} file and
+converts them into crypt-hooks suitable for use with the Mutt and
+Neomutt mail clients.
+
+@node Keyserver access for Python
+@section Keyserver access for Python
+
+The @uref{https://github.com/Selfnet/hkp4py, hkp4py} module by Marcel Fest was originally a port of the old
+@uref{https://github.com/dgladkov/python-hkp, python-hkp} module from Python 2 to Python 3 and updated to use the
+@uref{http://docs.python-requests.org/en/latest/index.html, requests} module instead.  It has since been modified to provide
+support for Python 2.7 as well and is available via PyPI.
+
+Since it rewrites the @samp{hkp} protocol prefix as @samp{http} and @samp{hkps} as
+@samp{https}, the module is able to be used even with servers which do not
+support the full scope of keyserver functions.@footnote{Such as with ProtonMail servers.  This also means that
+restricted servers which only advertise either HTTP or HTTPS end
+points and not HKP or HKPS end points must still be identified as as
+HKP or HKPS within the Python Code.  The @samp{hkp4py} module will rewrite
+these appropriately when the connection is made to the server.}  It also works quite
+readily when incorporated into a @ref{C plus Python plus SWIG plus Cython, , Cython} generated and compiled version
+of any code.
+
+@menu
+* Key import format::
+@end menu
+
+@node Key import format
+@subsection Key import format
+
+The hkp4py module returns key data via requests as string literals
+(@samp{r.text}) instead of byte literals (@samp{r.content}).  This means that
+the retrurned key data must be encoded to UTF-8 when importing that
+key material using a @samp{gpg.Context().key_import()} method.
+
+For this reason an alternative method has been added to the @samp{search}
+function of @samp{hkp4py.KeyServer()} which returns the key in the correct
+format as expected by @samp{key_import}.  When importing using this module,
+it is now possible to import with this:
+
+@example
+for key in keys:
+    if key.revoked is False:
+        gpg.Context().key_import(key.key_blob)
+    else:
+        pass
+@end example
+
+Without that recent addition it would have been necessary to encode
+the contents of each @samp{hkp4py.KeyServer().search()[i].key} in
+@samp{hkp4py.KeyServer().search()} before trying to import it.
+
+An example of this is included in the @ref{Importing keys, , Importing Keys} section of this
+HOWTO and the corresponding executable version of that example is
+available in the @samp{lang/python/examples/howto} directory as normal; the
+executable version is the @samp{import-keys-hkp.py} file.
+
+@node Copyright and Licensing
+@chapter Copyright and Licensing
+
+@menu
+* Copyright::
+* Draft Editions of this HOWTO::
+* License GPL compatible::
+@end menu
+
+@node Copyright
+@section Copyright
+
+Copyright © The GnuPG Project, 2018.
+
+Copyright (C) The GnuPG Project, 2018.
+
+@node Draft Editions of this HOWTO
+@section Draft Editions of this HOWTO
+
+Draft editions of this HOWTO may be periodically available directly
+from the author at any of the following URLs:
+
+@itemize
+@item
+@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.html, GPGME Python Bindings HOWTO draft (XHTML AWS S3 SSL)}
+@item
+@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.html, GPGME Python Bindings HOWTO draft (XHTML AWS S3 no SSL)}
+@item
+@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.texi, GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 SSL)}
+@item
+@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.texi, GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no SSL)}
+@item
+@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.info, GPGME Python Bindings HOWTO draft (Info file AWS S3 SSL)}
+@item
+@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.info, GPGME Python Bindings HOWTO draft (Info file AWS S3 no SSL)}
+@item
+@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.rst, GPGME Python Bindings HOWTO draft (reST file AWS S3 SSL)}
+@item
+@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.rst, GPGME Python Bindings HOWTO draft (reST file AWS S3 no SSL)}
+@item
+@uref{https://files.au.adversary.org/crypto/gpgme-python-howto.xml, GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 SSL)}
+@item
+@uref{http://files.au.adversary.org/crypto/gpgme-python-howto.xml, GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no SSL)}
+@end itemize
+
+All of these draft versions except for one have been generated from
+this document via Emacs @uref{https://orgmode.org/, Org mode} and @uref{https://www.gnu.org/software/texinfo/, GNU Texinfo}.  Though it is likely
+that the specific @uref{https://files.au.adversary.org/crypto/gpgme-python-howto.org, file} @uref{http://files.au.adversary.org/crypto/gpgme-python-howto.org, version} used will be on the same server with
+the generated output formats.
+
+The one exception is the reStructuredText version, which was converted
+using the latest version of Pandoc from the Org mode source file using
+the following command:
+
+@example
+pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org
+@end example
+
+In addition to these there is a significantly less frequently updated
+version as a HTML @uref{https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html, WebHelp site} (AWS S3 SSL); generated from DITA XML
+source files, which can be found in @uref{https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/, an alternative branch} of the GPGME
+git repository.
+
+These draft editions are not official documents and the version of
+documentation in the master branch or which ships with released
+versions is the only official documentation.  Nevertheless, these
+draft editions may occasionally be of use by providing more accessible
+web versions which are updated between releases.  They are provided on
+the understanding that they may contain errors or may contain content
+subject to change prior to an official release.
+
+@node License GPL compatible
+@section License GPL compatible
+
+This file is free software; as a special exception the author gives
+unlimited permission to copy and/or distribute it, with or without
+modifications, as long as this notice is preserved.
+
+This file is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.
+
+@bye
\ No newline at end of file
diff --git a/lang/python/doc/texinfo/index.texi b/lang/python/doc/texinfo/index.texi
new file mode 100644 (file)
index 0000000..4f80423
--- /dev/null
@@ -0,0 +1,52 @@
+\input texinfo    @c -*- texinfo -*-
+@c %**start of header
+@setfilename index.info
+@settitle GNU Privacy Guard (GnuPG) Made Easy Python Bindings
+@documentencoding UTF-8
+@documentlanguage en
+@c %**end of header
+
+@finalout
+@titlepage
+@title GNU Privacy Guard (GnuPG) Made Easy Python Bindings
+@author Ben McGinnes
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top
+@top GNU Privacy Guard (GnuPG) Made Easy Python Bindings
+@end ifnottex
+
+@menu
+* GPGME Python Bindings::
+
+@detailmenu
+--- The Detailed Node Listing ---
+
+GPGME Python Bindings
+
+* Contents::
+
+@end detailmenu
+@end menu
+
+@node GPGME Python Bindings
+@chapter GPGME Python Bindings
+
+@menu
+* Contents::
+@end menu
+
+@node Contents
+@section Contents
+
+@itemize
+@item
+@uref{short-history.org, A short history of the project}
+@item
+@uref{gpgme-python-howto.org, GPGME Python Bindings HOWTO}
+@end itemize
+
+@bye
\ No newline at end of file
diff --git a/lang/python/doc/texinfo/short-history.texi b/lang/python/doc/texinfo/short-history.texi
new file mode 100644 (file)
index 0000000..a982f02
--- /dev/null
@@ -0,0 +1,209 @@
+\input texinfo    @c -*- texinfo -*-
+@c %**start of header
+@setfilename short-history.info
+@settitle A Short History of the GPGME bindings for Python
+@documentencoding UTF-8
+@documentlanguage en
+@c %**end of header
+
+@finalout
+@titlepage
+@title A Short History of the GPGME bindings for Python
+@author Ben McGinnes
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top
+@top A Short History of the GPGME bindings for Python
+@end ifnottex
+
+@menu
+* Overview::
+* Relics of the past::
+
+@detailmenu
+--- The Detailed Node Listing ---
+
+Overview
+
+* In the beginning::
+* Keeping the flame alive::
+* Passing the torch::
+* Coming full circle::
+
+Relics of the past
+
+* The Annoyances of Git::
+* The Perils of PyPI::
+
+The Perils of PyPI
+
+* GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library::
+* PyME 0·9·0 - Python support for GPGME GnuPG cryptography library::
+
+@end detailmenu
+@end menu
+
+@node Overview
+@chapter Overview
+
+The GPGME Python bindings passed through many hands and numerous
+phases before, after a fifteen year journey, coming full circle to
+return to the source.  This is a short explanation of that journey.
+
+@menu
+* In the beginning::
+* Keeping the flame alive::
+* Passing the torch::
+* Coming full circle::
+@end menu
+
+@node In the beginning
+@section In the beginning
+
+In 2002 John Goerzen released PyME; Python bindings for the GPGME
+module which utilised the current release of Python of the time and
+SWIG.@footnote{In all likelihood thos would have been Python 2.2 or possibly
+Python 2.3.}  Shortly after creating it and ensuring it worked he stopped
+supporting it, though he left his work available on his Gopher
+site.
+
+@node Keeping the flame alive
+@section Keeping the flame alive
+
+A couple of years later the project was picked up by Igor Belyi and
+actively developed and maintained by him from 2004 to 2008.  Igor's
+whereabouts at the time of this document's creation are unknown,
+but the current authors do hope he is well.  We're assuming (or
+hoping) that life did what life does and made continuing untenable.
+
+@node Passing the torch
+@section Passing the torch
+
+In 2014 Martin Albrecht wanted to patch a bug in the PyME code and
+discovered the absence of Igor.  Following a discussion on the PyME
+mailing list he became the new maintainer for PyME, releasing
+version 0.9.0 in May of that year.  He remains the maintainer of
+the original PyME release in Python 2.6 and 2.7 (available via
+PyPI).
+
+@node Coming full circle
+@section Coming full circle
+
+In 2015 Ben McGinnes approached Martin about a Python 3 version,
+while investigating how complex a task this would be the task ended
+up being completed.  A subsequent discussion with Werner Koch led
+to the decision to fold the Python 3 port back into the original
+GPGME release in the languages subdirectory for non-C bindings
+under the module name of @samp{pyme3}.
+
+In 2016 this PyME module was integrated back into the GPGME project
+by Justus Winter.  During the course of this work Justus adjusted
+the port to restore limited support for Python 2, but not as many
+minor point releases as the original PyME package supports.  During
+the course of this integration the package was renamed to more
+accurately reflect its status as a component of GPGME.  The @samp{pyme3}
+module was renamed to @samp{gpg} and adopted by the upstream GnuPG team.
+
+In 2017 Justus departed G10code and the GnuPG team.  Following this
+Ben returned to maintain of gpgme Python bindings and continue
+building them from that point.
+
+@node Relics of the past
+@chapter Relics of the past
+
+There are a few things, in addition to code specific factors, such as
+SWIG itself, which are worth noting here.
+
+@menu
+* The Annoyances of Git::
+* The Perils of PyPI::
+@end menu
+
+@node The Annoyances of Git
+@section The Annoyances of Git
+
+As anyone who has ever worked with git knows, submodules are
+horrible way to deal with pretty much anything.  In the interests
+of avoiding migraines, that was skipped with addition of the PyME
+code to GPGME.
+
+Instead the files were added to a subdirectory of the @samp{lang/}
+directory, along with a copy of the entire git log up to that point
+as a separate file within the @samp{lang/python/docs/} directory.@footnote{The entire PyME git log and other preceding VCS logs are
+located in the @samp{gpgme/lang/python/docs/old-commits.log} file.}
+As the log for PyME is nearly 100KB and the log for GPGME is
+approximately 1MB, this would cause considerable bloat, as well as
+some confusion, should the two be merged.
+
+Hence the unfortunate, but necessary, step to simply move the
+files.  A regular repository version has been maintained should it
+be possible to implement this better in the future.
+
+@node The Perils of PyPI
+@section The Perils of PyPI
+
+The early port of the Python 2 @samp{pyme} module as @samp{pyme3} was never
+added to PyPI while the focus remained on development and testing
+during 2015 and early 2016.  Later in 2016, however, when Justus
+completed his major integration work and subsequently renamed the
+module from @samp{pyme3} to @samp{gpg}, some prior releases were also
+provided through PyPI.
+
+Since these bindings require a matching release of the GPGME
+libraries in order to function, it was determined that there was
+little benefit in also providing a copy through PyPI since anyone
+obtaining the GPGME source code would obtain the Python bindings
+source code at the same time.  Whereas there was the potential to
+sew confusion amongst Python users installing the module from PyPI,
+only to discover that without the relevant C files, header files or
+SWIG compiled binaries, the Python module did them little good.
+
+There are only two files on PyPI which might turn up in a search
+for this module or a sample of its content:
+
+@enumerate
+@item
+gpg (1.8.0) - Python bindings for GPGME GnuPG cryptography library
+@item
+pyme (0.9.0) - Python support for GPGME GnuPG cryptography library
+@end enumerate
+
+@menu
+* GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library::
+* PyME 0·9·0 - Python support for GPGME GnuPG cryptography library::
+@end menu
+
+@node GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library
+@subsection GPG 1·8·0 - Python bindings for GPGME GnuPG cryptography library
+
+This is the most recent version to reach PyPI and is the version
+of the official Pyhon bindings which shipped with GPGME 1.8.0.  If
+you have GPGME 1.8.0 installed and @emph{only} 1.8.0 installed, then it
+is probably safe to use this copy from PyPI.
+
+As there have been a lot of changes since the release of GPGME
+1.8.0, the GnuPG Project recommends not using this version of the
+module and instead installing the current version of GPGME along
+with the Python bindings included with that package.
+
+@node PyME 0·9·0 - Python support for GPGME GnuPG cryptography library
+@subsection PyME 0·9·0 - Python support for GPGME GnuPG cryptography library
+
+This is the last release of the PyME bindings maintained by Martin
+Albrecht and is only compatible with Python 2, it will not work
+with Python 3.  This is the version of the software from which the
+port from Python 2 to Python 3 code was made in 2015.
+
+Users of the more recent Python bindings will recognise numerous
+points of similarity, but also significant differences.  It is
+likely that the more recent official bindings will feel "more
+pythonic."
+
+For those using Python 2, there is essentially no harm in using
+this module, but it may lack a number of more recent features
+added to GPGME.
+
+@bye
\ No newline at end of file
diff --git a/lang/python/doc/texinfo/texinfo.tex b/lang/python/doc/texinfo/texinfo.tex
new file mode 100644 (file)
index 0000000..a5c849c
--- /dev/null
@@ -0,0 +1,8962 @@
+% texinfo.tex -- TeX macros to handle Texinfo files.
+%
+% Load plain if necessary, i.e., if running under initex.
+\expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi
+%
+\def\texinfoversion{2007-12-02.17}
+%
+% Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, 2007,
+% 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
+% 2007 Free Software Foundation, Inc.
+%
+% This texinfo.tex file 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 3 of the
+% License, or (at your option) any later version.
+%
+% This texinfo.tex file 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
+% General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with this program.  If not, see <https://www.gnu.org/licenses/>.
+%
+% As a special exception, when this file is read by TeX when processing
+% a Texinfo source document, you may use the result without
+% restriction.  (This has been our intent since Texinfo was invented.)
+%
+% Please try the latest version of texinfo.tex before submitting bug
+% reports; you can get the latest version from:
+%   https://www.gnu.org/software/texinfo/ (the Texinfo home page), or
+%   ftp://tug.org/tex/texinfo.tex
+%     (and all CTAN mirrors, see http://www.ctan.org).
+% The texinfo.tex in any given distribution could well be out
+% of date, so if that's what you're using, please check.
+%
+% Send bug reports to bug-texinfo@gnu.org.  Please include including a
+% complete document in each bug report with which we can reproduce the
+% problem.  Patches are, of course, greatly appreciated.
+%
+% To process a Texinfo manual with TeX, it's most reliable to use the
+% texi2dvi shell script that comes with the distribution.  For a simple
+% manual foo.texi, however, you can get away with this:
+%   tex foo.texi
+%   texindex foo.??
+%   tex foo.texi
+%   tex foo.texi
+%   dvips foo.dvi -o  # or whatever; this makes foo.ps.
+% The extra TeX runs get the cross-reference information correct.
+% Sometimes one run after texindex suffices, and sometimes you need more
+% than two; texi2dvi does it as many times as necessary.
+%
+% It is possible to adapt texinfo.tex for other languages, to some
+% extent.  You can get the existing language-specific files from the
+% full Texinfo distribution.
+%
+% The GNU Texinfo home page is https://www.gnu.org/software/texinfo.
+
+
+\message{Loading texinfo [version \texinfoversion]:}
+
+% If in a .fmt file, print the version number
+% and turn on active characters that we couldn't do earlier because
+% they might have appeared in the input file name.
+\everyjob{\message{[Texinfo version \texinfoversion]}%
+  \catcode`+=\active \catcode`\_=\active}
+
+
+\chardef\other=12
+
+% We never want plain's \outer definition of \+ in Texinfo.
+% For @tex, we can use \tabalign.
+\let\+ = \relax
+
+% Save some plain tex macros whose names we will redefine.
+\let\ptexb=\b
+\let\ptexbullet=\bullet
+\let\ptexc=\c
+\let\ptexcomma=\,
+\let\ptexdot=\.
+\let\ptexdots=\dots
+\let\ptexend=\end
+\let\ptexequiv=\equiv
+\let\ptexexclam=\!
+\let\ptexfootnote=\footnote
+\let\ptexgtr=>
+\let\ptexhat=^
+\let\ptexi=\i
+\let\ptexindent=\indent
+\let\ptexinsert=\insert
+\let\ptexlbrace=\{
+\let\ptexless=<
+\let\ptexnewwrite\newwrite
+\let\ptexnoindent=\noindent
+\let\ptexplus=+
+\let\ptexrbrace=\}
+\let\ptexslash=\/
+\let\ptexstar=\*
+\let\ptext=\t
+
+% If this character appears in an error message or help string, it
+% starts a new line in the output.
+\newlinechar = `^^J
+
+% Use TeX 3.0's \inputlineno to get the line number, for better error
+% messages, but if we're using an old version of TeX, don't do anything.
+%
+\ifx\inputlineno\thisisundefined
+  \let\linenumber = \empty % Pre-3.0.
+\else
+  \def\linenumber{l.\the\inputlineno:\space}
+\fi
+
+% Set up fixed words for English if not already set.
+\ifx\putwordAppendix\undefined  \gdef\putwordAppendix{Appendix}\fi
+\ifx\putwordChapter\undefined   \gdef\putwordChapter{Chapter}\fi
+\ifx\putwordfile\undefined      \gdef\putwordfile{file}\fi
+\ifx\putwordin\undefined        \gdef\putwordin{in}\fi
+\ifx\putwordIndexIsEmpty\undefined     \gdef\putwordIndexIsEmpty{(Index is empty)}\fi
+\ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi
+\ifx\putwordInfo\undefined      \gdef\putwordInfo{Info}\fi
+\ifx\putwordInstanceVariableof\undefined \gdef\putwordInstanceVariableof{Instance Variable of}\fi
+\ifx\putwordMethodon\undefined  \gdef\putwordMethodon{Method on}\fi
+\ifx\putwordNoTitle\undefined   \gdef\putwordNoTitle{No Title}\fi
+\ifx\putwordof\undefined        \gdef\putwordof{of}\fi
+\ifx\putwordon\undefined        \gdef\putwordon{on}\fi
+\ifx\putwordpage\undefined      \gdef\putwordpage{page}\fi
+\ifx\putwordsection\undefined   \gdef\putwordsection{section}\fi
+\ifx\putwordSection\undefined   \gdef\putwordSection{Section}\fi
+\ifx\putwordsee\undefined       \gdef\putwordsee{see}\fi
+\ifx\putwordSee\undefined       \gdef\putwordSee{See}\fi
+\ifx\putwordShortTOC\undefined  \gdef\putwordShortTOC{Short Contents}\fi
+\ifx\putwordTOC\undefined       \gdef\putwordTOC{Table of Contents}\fi
+%
+\ifx\putwordMJan\undefined \gdef\putwordMJan{January}\fi
+\ifx\putwordMFeb\undefined \gdef\putwordMFeb{February}\fi
+\ifx\putwordMMar\undefined \gdef\putwordMMar{March}\fi
+\ifx\putwordMApr\undefined \gdef\putwordMApr{April}\fi
+\ifx\putwordMMay\undefined \gdef\putwordMMay{May}\fi
+\ifx\putwordMJun\undefined \gdef\putwordMJun{June}\fi
+\ifx\putwordMJul\undefined \gdef\putwordMJul{July}\fi
+\ifx\putwordMAug\undefined \gdef\putwordMAug{August}\fi
+\ifx\putwordMSep\undefined \gdef\putwordMSep{September}\fi
+\ifx\putwordMOct\undefined \gdef\putwordMOct{October}\fi
+\ifx\putwordMNov\undefined \gdef\putwordMNov{November}\fi
+\ifx\putwordMDec\undefined \gdef\putwordMDec{December}\fi
+%
+\ifx\putwordDefmac\undefined    \gdef\putwordDefmac{Macro}\fi
+\ifx\putwordDefspec\undefined   \gdef\putwordDefspec{Special Form}\fi
+\ifx\putwordDefvar\undefined    \gdef\putwordDefvar{Variable}\fi
+\ifx\putwordDefopt\undefined    \gdef\putwordDefopt{User Option}\fi
+\ifx\putwordDeffunc\undefined   \gdef\putwordDeffunc{Function}\fi
+
+% Since the category of space is not known, we have to be careful.
+\chardef\spacecat = 10
+\def\spaceisspace{\catcode`\ =\spacecat}
+
+% sometimes characters are active, so we need control sequences.
+\chardef\colonChar = `\:
+\chardef\commaChar = `\,
+\chardef\dashChar  = `\-
+\chardef\dotChar   = `\.
+\chardef\exclamChar= `\!
+\chardef\lquoteChar= `\`
+\chardef\questChar = `\?
+\chardef\rquoteChar= `\'
+\chardef\semiChar  = `\;
+\chardef\underChar = `\_
+
+% Ignore a token.
+%
+\def\gobble#1{}
+
+% The following is used inside several \edef's.
+\def\makecsname#1{\expandafter\noexpand\csname#1\endcsname}
+
+% Hyphenation fixes.
+\hyphenation{
+  Flor-i-da Ghost-script Ghost-view Mac-OS Post-Script
+  ap-pen-dix bit-map bit-maps
+  data-base data-bases eshell fall-ing half-way long-est man-u-script
+  man-u-scripts mini-buf-fer mini-buf-fers over-view par-a-digm
+  par-a-digms rath-er rec-tan-gu-lar ro-bot-ics se-vere-ly set-up spa-ces
+  spell-ing spell-ings
+  stand-alone strong-est time-stamp time-stamps which-ever white-space
+  wide-spread wrap-around
+}
+
+% Margin to add to right of even pages, to left of odd pages.
+\newdimen\bindingoffset
+\newdimen\normaloffset
+\newdimen\pagewidth \newdimen\pageheight
+
+% For a final copy, take out the rectangles
+% that mark overfull boxes (in case you have decided
+% that the text looks ok even though it passes the margin).
+%
+\def\finalout{\overfullrule=0pt}
+
+% @| inserts a changebar to the left of the current line.  It should
+% surround any changed text.  This approach does *not* work if the
+% change spans more than two lines of output.  To handle that, we would
+% have adopt a much more difficult approach (putting marks into the main
+% vertical list for the beginning and end of each change).
+%
+\def\|{%
+  % \vadjust can only be used in horizontal mode.
+  \leavevmode
+  %
+  % Append this vertical mode material after the current line in the output.
+  \vadjust{%
+    % We want to insert a rule with the height and depth of the current
+    % leading; that is exactly what \strutbox is supposed to record.
+    \vskip-\baselineskip
+    %
+    % \vadjust-items are inserted at the left edge of the type.  So
+    % the \llap here moves out into the left-hand margin.
+    \llap{%
+      %
+      % For a thicker or thinner bar, change the `1pt'.
+      \vrule height\baselineskip width1pt
+      %
+      % This is the space between the bar and the text.
+      \hskip 12pt
+    }%
+  }%
+}
+
+% Sometimes it is convenient to have everything in the transcript file
+% and nothing on the terminal.  We don't just call \tracingall here,
+% since that produces some useless output on the terminal.  We also make
+% some effort to order the tracing commands to reduce output in the log
+% file; cf. trace.sty in LaTeX.
+%
+\def\gloggingall{\begingroup \globaldefs = 1 \loggingall \endgroup}%
+\def\loggingall{%
+  \tracingstats2
+  \tracingpages1
+  \tracinglostchars2  % 2 gives us more in etex
+  \tracingparagraphs1
+  \tracingoutput1
+  \tracingmacros2
+  \tracingrestores1
+  \showboxbreadth\maxdimen \showboxdepth\maxdimen
+  \ifx\eTeXversion\undefined\else % etex gives us more logging
+    \tracingscantokens1
+    \tracingifs1
+    \tracinggroups1
+    \tracingnesting2
+    \tracingassigns1
+  \fi
+  \tracingcommands3  % 3 gives us more in etex
+  \errorcontextlines16
+}%
+
+% add check for \lastpenalty to plain's definitions.  If the last thing
+% we did was a \nobreak, we don't want to insert more space.
+%
+\def\smallbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\smallskipamount
+  \removelastskip\penalty-50\smallskip\fi\fi}
+\def\medbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\medskipamount
+  \removelastskip\penalty-100\medskip\fi\fi}
+\def\bigbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\bigskipamount
+  \removelastskip\penalty-200\bigskip\fi\fi}
+
+% For @cropmarks command.
+% Do @cropmarks to get crop marks.
+%
+\newif\ifcropmarks
+\let\cropmarks = \cropmarkstrue
+%
+% Dimensions to add cropmarks at corners.
+% Added by P. A. MacKay, 12 Nov. 1986
+%
+\newdimen\outerhsize \newdimen\outervsize % set by the paper size routines
+\newdimen\cornerlong  \cornerlong=1pc
+\newdimen\cornerthick \cornerthick=.3pt
+\newdimen\topandbottommargin \topandbottommargin=.75in
+
+% Output a mark which sets \thischapter, \thissection and \thiscolor.
+% We dump everything together because we only have one kind of mark.
+% This works because we only use \botmark / \topmark, not \firstmark.
+%
+% A mark contains a subexpression of the \ifcase ... \fi construct.
+% \get*marks macros below extract the needed part using \ifcase.
+%
+% Another complication is to let the user choose whether \thischapter
+% (\thissection) refers to the chapter (section) in effect at the top
+% of a page, or that at the bottom of a page.  The solution is
+% described on page 260 of The TeXbook.  It involves outputting two
+% marks for the sectioning macros, one before the section break, and
+% one after.  I won't pretend I can describe this better than DEK...
+\def\domark{%
+  \toks0=\expandafter{\lastchapterdefs}%
+  \toks2=\expandafter{\lastsectiondefs}%
+  \toks4=\expandafter{\prevchapterdefs}%
+  \toks6=\expandafter{\prevsectiondefs}%
+  \toks8=\expandafter{\lastcolordefs}%
+  \mark{%
+                   \the\toks0 \the\toks2
+      \noexpand\or \the\toks4 \the\toks6
+    \noexpand\else \the\toks8
+  }%
+}
+% \topmark doesn't work for the very first chapter (after the title
+% page or the contents), so we use \firstmark there -- this gets us
+% the mark with the chapter defs, unless the user sneaks in, e.g.,
+% @setcolor (or @url, or @link, etc.) between @contents and the very
+% first @chapter.
+\def\gettopheadingmarks{%
+  \ifcase0\topmark\fi
+  \ifx\thischapter\empty \ifcase0\firstmark\fi \fi
+}
+\def\getbottomheadingmarks{\ifcase1\botmark\fi}
+\def\getcolormarks{\ifcase2\topmark\fi}
+
+% Avoid "undefined control sequence" errors.
+\def\lastchapterdefs{}
+\def\lastsectiondefs{}
+\def\prevchapterdefs{}
+\def\prevsectiondefs{}
+\def\lastcolordefs{}
+
+% Main output routine.
+\chardef\PAGE = 255
+\output = {\onepageout{\pagecontents\PAGE}}
+
+\newbox\headlinebox
+\newbox\footlinebox
+
+% \onepageout takes a vbox as an argument.  Note that \pagecontents
+% does insertions, but you have to call it yourself.
+\def\onepageout#1{%
+  \ifcropmarks \hoffset=0pt \else \hoffset=\normaloffset \fi
+  %
+  \ifodd\pageno  \advance\hoffset by \bindingoffset
+  \else \advance\hoffset by -\bindingoffset\fi
+  %
+  % Do this outside of the \shipout so @code etc. will be expanded in
+  % the headline as they should be, not taken literally (outputting ''code).
+  \ifodd\pageno \getoddheadingmarks \else \getevenheadingmarks \fi
+  \setbox\headlinebox = \vbox{\let\hsize=\pagewidth \makeheadline}%
+  \ifodd\pageno \getoddfootingmarks \else \getevenfootingmarks \fi
+  \setbox\footlinebox = \vbox{\let\hsize=\pagewidth \makefootline}%
+  %
+  {%
+    % Have to do this stuff outside the \shipout because we want it to
+    % take effect in \write's, yet the group defined by the \vbox ends
+    % before the \shipout runs.
+    %
+    \indexdummies         % don't expand commands in the output.
+    \normalturnoffactive  % \ in index entries must not stay \, e.g., if
+               % the page break happens to be in the middle of an example.
+               % We don't want .vr (or whatever) entries like this:
+               % \entry{{\tt \indexbackslash }acronym}{32}{\code {\acronym}}
+               % "\acronym" won't work when it's read back in;
+               % it needs to be
+               % {\code {{\tt \backslashcurfont }acronym}
+    \shipout\vbox{%
+      % Do this early so pdf references go to the beginning of the page.
+      \ifpdfmakepagedest \pdfdest name{\the\pageno} xyz\fi
+      %
+      \ifcropmarks \vbox to \outervsize\bgroup
+        \hsize = \outerhsize
+        \vskip-\topandbottommargin
+        \vtop to0pt{%
+          \line{\ewtop\hfil\ewtop}%
+          \nointerlineskip
+          \line{%
+            \vbox{\moveleft\cornerthick\nstop}%
+            \hfill
+            \vbox{\moveright\cornerthick\nstop}%
+          }%
+          \vss}%
+        \vskip\topandbottommargin
+        \line\bgroup
+          \hfil % center the page within the outer (page) hsize.
+          \ifodd\pageno\hskip\bindingoffset\fi
+          \vbox\bgroup
+      \fi
+      %
+      \unvbox\headlinebox
+      \pagebody{#1}%
+      \ifdim\ht\footlinebox > 0pt
+        % Only leave this space if the footline is nonempty.
+        % (We lessened \vsize for it in \oddfootingyyy.)
+        % The \baselineskip=24pt in plain's \makefootline has no effect.
+        \vskip 24pt
+        \unvbox\footlinebox
+      \fi
+      %
+      \ifcropmarks
+          \egroup % end of \vbox\bgroup
+        \hfil\egroup % end of (centering) \line\bgroup
+        \vskip\topandbottommargin plus1fill minus1fill
+        \boxmaxdepth = \cornerthick
+        \vbox to0pt{\vss
+          \line{%
+            \vbox{\moveleft\cornerthick\nsbot}%
+            \hfill
+            \vbox{\moveright\cornerthick\nsbot}%
+          }%
+          \nointerlineskip
+          \line{\ewbot\hfil\ewbot}%
+        }%
+      \egroup % \vbox from first cropmarks clause
+      \fi
+    }% end of \shipout\vbox
+  }% end of group with \indexdummies
+  \advancepageno
+  \ifnum\outputpenalty>-20000 \else\dosupereject\fi
+}
+
+\newinsert\margin \dimen\margin=\maxdimen
+
+\def\pagebody#1{\vbox to\pageheight{\boxmaxdepth=\maxdepth #1}}
+{\catcode`\@ =11
+\gdef\pagecontents#1{\ifvoid\topins\else\unvbox\topins\fi
+% marginal hacks, juha@viisa.uucp (Juha Takala)
+\ifvoid\margin\else % marginal info is present
+  \rlap{\kern\hsize\vbox to\z@{\kern1pt\box\margin \vss}}\fi
+\dimen@=\dp#1\relax \unvbox#1\relax
+\ifvoid\footins\else\vskip\skip\footins\footnoterule \unvbox\footins\fi
+\ifr@ggedbottom \kern-\dimen@ \vfil \fi}
+}
+
+% Here are the rules for the cropmarks.  Note that they are
+% offset so that the space between them is truly \outerhsize or \outervsize
+% (P. A. MacKay, 12 November, 1986)
+%
+\def\ewtop{\vrule height\cornerthick depth0pt width\cornerlong}
+\def\nstop{\vbox
+  {\hrule height\cornerthick depth\cornerlong width\cornerthick}}
+\def\ewbot{\vrule height0pt depth\cornerthick width\cornerlong}
+\def\nsbot{\vbox
+  {\hrule height\cornerlong depth\cornerthick width\cornerthick}}
+
+% Parse an argument, then pass it to #1.  The argument is the rest of
+% the input line (except we remove a trailing comment).  #1 should be a
+% macro which expects an ordinary undelimited TeX argument.
+%
+\def\parsearg{\parseargusing{}}
+\def\parseargusing#1#2{%
+  \def\argtorun{#2}%
+  \begingroup
+    \obeylines
+    \spaceisspace
+    #1%
+    \parseargline\empty% Insert the \empty token, see \finishparsearg below.
+}
+
+{\obeylines %
+  \gdef\parseargline#1^^M{%
+    \endgroup % End of the group started in \parsearg.
+    \argremovecomment #1\comment\ArgTerm%
+  }%
+}
+
+% First remove any @comment, then any @c comment.
+\def\argremovecomment#1\comment#2\ArgTerm{\argremovec #1\c\ArgTerm}
+\def\argremovec#1\c#2\ArgTerm{\argcheckspaces#1\^^M\ArgTerm}
+
+% Each occurrence of `\^^M' or `<space>\^^M' is replaced by a single space.
+%
+% \argremovec might leave us with trailing space, e.g.,
+%    @end itemize  @c foo
+% This space token undergoes the same procedure and is eventually removed
+% by \finishparsearg.
+%
+\def\argcheckspaces#1\^^M{\argcheckspacesX#1\^^M \^^M}
+\def\argcheckspacesX#1 \^^M{\argcheckspacesY#1\^^M}
+\def\argcheckspacesY#1\^^M#2\^^M#3\ArgTerm{%
+  \def\temp{#3}%
+  \ifx\temp\empty
+    % Do not use \next, perhaps the caller of \parsearg uses it; reuse \temp:
+    \let\temp\finishparsearg
+  \else
+    \let\temp\argcheckspaces
+  \fi
+  % Put the space token in:
+  \temp#1 #3\ArgTerm
+}
+
+% If a _delimited_ argument is enclosed in braces, they get stripped; so
+% to get _exactly_ the rest of the line, we had to prevent such situation.
+% We prepended an \empty token at the very beginning and we expand it now,
+% just before passing the control to \argtorun.
+% (Similarly, we have to think about #3 of \argcheckspacesY above: it is
+% either the null string, or it ends with \^^M---thus there is no danger
+% that a pair of braces would be stripped.
+%
+% But first, we have to remove the trailing space token.
+%
+\def\finishparsearg#1 \ArgTerm{\expandafter\argtorun\expandafter{#1}}
+
+% \parseargdef\foo{...}
+%      is roughly equivalent to
+% \def\foo{\parsearg\Xfoo}
+% \def\Xfoo#1{...}
+%
+% Actually, I use \csname\string\foo\endcsname, ie. \\foo, as it is my
+% favourite TeX trick.  --kasal, 16nov03
+
+\def\parseargdef#1{%
+  \expandafter \doparseargdef \csname\string#1\endcsname #1%
+}
+\def\doparseargdef#1#2{%
+  \def#2{\parsearg#1}%
+  \def#1##1%
+}
+
+% Several utility definitions with active space:
+{
+  \obeyspaces
+  \gdef\obeyedspace{ }
+
+  % Make each space character in the input produce a normal interword
+  % space in the output.  Don't allow a line break at this space, as this
+  % is used only in environments like @example, where each line of input
+  % should produce a line of output anyway.
+  %
+  \gdef\sepspaces{\obeyspaces\let =\tie}
+
+  % If an index command is used in an @example environment, any spaces
+  % therein should become regular spaces in the raw index file, not the
+  % expansion of \tie (\leavevmode \penalty \@M \ ).
+  \gdef\unsepspaces{\let =\space}
+}
+
+
+\def\flushcr{\ifx\par\lisppar \def\next##1{}\else \let\next=\relax \fi \next}
+
+% Define the framework for environments in texinfo.tex.  It's used like this:
+%
+%   \envdef\foo{...}
+%   \def\Efoo{...}
+%
+% It's the responsibility of \envdef to insert \begingroup before the
+% actual body; @end closes the group after calling \Efoo.  \envdef also
+% defines \thisenv, so the current environment is known; @end checks
+% whether the environment name matches.  The \checkenv macro can also be
+% used to check whether the current environment is the one expected.
+%
+% Non-false conditionals (@iftex, @ifset) don't fit into this, so they
+% are not treated as environments; they don't open a group.  (The
+% implementation of @end takes care not to call \endgroup in this
+% special case.)
+
+
+% At runtime, environments start with this:
+\def\startenvironment#1{\begingroup\def\thisenv{#1}}
+% initialize
+\let\thisenv\empty
+
+% ... but they get defined via ``\envdef\foo{...}'':
+\long\def\envdef#1#2{\def#1{\startenvironment#1#2}}
+\def\envparseargdef#1#2{\parseargdef#1{\startenvironment#1#2}}
+
+% Check whether we're in the right environment:
+\def\checkenv#1{%
+  \def\temp{#1}%
+  \ifx\thisenv\temp
+  \else
+    \badenverr
+  \fi
+}
+
+% Environment mismatch, #1 expected:
+\def\badenverr{%
+  \errhelp = \EMsimple
+  \errmessage{This command can appear only \inenvironment\temp,
+    not \inenvironment\thisenv}%
+}
+\def\inenvironment#1{%
+  \ifx#1\empty
+    out of any environment%
+  \else
+    in environment \expandafter\string#1%
+  \fi
+}
+
+% @end foo executes the definition of \Efoo.
+% But first, it executes a specialized version of \checkenv
+%
+\parseargdef\end{%
+  \if 1\csname iscond.#1\endcsname
+  \else
+    % The general wording of \badenverr may not be ideal, but... --kasal, 06nov03
+    \expandafter\checkenv\csname#1\endcsname
+    \csname E#1\endcsname
+    \endgroup
+  \fi
+}
+
+\newhelp\EMsimple{Press RETURN to continue.}
+
+
+%% Simple single-character @ commands
+
+% @@ prints an @
+% Kludge this until the fonts are right (grr).
+\def\@{{\tt\char64}}
+
+% This is turned off because it was never documented
+% and you can use @w{...} around a quote to suppress ligatures.
+%% Define @` and @' to be the same as ` and '
+%% but suppressing ligatures.
+%\def\`{{`}}
+%\def\'{{'}}
+
+% Used to generate quoted braces.
+\def\mylbrace {{\tt\char123}}
+\def\myrbrace {{\tt\char125}}
+\let\{=\mylbrace
+\let\}=\myrbrace
+\begingroup
+  % Definitions to produce \{ and \} commands for indices,
+  % and @{ and @} for the aux/toc files.
+  \catcode`\{ = \other \catcode`\} = \other
+  \catcode`\[ = 1 \catcode`\] = 2
+  \catcode`\! = 0 \catcode`\\ = \other
+  !gdef!lbracecmd[\{]%
+  !gdef!rbracecmd[\}]%
+  !gdef!lbraceatcmd[@{]%
+  !gdef!rbraceatcmd[@}]%
+!endgroup
+
+% @comma{} to avoid , parsing problems.
+\let\comma = ,
+
+% Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent
+% Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H.
+\let\, = \c
+\let\dotaccent = \.
+\def\ringaccent#1{{\accent23 #1}}
+\let\tieaccent = \t
+\let\ubaraccent = \b
+\let\udotaccent = \d
+
+% Other special characters: @questiondown @exclamdown @ordf @ordm
+% Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss.
+\def\questiondown{?`}
+\def\exclamdown{!`}
+\def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}}
+\def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}}
+
+% Dotless i and dotless j, used for accents.
+\def\imacro{i}
+\def\jmacro{j}
+\def\dotless#1{%
+  \def\temp{#1}%
+  \ifx\temp\imacro \ptexi
+  \else\ifx\temp\jmacro \j
+  \else \errmessage{@dotless can be used only with i or j}%
+  \fi\fi
+}
+
+% The \TeX{} logo, as in plain, but resetting the spacing so that a
+% period following counts as ending a sentence.  (Idea found in latex.)
+%
+\edef\TeX{\TeX \spacefactor=1000 }
+
+% @LaTeX{} logo.  Not quite the same results as the definition in
+% latex.ltx, since we use a different font for the raised A; it's most
+% convenient for us to use an explicitly smaller font, rather than using
+% the \scriptstyle font (since we don't reset \scriptstyle and
+% \scriptscriptstyle).
+%
+\def\LaTeX{%
+  L\kern-.36em
+  {\setbox0=\hbox{T}%
+   \vbox to \ht0{\hbox{\selectfonts\lllsize A}\vss}}%
+  \kern-.15em
+  \TeX
+}
+
+% Be sure we're in horizontal mode when doing a tie, since we make space
+% equivalent to this in @example-like environments. Otherwise, a space
+% at the beginning of a line will start with \penalty -- and
+% since \penalty is valid in vertical mode, we'd end up putting the
+% penalty on the vertical list instead of in the new paragraph.
+{\catcode`@ = 11
+ % Avoid using \@M directly, because that causes trouble
+ % if the definition is written into an index file.
+ \global\let\tiepenalty = \@M
+ \gdef\tie{\leavevmode\penalty\tiepenalty\ }
+}
+
+% @: forces normal size whitespace following.
+\def\:{\spacefactor=1000 }
+
+% @* forces a line break.
+\def\*{\hfil\break\hbox{}\ignorespaces}
+
+% @/ allows a line break.
+\let\/=\allowbreak
+
+% @. is an end-of-sentence period.
+\def\.{.\spacefactor=\endofsentencespacefactor\space}
+
+% @! is an end-of-sentence bang.
+\def\!{!\spacefactor=\endofsentencespacefactor\space}
+
+% @? is an end-of-sentence query.
+\def\?{?\spacefactor=\endofsentencespacefactor\space}
+
+% @frenchspacing on|off  says whether to put extra space after punctuation.
+%
+\def\onword{on}
+\def\offword{off}
+%
+\parseargdef\frenchspacing{%
+  \def\temp{#1}%
+  \ifx\temp\onword \plainfrenchspacing
+  \else\ifx\temp\offword \plainnonfrenchspacing
+  \else
+    \errhelp = \EMsimple
+    \errmessage{Unknown @frenchspacing option `\temp', must be on/off}%
+  \fi\fi
+}
+
+% @w prevents a word break.  Without the \leavevmode, @w at the
+% beginning of a paragraph, when TeX is still in vertical mode, would
+% produce a whole line of output instead of starting the paragraph.
+\def\w#1{\leavevmode\hbox{#1}}
+
+% @group ... @end group forces ... to be all on one page, by enclosing
+% it in a TeX vbox.  We use \vtop instead of \vbox to construct the box
+% to keep its height that of a normal line.  According to the rules for
+% \topskip (p.114 of the TeXbook), the glue inserted is
+% max (\topskip - \ht (first item), 0).  If that height is large,
+% therefore, no glue is inserted, and the space between the headline and
+% the text is small, which looks bad.
+%
+% Another complication is that the group might be very large.  This can
+% cause the glue on the previous page to be unduly stretched, because it
+% does not have much material.  In this case, it's better to add an
+% explicit \vfill so that the extra space is at the bottom.  The
+% threshold for doing this is if the group is more than \vfilllimit
+% percent of a page (\vfilllimit can be changed inside of @tex).
+%
+\newbox\groupbox
+\def\vfilllimit{0.7}
+%
+\envdef\group{%
+  \ifnum\catcode`\^^M=\active \else
+    \errhelp = \groupinvalidhelp
+    \errmessage{@group invalid in context where filling is enabled}%
+  \fi
+  \startsavinginserts
+  %
+  \setbox\groupbox = \vtop\bgroup
+    % Do @comment since we are called inside an environment such as
+    % @example, where each end-of-line in the input causes an
+    % end-of-line in the output.  We don't want the end-of-line after
+    % the `@group' to put extra space in the output.  Since @group
+    % should appear on a line by itself (according to the Texinfo
+    % manual), we don't worry about eating any user text.
+    \comment
+}
+%
+% The \vtop produces a box with normal height and large depth; thus, TeX puts
+% \baselineskip glue before it, and (when the next line of text is done)
+% \lineskip glue after it.  Thus, space below is not quite equal to space
+% above.  But it's pretty close.
+\def\Egroup{%
+    % To get correct interline space between the last line of the group
+    % and the first line afterwards, we have to propagate \prevdepth.
+    \endgraf % Not \par, as it may have been set to \lisppar.
+    \global\dimen1 = \prevdepth
+  \egroup           % End the \vtop.
+  % \dimen0 is the vertical size of the group's box.
+  \dimen0 = \ht\groupbox  \advance\dimen0 by \dp\groupbox
+  % \dimen2 is how much space is left on the page (more or less).
+  \dimen2 = \pageheight   \advance\dimen2 by -\pagetotal
+  % if the group doesn't fit on the current page, and it's a big big
+  % group, force a page break.
+  \ifdim \dimen0 > \dimen2
+    \ifdim \pagetotal < \vfilllimit\pageheight
+      \page
+    \fi
+  \fi
+  \box\groupbox
+  \prevdepth = \dimen1
+  \checkinserts
+}
+%
+% TeX puts in an \escapechar (i.e., `@') at the beginning of the help
+% message, so this ends up printing `@group can only ...'.
+%
+\newhelp\groupinvalidhelp{%
+group can only be used in environments such as @example,^^J%
+where each line of input produces a line of output.}
+
+% @need space-in-mils
+% forces a page break if there is not space-in-mils remaining.
+
+\newdimen\mil  \mil=0.001in
+
+% Old definition--didn't work.
+%\parseargdef\need{\par %
+%% This method tries to make TeX break the page naturally
+%% if the depth of the box does not fit.
+%{\baselineskip=0pt%
+%\vtop to #1\mil{\vfil}\kern -#1\mil\nobreak
+%\prevdepth=-1000pt
+%}}
+
+\parseargdef\need{%
+  % Ensure vertical mode, so we don't make a big box in the middle of a
+  % paragraph.
+  \par
+  %
+  % If the @need value is less than one line space, it's useless.
+  \dimen0 = #1\mil
+  \dimen2 = \ht\strutbox
+  \advance\dimen2 by \dp\strutbox
+  \ifdim\dimen0 > \dimen2
+    %
+    % Do a \strut just to make the height of this box be normal, so the
+    % normal leading is inserted relative to the preceding line.
+    % And a page break here is fine.
+    \vtop to #1\mil{\strut\vfil}%
+    %
+    % TeX does not even consider page breaks if a penalty added to the
+    % main vertical list is 10000 or more.  But in order to see if the
+    % empty box we just added fits on the page, we must make it consider
+    % page breaks.  On the other hand, we don't want to actually break the
+    % page after the empty box.  So we use a penalty of 9999.
+    %
+    % There is an extremely small chance that TeX will actually break the
+    % page at this \penalty, if there are no other feasible breakpoints in
+    % sight.  (If the user is using lots of big @group commands, which
+    % almost-but-not-quite fill up a page, TeX will have a hard time doing
+    % good page breaking, for example.)  However, I could not construct an
+    % example where a page broke at this \penalty; if it happens in a real
+    % document, then we can reconsider our strategy.
+    \penalty9999
+    %
+    % Back up by the size of the box, whether we did a page break or not.
+    \kern -#1\mil
+    %
+    % Do not allow a page break right after this kern.
+    \nobreak
+  \fi
+}
+
+% @br   forces paragraph break (and is undocumented).
+
+\let\br = \par
+
+% @page forces the start of a new page.
+%
+\def\page{\par\vfill\supereject}
+
+% @exdent text....
+% outputs text on separate line in roman font, starting at standard page margin
+
+% This records the amount of indent in the innermost environment.
+% That's how much \exdent should take out.
+\newskip\exdentamount
+
+% This defn is used inside fill environments such as @defun.
+\parseargdef\exdent{\hfil\break\hbox{\kern -\exdentamount{\rm#1}}\hfil\break}
+
+% This defn is used inside nofill environments such as @example.
+\parseargdef\nofillexdent{{\advance \leftskip by -\exdentamount
+  \leftline{\hskip\leftskip{\rm#1}}}}
+
+% @inmargin{WHICH}{TEXT} puts TEXT in the WHICH margin next to the current
+% paragraph.  For more general purposes, use the \margin insertion
+% class.  WHICH is `l' or `r'.
+%
+\newskip\inmarginspacing \inmarginspacing=1cm
+\def\strutdepth{\dp\strutbox}
+%
+\def\doinmargin#1#2{\strut\vadjust{%
+  \nobreak
+  \kern-\strutdepth
+  \vtop to \strutdepth{%
+    \baselineskip=\strutdepth
+    \vss
+    % if you have multiple lines of stuff to put here, you'll need to
+    % make the vbox yourself of the appropriate size.
+    \ifx#1l%
+      \llap{\ignorespaces #2\hskip\inmarginspacing}%
+    \else
+      \rlap{\hskip\hsize \hskip\inmarginspacing \ignorespaces #2}%
+    \fi
+    \null
+  }%
+}}
+\def\inleftmargin{\doinmargin l}
+\def\inrightmargin{\doinmargin r}
+%
+% @inmargin{TEXT [, RIGHT-TEXT]}
+% (if RIGHT-TEXT is given, use TEXT for left page, RIGHT-TEXT for right;
+% else use TEXT for both).
+%
+\def\inmargin#1{\parseinmargin #1,,\finish}
+\def\parseinmargin#1,#2,#3\finish{% not perfect, but better than nothing.
+  \setbox0 = \hbox{\ignorespaces #2}%
+  \ifdim\wd0 > 0pt
+    \def\lefttext{#1}%  have both texts
+    \def\righttext{#2}%
+  \else
+    \def\lefttext{#1}%  have only one text
+    \def\righttext{#1}%
+  \fi
+  %
+  \ifodd\pageno
+    \def\temp{\inrightmargin\righttext}% odd page -> outside is right margin
+  \else
+    \def\temp{\inleftmargin\lefttext}%
+  \fi
+  \temp
+}
+
+% @include file    insert text of that file as input.
+%
+\def\include{\parseargusing\filenamecatcodes\includezzz}
+\def\includezzz#1{%
+  \pushthisfilestack
+  \def\thisfile{#1}%
+  {%
+    \makevalueexpandable
+    \def\temp{\input #1 }%
+    \expandafter
+  }\temp
+  \popthisfilestack
+}
+\def\filenamecatcodes{%
+  \catcode`\\=\other
+  \catcode`~=\other
+  \catcode`^=\other
+  \catcode`_=\other
+  \catcode`|=\other
+  \catcode`<=\other
+  \catcode`>=\other
+  \catcode`+=\other
+  \catcode`-=\other
+}
+
+\def\pushthisfilestack{%
+  \expandafter\pushthisfilestackX\popthisfilestack\StackTerm
+}
+\def\pushthisfilestackX{%
+  \expandafter\pushthisfilestackY\thisfile\StackTerm
+}
+\def\pushthisfilestackY #1\StackTerm #2\StackTerm {%
+  \gdef\popthisfilestack{\gdef\thisfile{#1}\gdef\popthisfilestack{#2}}%
+}
+
+\def\popthisfilestack{\errthisfilestackempty}
+\def\errthisfilestackempty{\errmessage{Internal error:
+  the stack of filenames is empty.}}
+
+\def\thisfile{}
+
+% @center line
+% outputs that line, centered.
+%
+\parseargdef\center{%
+  \ifhmode
+    \let\next\centerH
+  \else
+    \let\next\centerV
+  \fi
+  \next{\hfil \ignorespaces#1\unskip \hfil}%
+}
+\def\centerH#1{%
+  {%
+    \hfil\break
+    \advance\hsize by -\leftskip
+    \advance\hsize by -\rightskip
+    \line{#1}%
+    \break
+  }%
+}
+\def\centerV#1{\line{\kern\leftskip #1\kern\rightskip}}
+
+% @sp n   outputs n lines of vertical space
+
+\parseargdef\sp{\vskip #1\baselineskip}
+
+% @comment ...line which is ignored...
+% @c is the same as @comment
+% @ignore ... @end ignore  is another way to write a comment
+
+\def\comment{\begingroup \catcode`\^^M=\other%
+\catcode`\@=\other \catcode`\{=\other \catcode`\}=\other%
+\commentxxx}
+{\catcode`\^^M=\other \gdef\commentxxx#1^^M{\endgroup}}
+
+\let\c=\comment
+
+% @paragraphindent NCHARS
+% We'll use ems for NCHARS, close enough.
+% NCHARS can also be the word `asis' or `none'.
+% We cannot feasibly implement @paragraphindent asis, though.
+%
+\def\asisword{asis} % no translation, these are keywords
+\def\noneword{none}
+%
+\parseargdef\paragraphindent{%
+  \def\temp{#1}%
+  \ifx\temp\asisword
+  \else
+    \ifx\temp\noneword
+      \defaultparindent = 0pt
+    \else
+      \defaultparindent = #1em
+    \fi
+  \fi
+  \parindent = \defaultparindent
+}
+
+% @exampleindent NCHARS
+% We'll use ems for NCHARS like @paragraphindent.
+% It seems @exampleindent asis isn't necessary, but
+% I preserve it to make it similar to @paragraphindent.
+\parseargdef\exampleindent{%
+  \def\temp{#1}%
+  \ifx\temp\asisword
+  \else
+    \ifx\temp\noneword
+      \lispnarrowing = 0pt
+    \else
+      \lispnarrowing = #1em
+    \fi
+  \fi
+}
+
+% @firstparagraphindent WORD
+% If WORD is `none', then suppress indentation of the first paragraph
+% after a section heading.  If WORD is `insert', then do indent at such
+% paragraphs.
+%
+% The paragraph indentation is suppressed or not by calling
+% \suppressfirstparagraphindent, which the sectioning commands do.
+% We switch the definition of this back and forth according to WORD.
+% By default, we suppress indentation.
+%
+\def\suppressfirstparagraphindent{\dosuppressfirstparagraphindent}
+\def\insertword{insert}
+%
+\parseargdef\firstparagraphindent{%
+  \def\temp{#1}%
+  \ifx\temp\noneword
+    \let\suppressfirstparagraphindent = \dosuppressfirstparagraphindent
+  \else\ifx\temp\insertword
+    \let\suppressfirstparagraphindent = \relax
+  \else
+    \errhelp = \EMsimple
+    \errmessage{Unknown @firstparagraphindent option `\temp'}%
+  \fi\fi
+}
+
+% Here is how we actually suppress indentation.  Redefine \everypar to
+% \kern backwards by \parindent, and then reset itself to empty.
+%
+% We also make \indent itself not actually do anything until the next
+% paragraph.
+%
+\gdef\dosuppressfirstparagraphindent{%
+  \gdef\indent{%
+    \restorefirstparagraphindent
+    \indent
+  }%
+  \gdef\noindent{%
+    \restorefirstparagraphindent
+    \noindent
+  }%
+  \global\everypar = {%
+    \kern -\parindent
+    \restorefirstparagraphindent
+  }%
+}
+
+\gdef\restorefirstparagraphindent{%
+  \global \let \indent = \ptexindent
+  \global \let \noindent = \ptexnoindent
+  \global \everypar = {}%
+}
+
+
+% @asis just yields its argument.  Used with @table, for example.
+%
+\def\asis#1{#1}
+
+% @math outputs its argument in math mode.
+%
+% One complication: _ usually means subscripts, but it could also mean
+% an actual _ character, as in @math{@var{some_variable} + 1}.  So make
+% _ active, and distinguish by seeing if the current family is \slfam,
+% which is what @var uses.
+{
+  \catcode`\_ = \active
+  \gdef\mathunderscore{%
+    \catcode`\_=\active
+    \def_{\ifnum\fam=\slfam \_\else\sb\fi}%
+  }
+}
+% Another complication: we want \\ (and @\) to output a \ character.
+% FYI, plain.tex uses \\ as a temporary control sequence (why?), but
+% this is not advertised and we don't care.  Texinfo does not
+% otherwise define @\.
+%
+% The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\.
+\def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi}
+%
+\def\math{%
+  \tex
+  \mathunderscore
+  \let\\ = \mathbackslash
+  \mathactive
+  $\finishmath
+}
+\def\finishmath#1{#1$\endgroup}  % Close the group opened by \tex.
+
+% Some active characters (such as <) are spaced differently in math.
+% We have to reset their definitions in case the @math was an argument
+% to a command which sets the catcodes (such as @item or @section).
+%
+{
+  \catcode`^ = \active
+  \catcode`< = \active
+  \catcode`> = \active
+  \catcode`+ = \active
+  \gdef\mathactive{%
+    \let^ = \ptexhat
+    \let< = \ptexless
+    \let> = \ptexgtr
+    \let+ = \ptexplus
+  }
+}
+
+% @bullet and @minus need the same treatment as @math, just above.
+\def\bullet{$\ptexbullet$}
+\def\minus{$-$}
+
+% @dots{} outputs an ellipsis using the current font.
+% We do .5em per period so that it has the same spacing in the cm
+% typewriter fonts as three actual period characters; on the other hand,
+% in other typewriter fonts three periods are wider than 1.5em.  So do
+% whichever is larger.
+%
+\def\dots{%
+  \leavevmode
+  \setbox0=\hbox{...}% get width of three periods
+  \ifdim\wd0 > 1.5em
+    \dimen0 = \wd0
+  \else
+    \dimen0 = 1.5em
+  \fi
+  \hbox to \dimen0{%
+    \hskip 0pt plus.25fil
+    .\hskip 0pt plus1fil
+    .\hskip 0pt plus1fil
+    .\hskip 0pt plus.5fil
+  }%
+}
+
+% @enddots{} is an end-of-sentence ellipsis.
+%
+\def\enddots{%
+  \dots
+  \spacefactor=\endofsentencespacefactor
+}
+
+% @comma{} is so commas can be inserted into text without messing up
+% Texinfo's parsing.
+%
+\let\comma = ,
+
+% @refill is a no-op.
+\let\refill=\relax
+
+% If working on a large document in chapters, it is convenient to
+% be able to disable indexing, cross-referencing, and contents, for test runs.
+% This is done with @novalidate (before @setfilename).
+%
+\newif\iflinks \linkstrue % by default we want the aux files.
+\let\novalidate = \linksfalse
+
+% @setfilename is done at the beginning of every texinfo file.
+% So open here the files we need to have open while reading the input.
+% This makes it possible to make a .fmt file for texinfo.
+\def\setfilename{%
+   \fixbackslash  % Turn off hack to swallow `\input texinfo'.
+   \iflinks
+     \tryauxfile
+     % Open the new aux file.  TeX will close it automatically at exit.
+     \immediate\openout\auxfile=\jobname.aux
+   \fi % \openindices needs to do some work in any case.
+   \openindices
+   \let\setfilename=\comment % Ignore extra @setfilename cmds.
+   %
+   % If texinfo.cnf is present on the system, read it.
+   % Useful for site-wide @afourpaper, etc.
+   \openin 1 texinfo.cnf
+   \ifeof 1 \else \input texinfo.cnf \fi
+   \closein 1
+   %
+   \comment % Ignore the actual filename.
+}
+
+% Called from \setfilename.
+%
+\def\openindices{%
+  \newindex{cp}%
+  \newcodeindex{fn}%
+  \newcodeindex{vr}%
+  \newcodeindex{tp}%
+  \newcodeindex{ky}%
+  \newcodeindex{pg}%
+}
+
+% @bye.
+\outer\def\bye{\pagealignmacro\tracingstats=1\ptexend}
+
+
+\message{pdf,}
+% adobe `portable' document format
+\newcount\tempnum
+\newcount\lnkcount
+\newtoks\filename
+\newcount\filenamelength
+\newcount\pgn
+\newtoks\toksA
+\newtoks\toksB
+\newtoks\toksC
+\newtoks\toksD
+\newbox\boxA
+\newcount\countA
+\newif\ifpdf
+\newif\ifpdfmakepagedest
+
+% when pdftex is run in dvi mode, \pdfoutput is defined (so \pdfoutput=1
+% can be set).  So we test for \relax and 0 as well as \undefined,
+% borrowed from ifpdf.sty.
+\ifx\pdfoutput\undefined
+\else
+  \ifx\pdfoutput\relax
+  \else
+    \ifcase\pdfoutput
+    \else
+      \pdftrue
+    \fi
+  \fi
+\fi
+
+% PDF uses PostScript string constants for the names of xref targets,
+% for display in the outlines, and in other places.  Thus, we have to
+% double any backslashes.  Otherwise, a name like "\node" will be
+% interpreted as a newline (\n), followed by o, d, e.  Not good.
+% http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html
+% (and related messages, the final outcome is that it is up to the TeX
+% user to double the backslashes and otherwise make the string valid, so
+% that's what we do).
+
+% double active backslashes.
+%
+{\catcode`\@=0 \catcode`\\=\active
+ @gdef@activebackslashdouble{%
+   @catcode`@\=@active
+   @let\=@doublebackslash}
+}
+
+% To handle parens, we must adopt a different approach, since parens are
+% not active characters.  hyperref.dtx (which has the same problem as
+% us) handles it with this amazing macro to replace tokens, with minor
+% changes for Texinfo.  It is included here under the GPL by permission
+% from the author, Heiko Oberdiek.
+%
+% #1 is the tokens to replace.
+% #2 is the replacement.
+% #3 is the control sequence with the string.
+%
+\def\HyPsdSubst#1#2#3{%
+  \def\HyPsdReplace##1#1##2\END{%
+    ##1%
+    \ifx\\##2\\%
+    \else
+      #2%
+      \HyReturnAfterFi{%
+        \HyPsdReplace##2\END
+      }%
+    \fi
+  }%
+  \xdef#3{\expandafter\HyPsdReplace#3#1\END}%
+}
+\long\def\HyReturnAfterFi#1\fi{\fi#1}
+
+% #1 is a control sequence in which to do the replacements.
+\def\backslashparens#1{%
+  \xdef#1{#1}% redefine it as its expansion; the definition is simply
+             % \lastnode when called from \setref -> \pdfmkdest.
+  \HyPsdSubst{(}{\realbackslash(}{#1}%
+  \HyPsdSubst{)}{\realbackslash)}{#1}%
+}
+
+\newhelp\nopdfimagehelp{Texinfo supports .png, .jpg, .jpeg, and .pdf images
+with PDF output, and none of those formats could be found.  (.eps cannot
+be supported due to the design of the PDF format; use regular TeX (DVI
+output) for that.)}
+
+\ifpdf
+  %
+  % Color manipulation macros based on pdfcolor.tex.
+  \def\cmykDarkRed{0.28 1 1 0.35}
+  \def\cmykBlack{0 0 0 1}
+  %
+  \def\pdfsetcolor#1{\pdfliteral{#1 k}}
+  % Set color, and create a mark which defines \thiscolor accordingly,
+  % so that \makeheadline knows which color to restore.
+  \def\setcolor#1{%
+    \xdef\lastcolordefs{\gdef\noexpand\thiscolor{#1}}%
+    \domark
+    \pdfsetcolor{#1}%
+  }
+  %
+  \def\maincolor{\cmykBlack}
+  \pdfsetcolor{\maincolor}
+  \edef\thiscolor{\maincolor}
+  \def\lastcolordefs{}
+  %
+  \def\makefootline{%
+    \baselineskip24pt
+    \line{\pdfsetcolor{\maincolor}\the\footline}%
+  }
+  %
+  \def\makeheadline{%
+    \vbox to 0pt{%
+      \vskip-22.5pt
+      \line{%
+        \vbox to8.5pt{}%
+        % Extract \thiscolor definition from the marks.
+        \getcolormarks
+        % Typeset the headline with \maincolor, then restore the color.
+        \pdfsetcolor{\maincolor}\the\headline\pdfsetcolor{\thiscolor}%
+      }%
+      \vss
+    }%
+    \nointerlineskip
+  }
+  %
+  %
+  \pdfcatalog{/PageMode /UseOutlines}
+  %
+  % #1 is image name, #2 width (might be empty/whitespace), #3 height (ditto).
+  \def\dopdfimage#1#2#3{%
+    \def\imagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}%
+    \def\imageheight{#3}\setbox2 = \hbox{\ignorespaces #3}%
+    %
+    % pdftex (and the PDF format) support .png, .jpg, .pdf (among
+    % others).  Let's try in that order.
+    \let\pdfimgext=\empty
+    \begingroup
+      \openin 1 #1.png \ifeof 1
+        \openin 1 #1.jpg \ifeof 1
+          \openin 1 #1.jpeg \ifeof 1
+            \openin 1 #1.JPG \ifeof 1
+              \openin 1 #1.pdf \ifeof 1
+                \errhelp = \nopdfimagehelp
+                \errmessage{Could not find image file #1 for pdf}%
+              \else \gdef\pdfimgext{pdf}%
+              \fi
+            \else \gdef\pdfimgext{JPG}%
+            \fi
+          \else \gdef\pdfimgext{jpeg}%
+          \fi
+        \else \gdef\pdfimgext{jpg}%
+        \fi
+      \else \gdef\pdfimgext{png}%
+      \fi
+      \closein 1
+    \endgroup
+    %
+    % without \immediate, pdftex seg faults when the same image is
+    % included twice.  (Version 3.14159-pre-1.0-unofficial-20010704.)
+    \ifnum\pdftexversion < 14
+      \immediate\pdfimage
+    \else
+      \immediate\pdfximage
+    \fi
+      \ifdim \wd0 >0pt width \imagewidth \fi
+      \ifdim \wd2 >0pt height \imageheight \fi
+      \ifnum\pdftexversion<13
+         #1.\pdfimgext
+       \else
+         {#1.\pdfimgext}%
+       \fi
+    \ifnum\pdftexversion < 14 \else
+      \pdfrefximage \pdflastximage
+    \fi}
+  %
+  \def\pdfmkdest#1{{%
+    % We have to set dummies so commands such as @code, and characters
+    % such as \, aren't expanded when present in a section title.
+    \indexnofonts
+    \turnoffactive
+    \activebackslashdouble
+    \makevalueexpandable
+    \def\pdfdestname{#1}%
+    \backslashparens\pdfdestname
+    \safewhatsit{\pdfdest name{\pdfdestname} xyz}%
+  }}
+  %
+  % used to mark target names; must be expandable.
+  \def\pdfmkpgn#1{#1}
+  %
+  % by default, use a color that is dark enough to print on paper as
+  % nearly black, but still distinguishable for online viewing.
+  \def\urlcolor{\cmykDarkRed}
+  \def\linkcolor{\cmykDarkRed}
+  \def\endlink{\setcolor{\maincolor}\pdfendlink}
+  %
+  % Adding outlines to PDF; macros for calculating structure of outlines
+  % come from Petr Olsak
+  \def\expnumber#1{\expandafter\ifx\csname#1\endcsname\relax 0%
+    \else \csname#1\endcsname \fi}
+  \def\advancenumber#1{\tempnum=\expnumber{#1}\relax
+    \advance\tempnum by 1
+    \expandafter\xdef\csname#1\endcsname{\the\tempnum}}
+  %
+  % #1 is the section text, which is what will be displayed in the
+  % outline by the pdf viewer.  #2 is the pdf expression for the number
+  % of subentries (or empty, for subsubsections).  #3 is the node text,
+  % which might be empty if this toc entry had no corresponding node.
+  % #4 is the page number
+  %
+  \def\dopdfoutline#1#2#3#4{%
+    % Generate a link to the node text if that exists; else, use the
+    % page number.  We could generate a destination for the section
+    % text in the case where a section has no node, but it doesn't
+    % seem worth the trouble, since most documents are normally structured.
+    \def\pdfoutlinedest{#3}%
+    \ifx\pdfoutlinedest\empty
+      \def\pdfoutlinedest{#4}%
+    \else
+      % Doubled backslashes in the name.
+      {\activebackslashdouble \xdef\pdfoutlinedest{#3}%
+       \backslashparens\pdfoutlinedest}%
+    \fi
+    %
+    % Also double the backslashes in the display string.
+    {\activebackslashdouble \xdef\pdfoutlinetext{#1}%
+     \backslashparens\pdfoutlinetext}%
+    %
+    \pdfoutline goto name{\pdfmkpgn{\pdfoutlinedest}}#2{\pdfoutlinetext}%
+  }
+  %
+  \def\pdfmakeoutlines{%
+    \begingroup
+      % Thanh's hack / proper braces in bookmarks
+      \edef\mylbrace{\iftrue \string{\else}\fi}\let\{=\mylbrace
+      \edef\myrbrace{\iffalse{\else\string}\fi}\let\}=\myrbrace
+      %
+      % Read toc silently, to get counts of subentries for \pdfoutline.
+      \def\numchapentry##1##2##3##4{%
+       \def\thischapnum{##2}%
+       \def\thissecnum{0}%
+       \def\thissubsecnum{0}%
+      }%
+      \def\numsecentry##1##2##3##4{%
+       \advancenumber{chap\thischapnum}%
+       \def\thissecnum{##2}%
+       \def\thissubsecnum{0}%
+      }%
+      \def\numsubsecentry##1##2##3##4{%
+       \advancenumber{sec\thissecnum}%
+       \def\thissubsecnum{##2}%
+      }%
+      \def\numsubsubsecentry##1##2##3##4{%
+       \advancenumber{subsec\thissubsecnum}%
+      }%
+      \def\thischapnum{0}%
+      \def\thissecnum{0}%
+      \def\thissubsecnum{0}%
+      %
+      % use \def rather than \let here because we redefine \chapentry et
+      % al. a second time, below.
+      \def\appentry{\numchapentry}%
+      \def\appsecentry{\numsecentry}%
+      \def\appsubsecentry{\numsubsecentry}%
+      \def\appsubsubsecentry{\numsubsubsecentry}%
+      \def\unnchapentry{\numchapentry}%
+      \def\unnsecentry{\numsecentry}%
+      \def\unnsubsecentry{\numsubsecentry}%
+      \def\unnsubsubsecentry{\numsubsubsecentry}%
+      \readdatafile{toc}%
+      %
+      % Read toc second time, this time actually producing the outlines.
+      % The `-' means take the \expnumber as the absolute number of
+      % subentries, which we calculated on our first read of the .toc above.
+      %
+      % We use the node names as the destinations.
+      \def\numchapentry##1##2##3##4{%
+        \dopdfoutline{##1}{count-\expnumber{chap##2}}{##3}{##4}}%
+      \def\numsecentry##1##2##3##4{%
+        \dopdfoutline{##1}{count-\expnumber{sec##2}}{##3}{##4}}%
+      \def\numsubsecentry##1##2##3##4{%
+        \dopdfoutline{##1}{count-\expnumber{subsec##2}}{##3}{##4}}%
+      \def\numsubsubsecentry##1##2##3##4{% count is always zero
+        \dopdfoutline{##1}{}{##3}{##4}}%
+      %
+      % PDF outlines are displayed using system fonts, instead of
+      % document fonts.  Therefore we cannot use special characters,
+      % since the encoding is unknown.  For example, the eogonek from
+      % Latin 2 (0xea) gets translated to a | character.  Info from
+      % Staszek Wawrykiewicz, 19 Jan 2004 04:09:24 +0100.
+      %
+      % xx to do this right, we have to translate 8-bit characters to
+      % their "best" equivalent, based on the @documentencoding.  Right
+      % now, I guess we'll just let the pdf reader have its way.
+      \indexnofonts
+      \setupdatafile
+      \catcode`\\=\active \otherbackslash
+      \input \tocreadfilename
+    \endgroup
+  }
+  %
+  \def\skipspaces#1{\def\PP{#1}\def\D{|}%
+    \ifx\PP\D\let\nextsp\relax
+    \else\let\nextsp\skipspaces
+      \ifx\p\space\else\addtokens{\filename}{\PP}%
+        \advance\filenamelength by 1
+      \fi
+    \fi
+    \nextsp}
+  \def\getfilename#1{\filenamelength=0\expandafter\skipspaces#1|\relax}
+  \ifnum\pdftexversion < 14
+    \let \startlink \pdfannotlink
+  \else
+    \let \startlink \pdfstartlink
+  \fi
+  % make a live url in pdf output.
+  \def\pdfurl#1{%
+    \begingroup
+      % it seems we really need yet another set of dummies; have not
+      % tried to figure out what each command should do in the context
+      % of @url.  for now, just make @/ a no-op, that's the only one
+      % people have actually reported a problem with.
+      %
+      \normalturnoffactive
+      \def\@{@}%
+      \let\/=\empty
+      \makevalueexpandable
+      \leavevmode\setcolor{\urlcolor}%
+      \startlink attr{/Border [0 0 0]}%
+        user{/Subtype /Link /A << /S /URI /URI (#1) >>}%
+    \endgroup}
+  \def\pdfgettoks#1.{\setbox\boxA=\hbox{\toksA={#1.}\toksB={}\maketoks}}
+  \def\addtokens#1#2{\edef\addtoks{\noexpand#1={\the#1#2}}\addtoks}
+  \def\adn#1{\addtokens{\toksC}{#1}\global\countA=1\let\next=\maketoks}
+  \def\poptoks#1#2|ENDTOKS|{\let\first=#1\toksD={#1}\toksA={#2}}
+  \def\maketoks{%
+    \expandafter\poptoks\the\toksA|ENDTOKS|\relax
+    \ifx\first0\adn0
+    \else\ifx\first1\adn1 \else\ifx\first2\adn2 \else\ifx\first3\adn3
+    \else\ifx\first4\adn4 \else\ifx\first5\adn5 \else\ifx\first6\adn6
+    \else\ifx\first7\adn7 \else\ifx\first8\adn8 \else\ifx\first9\adn9
+    \else
+      \ifnum0=\countA\else\makelink\fi
+      \ifx\first.\let\next=\done\else
+        \let\next=\maketoks
+        \addtokens{\toksB}{\the\toksD}
+        \ifx\first,\addtokens{\toksB}{\space}\fi
+      \fi
+    \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
+    \next}
+  \def\makelink{\addtokens{\toksB}%
+    {\noexpand\pdflink{\the\toksC}}\toksC={}\global\countA=0}
+  \def\pdflink#1{%
+    \startlink attr{/Border [0 0 0]} goto name{\pdfmkpgn{#1}}
+    \setcolor{\linkcolor}#1\endlink}
+  \def\done{\edef\st{\global\noexpand\toksA={\the\toksB}}\st}
+\else
+  \let\pdfmkdest = \gobble
+  \let\pdfurl = \gobble
+  \let\endlink = \relax
+  \let\setcolor = \gobble
+  \let\pdfsetcolor = \gobble
+  \let\pdfmakeoutlines = \relax
+\fi  % \ifx\pdfoutput
+
+
+\message{fonts,}
+
+% Change the current font style to #1, remembering it in \curfontstyle.
+% For now, we do not accumulate font styles: @b{@i{foo}} prints foo in
+% italics, not bold italics.
+%
+\def\setfontstyle#1{%
+  \def\curfontstyle{#1}% not as a control sequence, because we are \edef'd.
+  \csname ten#1\endcsname  % change the current font
+}
+
+% Select #1 fonts with the current style.
+%
+\def\selectfonts#1{\csname #1fonts\endcsname \csname\curfontstyle\endcsname}
+
+\def\rm{\fam=0 \setfontstyle{rm}}
+\def\it{\fam=\itfam \setfontstyle{it}}
+\def\sl{\fam=\slfam \setfontstyle{sl}}
+\def\bf{\fam=\bffam \setfontstyle{bf}}\def\bfstylename{bf}
+\def\tt{\fam=\ttfam \setfontstyle{tt}}
+
+% Texinfo sort of supports the sans serif font style, which plain TeX does not.
+% So we set up a \sf.
+\newfam\sffam
+\def\sf{\fam=\sffam \setfontstyle{sf}}
+\let\li = \sf % Sometimes we call it \li, not \sf.
+
+% We don't need math for this font style.
+\def\ttsl{\setfontstyle{ttsl}}
+
+
+% Default leading.
+\newdimen\textleading  \textleading = 13.2pt
+
+% Set the baselineskip to #1, and the lineskip and strut size
+% correspondingly.  There is no deep meaning behind these magic numbers
+% used as factors; they just match (closely enough) what Knuth defined.
+%
+\def\lineskipfactor{.08333}
+\def\strutheightpercent{.70833}
+\def\strutdepthpercent {.29167}
+%
+% can get a sort of poor man's double spacing by redefining this.
+\def\baselinefactor{1}
+%
+\def\setleading#1{%
+  \dimen0 = #1\relax
+  \normalbaselineskip = \baselinefactor\dimen0
+  \normallineskip = \lineskipfactor\normalbaselineskip
+  \normalbaselines
+  \setbox\strutbox =\hbox{%
+    \vrule width0pt height\strutheightpercent\baselineskip
+                    depth \strutdepthpercent \baselineskip
+  }%
+}
+
+% PDF CMaps.  See also LaTeX's t1.cmap.
+%
+% do nothing with this by default.
+\expandafter\let\csname cmapOT1\endcsname\gobble
+\expandafter\let\csname cmapOT1IT\endcsname\gobble
+\expandafter\let\csname cmapOT1TT\endcsname\gobble
+
+% if we are producing pdf, and we have \pdffontattr, then define cmaps.
+% (\pdffontattr was introduced many years ago, but people still run
+% older pdftex's; it's easy to conditionalize, so we do.)
+\ifpdf \ifx\pdffontattr\undefined \else
+  \begingroup
+    \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char.
+    \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap
+%%DocumentNeededResources: ProcSet (CIDInit)
+%%IncludeResource: ProcSet (CIDInit)
+%%BeginResource: CMap (TeX-OT1-0)
+%%Title: (TeX-OT1-0 TeX OT1 0)
+%%Version: 1.000
+%%EndComments
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<< /Registry (TeX)
+/Ordering (OT1)
+/Supplement 0
+>> def
+/CMapName /TeX-OT1-0 def
+/CMapType 2 def
+1 begincodespacerange
+<00> <7F>
+endcodespacerange
+8 beginbfrange
+<00> <01> <0393>
+<09> <0A> <03A8>
+<23> <26> <0023>
+<28> <3B> <0028>
+<3F> <5B> <003F>
+<5D> <5E> <005D>
+<61> <7A> <0061>
+<7B> <7C> <2013>
+endbfrange
+40 beginbfchar
+<02> <0398>
+<03> <039B>
+<04> <039E>
+<05> <03A0>
+<06> <03A3>
+<07> <03D2>
+<08> <03A6>
+<0B> <00660066>
+<0C> <00660069>
+<0D> <0066006C>
+<0E> <006600660069>
+<0F> <00660066006C>
+<10> <0131>
+<11> <0237>
+<12> <0060>
+<13> <00B4>
+<14> <02C7>
+<15> <02D8>
+<16> <00AF>
+<17> <02DA>
+<18> <00B8>
+<19> <00DF>
+<1A> <00E6>
+<1B> <0153>
+<1C> <00F8>
+<1D> <00C6>
+<1E> <0152>
+<1F> <00D8>
+<21> <0021>
+<22> <201D>
+<27> <2019>
+<3C> <00A1>
+<3D> <003D>
+<3E> <00BF>
+<5C> <201C>
+<5F> <02D9>
+<60> <2018>
+<7D> <02DD>
+<7E> <007E>
+<7F> <00A8>
+endbfchar
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+%%EndResource
+%%EOF
+    }\endgroup
+  \expandafter\edef\csname cmapOT1\endcsname#1{%
+    \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}%
+  }%
+%
+% \cmapOT1IT
+  \begingroup
+    \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char.
+    \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap
+%%DocumentNeededResources: ProcSet (CIDInit)
+%%IncludeResource: ProcSet (CIDInit)
+%%BeginResource: CMap (TeX-OT1IT-0)
+%%Title: (TeX-OT1IT-0 TeX OT1IT 0)
+%%Version: 1.000
+%%EndComments
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<< /Registry (TeX)
+/Ordering (OT1IT)
+/Supplement 0
+>> def
+/CMapName /TeX-OT1IT-0 def
+/CMapType 2 def
+1 begincodespacerange
+<00> <7F>
+endcodespacerange
+8 beginbfrange
+<00> <01> <0393>
+<09> <0A> <03A8>
+<25> <26> <0025>
+<28> <3B> <0028>
+<3F> <5B> <003F>
+<5D> <5E> <005D>
+<61> <7A> <0061>
+<7B> <7C> <2013>
+endbfrange
+42 beginbfchar
+<02> <0398>
+<03> <039B>
+<04> <039E>
+<05> <03A0>
+<06> <03A3>
+<07> <03D2>
+<08> <03A6>
+<0B> <00660066>
+<0C> <00660069>
+<0D> <0066006C>
+<0E> <006600660069>
+<0F> <00660066006C>
+<10> <0131>
+<11> <0237>
+<12> <0060>
+<13> <00B4>
+<14> <02C7>
+<15> <02D8>
+<16> <00AF>
+<17> <02DA>
+<18> <00B8>
+<19> <00DF>
+<1A> <00E6>
+<1B> <0153>
+<1C> <00F8>
+<1D> <00C6>
+<1E> <0152>
+<1F> <00D8>
+<21> <0021>
+<22> <201D>
+<23> <0023>
+<24> <00A3>
+<27> <2019>
+<3C> <00A1>
+<3D> <003D>
+<3E> <00BF>
+<5C> <201C>
+<5F> <02D9>
+<60> <2018>
+<7D> <02DD>
+<7E> <007E>
+<7F> <00A8>
+endbfchar
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+%%EndResource
+%%EOF
+    }\endgroup
+  \expandafter\edef\csname cmapOT1IT\endcsname#1{%
+    \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}%
+  }%
+%
+% \cmapOT1TT
+  \begingroup
+    \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char.
+    \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap
+%%DocumentNeededResources: ProcSet (CIDInit)
+%%IncludeResource: ProcSet (CIDInit)
+%%BeginResource: CMap (TeX-OT1TT-0)
+%%Title: (TeX-OT1TT-0 TeX OT1TT 0)
+%%Version: 1.000
+%%EndComments
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<< /Registry (TeX)
+/Ordering (OT1TT)
+/Supplement 0
+>> def
+/CMapName /TeX-OT1TT-0 def
+/CMapType 2 def
+1 begincodespacerange
+<00> <7F>
+endcodespacerange
+5 beginbfrange
+<00> <01> <0393>
+<09> <0A> <03A8>
+<21> <26> <0021>
+<28> <5F> <0028>
+<61> <7E> <0061>
+endbfrange
+32 beginbfchar
+<02> <0398>
+<03> <039B>
+<04> <039E>
+<05> <03A0>
+<06> <03A3>
+<07> <03D2>
+<08> <03A6>
+<0B> <2191>
+<0C> <2193>
+<0D> <0027>
+<0E> <00A1>
+<0F> <00BF>
+<10> <0131>
+<11> <0237>
+<12> <0060>
+<13> <00B4>
+<14> <02C7>
+<15> <02D8>
+<16> <00AF>
+<17> <02DA>
+<18> <00B8>
+<19> <00DF>
+<1A> <00E6>
+<1B> <0153>
+<1C> <00F8>
+<1D> <00C6>
+<1E> <0152>
+<1F> <00D8>
+<20> <2423>
+<27> <2019>
+<60> <2018>
+<7F> <00A8>
+endbfchar
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+%%EndResource
+%%EOF
+    }\endgroup
+  \expandafter\edef\csname cmapOT1TT\endcsname#1{%
+    \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}%
+  }%
+\fi\fi
+
+
+% Set the font macro #1 to the font named #2, adding on the
+% specified font prefix (normally `cm').
+% #3 is the font's design size, #4 is a scale factor, #5 is the CMap
+% encoding (currently only OT1, OT1IT and OT1TT are allowed, pass
+% empty to omit).
+\def\setfont#1#2#3#4#5{%
+  \font#1=\fontprefix#2#3 scaled #4
+  \csname cmap#5\endcsname#1%
+}
+% This is what gets called when #5 of \setfont is empty.
+\let\cmap\gobble
+% emacs-page end of cmaps
+
+% Use cm as the default font prefix.
+% To specify the font prefix, you must define \fontprefix
+% before you read in texinfo.tex.
+\ifx\fontprefix\undefined
+\def\fontprefix{cm}
+\fi
+% Support font families that don't use the same naming scheme as CM.
+\def\rmshape{r}
+\def\rmbshape{bx}               %where the normal face is bold
+\def\bfshape{b}
+\def\bxshape{bx}
+\def\ttshape{tt}
+\def\ttbshape{tt}
+\def\ttslshape{sltt}
+\def\itshape{ti}
+\def\itbshape{bxti}
+\def\slshape{sl}
+\def\slbshape{bxsl}
+\def\sfshape{ss}
+\def\sfbshape{ss}
+\def\scshape{csc}
+\def\scbshape{csc}
+
+% Definitions for a main text size of 11pt.  This is the default in
+% Texinfo.
+%
+\def\definetextfontsizexi{%
+% Text fonts (11.2pt, magstep1).
+\def\textnominalsize{11pt}
+\edef\mainmagstep{\magstephalf}
+\setfont\textrm\rmshape{10}{\mainmagstep}{OT1}
+\setfont\texttt\ttshape{10}{\mainmagstep}{OT1TT}
+\setfont\textbf\bfshape{10}{\mainmagstep}{OT1}
+\setfont\textit\itshape{10}{\mainmagstep}{OT1IT}
+\setfont\textsl\slshape{10}{\mainmagstep}{OT1}
+\setfont\textsf\sfshape{10}{\mainmagstep}{OT1}
+\setfont\textsc\scshape{10}{\mainmagstep}{OT1}
+\setfont\textttsl\ttslshape{10}{\mainmagstep}{OT1TT}
+\font\texti=cmmi10 scaled \mainmagstep
+\font\textsy=cmsy10 scaled \mainmagstep
+\def\textecsize{1095}
+
+% A few fonts for @defun names and args.
+\setfont\defbf\bfshape{10}{\magstep1}{OT1}
+\setfont\deftt\ttshape{10}{\magstep1}{OT1TT}
+\setfont\defttsl\ttslshape{10}{\magstep1}{OT1TT}
+\def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf}
+
+% Fonts for indices, footnotes, small examples (9pt).
+\def\smallnominalsize{9pt}
+\setfont\smallrm\rmshape{9}{1000}{OT1}
+\setfont\smalltt\ttshape{9}{1000}{OT1TT}
+\setfont\smallbf\bfshape{10}{900}{OT1}
+\setfont\smallit\itshape{9}{1000}{OT1IT}
+\setfont\smallsl\slshape{9}{1000}{OT1}
+\setfont\smallsf\sfshape{9}{1000}{OT1}
+\setfont\smallsc\scshape{10}{900}{OT1}
+\setfont\smallttsl\ttslshape{10}{900}{OT1TT}
+\font\smalli=cmmi9
+\font\smallsy=cmsy9
+\def\smallecsize{0900}
+
+% Fonts for small examples (8pt).
+\def\smallernominalsize{8pt}
+\setfont\smallerrm\rmshape{8}{1000}{OT1}
+\setfont\smallertt\ttshape{8}{1000}{OT1TT}
+\setfont\smallerbf\bfshape{10}{800}{OT1}
+\setfont\smallerit\itshape{8}{1000}{OT1IT}
+\setfont\smallersl\slshape{8}{1000}{OT1}
+\setfont\smallersf\sfshape{8}{1000}{OT1}
+\setfont\smallersc\scshape{10}{800}{OT1}
+\setfont\smallerttsl\ttslshape{10}{800}{OT1TT}
+\font\smalleri=cmmi8
+\font\smallersy=cmsy8
+\def\smallerecsize{0800}
+
+% Fonts for title page (20.4pt):
+\def\titlenominalsize{20pt}
+\setfont\titlerm\rmbshape{12}{\magstep3}{OT1}
+\setfont\titleit\itbshape{10}{\magstep4}{OT1IT}
+\setfont\titlesl\slbshape{10}{\magstep4}{OT1}
+\setfont\titlett\ttbshape{12}{\magstep3}{OT1TT}
+\setfont\titlettsl\ttslshape{10}{\magstep4}{OT1TT}
+\setfont\titlesf\sfbshape{17}{\magstep1}{OT1}
+\let\titlebf=\titlerm
+\setfont\titlesc\scbshape{10}{\magstep4}{OT1}
+\font\titlei=cmmi12 scaled \magstep3
+\font\titlesy=cmsy10 scaled \magstep4
+\def\authorrm{\secrm}
+\def\authortt{\sectt}
+\def\titleecsize{2074}
+
+% Chapter (and unnumbered) fonts (17.28pt).
+\def\chapnominalsize{17pt}
+\setfont\chaprm\rmbshape{12}{\magstep2}{OT1}
+\setfont\chapit\itbshape{10}{\magstep3}{OT1IT}
+\setfont\chapsl\slbshape{10}{\magstep3}{OT1}
+\setfont\chaptt\ttbshape{12}{\magstep2}{OT1TT}
+\setfont\chapttsl\ttslshape{10}{\magstep3}{OT1TT}
+\setfont\chapsf\sfbshape{17}{1000}{OT1}
+\let\chapbf=\chaprm
+\setfont\chapsc\scbshape{10}{\magstep3}{OT1}
+\font\chapi=cmmi12 scaled \magstep2
+\font\chapsy=cmsy10 scaled \magstep3
+\def\chapecsize{1728}
+
+% Section fonts (14.4pt).
+\def\secnominalsize{14pt}
+\setfont\secrm\rmbshape{12}{\magstep1}{OT1}
+\setfont\secit\itbshape{10}{\magstep2}{OT1IT}
+\setfont\secsl\slbshape{10}{\magstep2}{OT1}
+\setfont\sectt\ttbshape{12}{\magstep1}{OT1TT}
+\setfont\secttsl\ttslshape{10}{\magstep2}{OT1TT}
+\setfont\secsf\sfbshape{12}{\magstep1}{OT1}
+\let\secbf\secrm
+\setfont\secsc\scbshape{10}{\magstep2}{OT1}
+\font\seci=cmmi12 scaled \magstep1
+\font\secsy=cmsy10 scaled \magstep2
+\def\sececsize{1440}
+
+% Subsection fonts (13.15pt).
+\def\ssecnominalsize{13pt}
+\setfont\ssecrm\rmbshape{12}{\magstephalf}{OT1}
+\setfont\ssecit\itbshape{10}{1315}{OT1IT}
+\setfont\ssecsl\slbshape{10}{1315}{OT1}
+\setfont\ssectt\ttbshape{12}{\magstephalf}{OT1TT}
+\setfont\ssecttsl\ttslshape{10}{1315}{OT1TT}
+\setfont\ssecsf\sfbshape{12}{\magstephalf}{OT1}
+\let\ssecbf\ssecrm
+\setfont\ssecsc\scbshape{10}{1315}{OT1}
+\font\sseci=cmmi12 scaled \magstephalf
+\font\ssecsy=cmsy10 scaled 1315
+\def\ssececsize{1200}
+
+% Reduced fonts for @acro in text (10pt).
+\def\reducednominalsize{10pt}
+\setfont\reducedrm\rmshape{10}{1000}{OT1}
+\setfont\reducedtt\ttshape{10}{1000}{OT1TT}
+\setfont\reducedbf\bfshape{10}{1000}{OT1}
+\setfont\reducedit\itshape{10}{1000}{OT1IT}
+\setfont\reducedsl\slshape{10}{1000}{OT1}
+\setfont\reducedsf\sfshape{10}{1000}{OT1}
+\setfont\reducedsc\scshape{10}{1000}{OT1}
+\setfont\reducedttsl\ttslshape{10}{1000}{OT1TT}
+\font\reducedi=cmmi10
+\font\reducedsy=cmsy10
+\def\reducedecsize{1000}
+
+% reset the current fonts
+\textfonts
+\rm
+} % end of 11pt text font size definitions
+
+
+% Definitions to make the main text be 10pt Computer Modern, with
+% section, chapter, etc., sizes following suit.  This is for the GNU
+% Press printing of the Emacs 22 manual.  Maybe other manuals in the
+% future.  Used with @smallbook, which sets the leading to 12pt.
+%
+\def\definetextfontsizex{%
+% Text fonts (10pt).
+\def\textnominalsize{10pt}
+\edef\mainmagstep{1000}
+\setfont\textrm\rmshape{10}{\mainmagstep}{OT1}
+\setfont\texttt\ttshape{10}{\mainmagstep}{OT1TT}
+\setfont\textbf\bfshape{10}{\mainmagstep}{OT1}
+\setfont\textit\itshape{10}{\mainmagstep}{OT1IT}
+\setfont\textsl\slshape{10}{\mainmagstep}{OT1}
+\setfont\textsf\sfshape{10}{\mainmagstep}{OT1}
+\setfont\textsc\scshape{10}{\mainmagstep}{OT1}
+\setfont\textttsl\ttslshape{10}{\mainmagstep}{OT1TT}
+\font\texti=cmmi10 scaled \mainmagstep
+\font\textsy=cmsy10 scaled \mainmagstep
+\def\textecsize{1000}
+
+% A few fonts for @defun names and args.
+\setfont\defbf\bfshape{10}{\magstephalf}{OT1}
+\setfont\deftt\ttshape{10}{\magstephalf}{OT1TT}
+\setfont\defttsl\ttslshape{10}{\magstephalf}{OT1TT}
+\def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf}
+
+% Fonts for indices, footnotes, small examples (9pt).
+\def\smallnominalsize{9pt}
+\setfont\smallrm\rmshape{9}{1000}{OT1}
+\setfont\smalltt\ttshape{9}{1000}{OT1TT}
+\setfont\smallbf\bfshape{10}{900}{OT1}
+\setfont\smallit\itshape{9}{1000}{OT1IT}
+\setfont\smallsl\slshape{9}{1000}{OT1}
+\setfont\smallsf\sfshape{9}{1000}{OT1}
+\setfont\smallsc\scshape{10}{900}{OT1}
+\setfont\smallttsl\ttslshape{10}{900}{OT1TT}
+\font\smalli=cmmi9
+\font\smallsy=cmsy9
+\def\smallecsize{0900}
+
+% Fonts for small examples (8pt).
+\def\smallernominalsize{8pt}
+\setfont\smallerrm\rmshape{8}{1000}{OT1}
+\setfont\smallertt\ttshape{8}{1000}{OT1TT}
+\setfont\smallerbf\bfshape{10}{800}{OT1}
+\setfont\smallerit\itshape{8}{1000}{OT1IT}
+\setfont\smallersl\slshape{8}{1000}{OT1}
+\setfont\smallersf\sfshape{8}{1000}{OT1}
+\setfont\smallersc\scshape{10}{800}{OT1}
+\setfont\smallerttsl\ttslshape{10}{800}{OT1TT}
+\font\smalleri=cmmi8
+\font\smallersy=cmsy8
+\def\smallerecsize{0800}
+
+% Fonts for title page (20.4pt):
+\def\titlenominalsize{20pt}
+\setfont\titlerm\rmbshape{12}{\magstep3}{OT1}
+\setfont\titleit\itbshape{10}{\magstep4}{OT1IT}
+\setfont\titlesl\slbshape{10}{\magstep4}{OT1}
+\setfont\titlett\ttbshape{12}{\magstep3}{OT1TT}
+\setfont\titlettsl\ttslshape{10}{\magstep4}{OT1TT}
+\setfont\titlesf\sfbshape{17}{\magstep1}{OT1}
+\let\titlebf=\titlerm
+\setfont\titlesc\scbshape{10}{\magstep4}{OT1}
+\font\titlei=cmmi12 scaled \magstep3
+\font\titlesy=cmsy10 scaled \magstep4
+\def\authorrm{\secrm}
+\def\authortt{\sectt}
+\def\titleecsize{2074}
+
+% Chapter fonts (14.4pt).
+\def\chapnominalsize{14pt}
+\setfont\chaprm\rmbshape{12}{\magstep1}{OT1}
+\setfont\chapit\itbshape{10}{\magstep2}{OT1IT}
+\setfont\chapsl\slbshape{10}{\magstep2}{OT1}
+\setfont\chaptt\ttbshape{12}{\magstep1}{OT1TT}
+\setfont\chapttsl\ttslshape{10}{\magstep2}{OT1TT}
+\setfont\chapsf\sfbshape{12}{\magstep1}{OT1}
+\let\chapbf\chaprm
+\setfont\chapsc\scbshape{10}{\magstep2}{OT1}
+\font\chapi=cmmi12 scaled \magstep1
+\font\chapsy=cmsy10 scaled \magstep2
+\def\chapecsize{1440}
+
+% Section fonts (12pt).
+\def\secnominalsize{12pt}
+\setfont\secrm\rmbshape{12}{1000}{OT1}
+\setfont\secit\itbshape{10}{\magstep1}{OT1IT}
+\setfont\secsl\slbshape{10}{\magstep1}{OT1}
+\setfont\sectt\ttbshape{12}{1000}{OT1TT}
+\setfont\secttsl\ttslshape{10}{\magstep1}{OT1TT}
+\setfont\secsf\sfbshape{12}{1000}{OT1}
+\let\secbf\secrm
+\setfont\secsc\scbshape{10}{\magstep1}{OT1}
+\font\seci=cmmi12
+\font\secsy=cmsy10 scaled \magstep1
+\def\sececsize{1200}
+
+% Subsection fonts (10pt).
+\def\ssecnominalsize{10pt}
+\setfont\ssecrm\rmbshape{10}{1000}{OT1}
+\setfont\ssecit\itbshape{10}{1000}{OT1IT}
+\setfont\ssecsl\slbshape{10}{1000}{OT1}
+\setfont\ssectt\ttbshape{10}{1000}{OT1TT}
+\setfont\ssecttsl\ttslshape{10}{1000}{OT1TT}
+\setfont\ssecsf\sfbshape{10}{1000}{OT1}
+\let\ssecbf\ssecrm
+\setfont\ssecsc\scbshape{10}{1000}{OT1}
+\font\sseci=cmmi10
+\font\ssecsy=cmsy10
+\def\ssececsize{1000}
+
+% Reduced fonts for @acro in text (9pt).
+\def\reducednominalsize{9pt}
+\setfont\reducedrm\rmshape{9}{1000}{OT1}
+\setfont\reducedtt\ttshape{9}{1000}{OT1TT}
+\setfont\reducedbf\bfshape{10}{900}{OT1}
+\setfont\reducedit\itshape{9}{1000}{OT1IT}
+\setfont\reducedsl\slshape{9}{1000}{OT1}
+\setfont\reducedsf\sfshape{9}{1000}{OT1}
+\setfont\reducedsc\scshape{10}{900}{OT1}
+\setfont\reducedttsl\ttslshape{10}{900}{OT1TT}
+\font\reducedi=cmmi9
+\font\reducedsy=cmsy9
+\def\reducedecsize{0900}
+
+% reduce space between paragraphs
+\divide\parskip by 2
+
+% reset the current fonts
+\textfonts
+\rm
+} % end of 10pt text font size definitions
+
+
+% We provide the user-level command
+%   @fonttextsize 10
+% (or 11) to redefine the text font size.  pt is assumed.
+%
+\def\xword{10}
+\def\xiword{11}
+%
+\parseargdef\fonttextsize{%
+  \def\textsizearg{#1}%
+  \wlog{doing @fonttextsize \textsizearg}%
+  %
+  % Set \globaldefs so that documents can use this inside @tex, since
+  % makeinfo 4.8 does not support it, but we need it nonetheless.
+  %
+ \begingroup \globaldefs=1
+  \ifx\textsizearg\xword \definetextfontsizex
+  \else \ifx\textsizearg\xiword \definetextfontsizexi
+  \else
+    \errhelp=\EMsimple
+    \errmessage{@fonttextsize only supports `10' or `11', not `\textsizearg'}
+  \fi\fi
+ \endgroup
+}
+
+
+% In order for the font changes to affect most math symbols and letters,
+% we have to define the \textfont of the standard families.  Since
+% texinfo doesn't allow for producing subscripts and superscripts except
+% in the main text, we don't bother to reset \scriptfont and
+% \scriptscriptfont (which would also require loading a lot more fonts).
+%
+\def\resetmathfonts{%
+  \textfont0=\tenrm \textfont1=\teni \textfont2=\tensy
+  \textfont\itfam=\tenit \textfont\slfam=\tensl \textfont\bffam=\tenbf
+  \textfont\ttfam=\tentt \textfont\sffam=\tensf
+}
+
+% The font-changing commands redefine the meanings of \tenSTYLE, instead
+% of just \STYLE.  We do this because \STYLE needs to also set the
+% current \fam for math mode.  Our \STYLE (e.g., \rm) commands hardwire
+% \tenSTYLE to set the current font.
+%
+% Each font-changing command also sets the names \lsize (one size lower)
+% and \lllsize (three sizes lower).  These relative commands are used in
+% the LaTeX logo and acronyms.
+%
+% This all needs generalizing, badly.
+%
+\def\textfonts{%
+  \let\tenrm=\textrm \let\tenit=\textit \let\tensl=\textsl
+  \let\tenbf=\textbf \let\tentt=\texttt \let\smallcaps=\textsc
+  \let\tensf=\textsf \let\teni=\texti \let\tensy=\textsy
+  \let\tenttsl=\textttsl
+  \def\curfontsize{text}%
+  \def\lsize{reduced}\def\lllsize{smaller}%
+  \resetmathfonts \setleading{\textleading}}
+\def\titlefonts{%
+  \let\tenrm=\titlerm \let\tenit=\titleit \let\tensl=\titlesl
+  \let\tenbf=\titlebf \let\tentt=\titlett \let\smallcaps=\titlesc
+  \let\tensf=\titlesf \let\teni=\titlei \let\tensy=\titlesy
+  \let\tenttsl=\titlettsl
+  \def\curfontsize{title}%
+  \def\lsize{chap}\def\lllsize{subsec}%
+  \resetmathfonts \setleading{25pt}}
+\def\titlefont#1{{\titlefonts\rm #1}}
+\def\chapfonts{%
+  \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl
+  \let\tenbf=\chapbf \let\tentt=\chaptt \let\smallcaps=\chapsc
+  \let\tensf=\chapsf \let\teni=\chapi \let\tensy=\chapsy
+  \let\tenttsl=\chapttsl
+  \def\curfontsize{chap}%
+  \def\lsize{sec}\def\lllsize{text}%
+  \resetmathfonts \setleading{19pt}}
+\def\secfonts{%
+  \let\tenrm=\secrm \let\tenit=\secit \let\tensl=\secsl
+  \let\tenbf=\secbf \let\tentt=\sectt \let\smallcaps=\secsc
+  \let\tensf=\secsf \let\teni=\seci \let\tensy=\secsy
+  \let\tenttsl=\secttsl
+  \def\curfontsize{sec}%
+  \def\lsize{subsec}\def\lllsize{reduced}%
+  \resetmathfonts \setleading{16pt}}
+\def\subsecfonts{%
+  \let\tenrm=\ssecrm \let\tenit=\ssecit \let\tensl=\ssecsl
+  \let\tenbf=\ssecbf \let\tentt=\ssectt \let\smallcaps=\ssecsc
+  \let\tensf=\ssecsf \let\teni=\sseci \let\tensy=\ssecsy
+  \let\tenttsl=\ssecttsl
+  \def\curfontsize{ssec}%
+  \def\lsize{text}\def\lllsize{small}%
+  \resetmathfonts \setleading{15pt}}
+\let\subsubsecfonts = \subsecfonts
+\def\reducedfonts{%
+  \let\tenrm=\reducedrm \let\tenit=\reducedit \let\tensl=\reducedsl
+  \let\tenbf=\reducedbf \let\tentt=\reducedtt \let\reducedcaps=\reducedsc
+  \let\tensf=\reducedsf \let\teni=\reducedi \let\tensy=\reducedsy
+  \let\tenttsl=\reducedttsl
+  \def\curfontsize{reduced}%
+  \def\lsize{small}\def\lllsize{smaller}%
+  \resetmathfonts \setleading{10.5pt}}
+\def\smallfonts{%
+  \let\tenrm=\smallrm \let\tenit=\smallit \let\tensl=\smallsl
+  \let\tenbf=\smallbf \let\tentt=\smalltt \let\smallcaps=\smallsc
+  \let\tensf=\smallsf \let\teni=\smalli \let\tensy=\smallsy
+  \let\tenttsl=\smallttsl
+  \def\curfontsize{small}%
+  \def\lsize{smaller}\def\lllsize{smaller}%
+  \resetmathfonts \setleading{10.5pt}}
+\def\smallerfonts{%
+  \let\tenrm=\smallerrm \let\tenit=\smallerit \let\tensl=\smallersl
+  \let\tenbf=\smallerbf \let\tentt=\smallertt \let\smallcaps=\smallersc
+  \let\tensf=\smallersf \let\teni=\smalleri \let\tensy=\smallersy
+  \let\tenttsl=\smallerttsl
+  \def\curfontsize{smaller}%
+  \def\lsize{smaller}\def\lllsize{smaller}%
+  \resetmathfonts \setleading{9.5pt}}
+
+% Set the fonts to use with the @small... environments.
+\let\smallexamplefonts = \smallfonts
+
+% About \smallexamplefonts.  If we use \smallfonts (9pt), @smallexample
+% can fit this many characters:
+%   8.5x11=86   smallbook=72  a4=90  a5=69
+% If we use \scriptfonts (8pt), then we can fit this many characters:
+%   8.5x11=90+  smallbook=80  a4=90+  a5=77
+% For me, subjectively, the few extra characters that fit aren't worth
+% the additional smallness of 8pt.  So I'm making the default 9pt.
+%
+% By the way, for comparison, here's what fits with @example (10pt):
+%   8.5x11=71  smallbook=60  a4=75  a5=58
+%
+% I wish the USA used A4 paper.
+% --karl, 24jan03.
+
+
+% Set up the default fonts, so we can use them for creating boxes.
+%
+\definetextfontsizexi
+
+% Define these so they can be easily changed for other fonts.
+\def\angleleft{$\langle$}
+\def\angleright{$\rangle$}
+
+% Count depth in font-changes, for error checks
+\newcount\fontdepth \fontdepth=0
+
+% Fonts for short table of contents.
+\setfont\shortcontrm\rmshape{12}{1000}{OT1}
+\setfont\shortcontbf\bfshape{10}{\magstep1}{OT1}  % no cmb12
+\setfont\shortcontsl\slshape{12}{1000}{OT1}
+\setfont\shortconttt\ttshape{12}{1000}{OT1TT}
+
+%% Add scribe-like font environments, plus @l for inline lisp (usually sans
+%% serif) and @ii for TeX italic
+
+% \smartitalic{ARG} outputs arg in italics, followed by an italic correction
+% unless the following character is such as not to need one.
+\def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else
+                    \ptexslash\fi\fi\fi}
+\def\smartslanted#1{{\ifusingtt\ttsl\sl #1}\futurelet\next\smartitalicx}
+\def\smartitalic#1{{\ifusingtt\ttsl\it #1}\futurelet\next\smartitalicx}
+
+% like \smartslanted except unconditionally uses \ttsl.
+% @var is set to this for defun arguments.
+\def\ttslanted#1{{\ttsl #1}\futurelet\next\smartitalicx}
+
+% like \smartslanted except unconditionally use \sl.  We never want
+% ttsl for book titles, do we?
+\def\cite#1{{\sl #1}\futurelet\next\smartitalicx}
+
+\let\i=\smartitalic
+\let\slanted=\smartslanted
+\let\var=\smartslanted
+\let\dfn=\smartslanted
+\let\emph=\smartitalic
+
+% @b, explicit bold.
+\def\b#1{{\bf #1}}
+\let\strong=\b
+
+% @sansserif, explicit sans.
+\def\sansserif#1{{\sf #1}}
+
+% We can't just use \exhyphenpenalty, because that only has effect at
+% the end of a paragraph.  Restore normal hyphenation at the end of the
+% group within which \nohyphenation is presumably called.
+%
+\def\nohyphenation{\hyphenchar\font = -1  \aftergroup\restorehyphenation}
+\def\restorehyphenation{\hyphenchar\font = `- }
+
+% Set sfcode to normal for the chars that usually have another value.
+% Can't use plain's \frenchspacing because it uses the `\x notation, and
+% sometimes \x has an active definition that messes things up.
+%
+\catcode`@=11
+  \def\plainfrenchspacing{%
+    \sfcode\dotChar  =\@m \sfcode\questChar=\@m \sfcode\exclamChar=\@m
+    \sfcode\colonChar=\@m \sfcode\semiChar =\@m \sfcode\commaChar =\@m
+    \def\endofsentencespacefactor{1000}% for @. and friends
+  }
+  \def\plainnonfrenchspacing{%
+    \sfcode`\.3000\sfcode`\?3000\sfcode`\!3000
+    \sfcode`\:2000\sfcode`\;1500\sfcode`\,1250
+    \def\endofsentencespacefactor{3000}% for @. and friends
+  }
+\catcode`@=\other
+\def\endofsentencespacefactor{3000}% default
+
+\def\t#1{%
+  {\tt \rawbackslash \plainfrenchspacing #1}%
+  \null
+}
+\def\samp#1{`\tclose{#1}'\null}
+\setfont\keyrm\rmshape{8}{1000}{OT1}
+\font\keysy=cmsy9
+\def\key#1{{\keyrm\textfont2=\keysy \leavevmode\hbox{%
+  \raise0.4pt\hbox{\angleleft}\kern-.08em\vtop{%
+    \vbox{\hrule\kern-0.4pt
+     \hbox{\raise0.4pt\hbox{\vphantom{\angleleft}}#1}}%
+    \kern-0.4pt\hrule}%
+  \kern-.06em\raise0.4pt\hbox{\angleright}}}}
+\def\key #1{{\nohyphenation \uppercase{#1}}\null}
+% The old definition, with no lozenge:
+%\def\key #1{{\ttsl \nohyphenation \uppercase{#1}}\null}
+\def\ctrl #1{{\tt \rawbackslash \hat}#1}
+
+% @file, @option are the same as @samp.
+\let\file=\samp
+\let\option=\samp
+
+% @code is a modification of @t,
+% which makes spaces the same size as normal in the surrounding text.
+\def\tclose#1{%
+  {%
+    % Change normal interword space to be same as for the current font.
+    \spaceskip = \fontdimen2\font
+    %
+    % Switch to typewriter.
+    \tt
+    %
+    % But `\ ' produces the large typewriter interword space.
+    \def\ {{\spaceskip = 0pt{} }}%
+    %
+    % Turn off hyphenation.
+    \nohyphenation
+    %
+    \rawbackslash
+    \plainfrenchspacing
+    #1%
+  }%
+  \null
+}
+
+% We *must* turn on hyphenation at `-' and `_' in @code.
+% Otherwise, it is too hard to avoid overfull hboxes
+% in the Emacs manual, the Library manual, etc.
+
+% Unfortunately, TeX uses one parameter (\hyphenchar) to control
+% both hyphenation at - and hyphenation within words.
+% We must therefore turn them both off (\tclose does that)
+% and arrange explicitly to hyphenate at a dash.
+%  -- rms.
+{
+  \catcode`\-=\active \catcode`\_=\active
+  \catcode`\'=\active \catcode`\`=\active
+  %
+  \global\def\code{\begingroup
+    \catcode\rquoteChar=\active \catcode\lquoteChar=\active
+    \let'\codequoteright \let`\codequoteleft
+    %
+    \catcode\dashChar=\active  \catcode\underChar=\active
+    \ifallowcodebreaks
+     \let-\codedash
+     \let_\codeunder
+    \else
+     \let-\realdash
+     \let_\realunder
+    \fi
+    \codex
+  }
+}
+
+\def\realdash{-}
+\def\codedash{-\discretionary{}{}{}}
+\def\codeunder{%
+  % this is all so @math{@code{var_name}+1} can work.  In math mode, _
+  % is "active" (mathcode"8000) and \normalunderscore (or \char95, etc.)
+  % will therefore expand the active definition of _, which is us
+  % (inside @code that is), therefore an endless loop.
+  \ifusingtt{\ifmmode
+               \mathchar"075F % class 0=ordinary, family 7=ttfam, pos 0x5F=_.
+             \else\normalunderscore \fi
+             \discretionary{}{}{}}%
+            {\_}%
+}
+\def\codex #1{\tclose{#1}\endgroup}
+
+% An additional complication: the above will allow breaks after, e.g.,
+% each of the four underscores in __typeof__.  This is undesirable in
+% some manuals, especially if they don't have long identifiers in
+% general.  @allowcodebreaks provides a way to control this.
+%
+\newif\ifallowcodebreaks  \allowcodebreakstrue
+
+\def\keywordtrue{true}
+\def\keywordfalse{false}
+
+\parseargdef\allowcodebreaks{%
+  \def\txiarg{#1}%
+  \ifx\txiarg\keywordtrue
+    \allowcodebreakstrue
+  \else\ifx\txiarg\keywordfalse
+    \allowcodebreaksfalse
+  \else
+    \errhelp = \EMsimple
+    \errmessage{Unknown @allowcodebreaks option `\txiarg'}%
+  \fi\fi
+}
+
+% @kbd is like @code, except that if the argument is just one @key command,
+% then @kbd has no effect.
+
+% @kbdinputstyle -- arg is `distinct' (@kbd uses slanted tty font always),
+%   `example' (@kbd uses ttsl only inside of @example and friends),
+%   or `code' (@kbd uses normal tty font always).
+\parseargdef\kbdinputstyle{%
+  \def\txiarg{#1}%
+  \ifx\txiarg\worddistinct
+    \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\ttsl}%
+  \else\ifx\txiarg\wordexample
+    \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\tt}%
+  \else\ifx\txiarg\wordcode
+    \gdef\kbdexamplefont{\tt}\gdef\kbdfont{\tt}%
+  \else
+    \errhelp = \EMsimple
+    \errmessage{Unknown @kbdinputstyle option `\txiarg'}%
+  \fi\fi\fi
+}
+\def\worddistinct{distinct}
+\def\wordexample{example}
+\def\wordcode{code}
+
+% Default is `distinct.'
+\kbdinputstyle distinct
+
+\def\xkey{\key}
+\def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}%
+\ifx\one\xkey\ifx\threex\three \key{#2}%
+\else{\tclose{\kbdfont\look}}\fi
+\else{\tclose{\kbdfont\look}}\fi}
+
+% For @indicateurl, @env, @command quotes seem unnecessary, so use \code.
+\let\indicateurl=\code
+\let\env=\code
+\let\command=\code
+
+% @uref (abbreviation for `urlref') takes an optional (comma-separated)
+% second argument specifying the text to display and an optional third
+% arg as text to display instead of (rather than in addition to) the url
+% itself.  First (mandatory) arg is the url.  Perhaps eventually put in
+% a hypertex \special here.
+%
+\def\uref#1{\douref #1,,,\finish}
+\def\douref#1,#2,#3,#4\finish{\begingroup
+  \unsepspaces
+  \pdfurl{#1}%
+  \setbox0 = \hbox{\ignorespaces #3}%
+  \ifdim\wd0 > 0pt
+    \unhbox0 % third arg given, show only that
+  \else
+    \setbox0 = \hbox{\ignorespaces #2}%
+    \ifdim\wd0 > 0pt
+      \ifpdf
+        \unhbox0             % PDF: 2nd arg given, show only it
+      \else
+        \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url
+      \fi
+    \else
+      \code{#1}% only url given, so show it
+    \fi
+  \fi
+  \endlink
+\endgroup}
+
+% @url synonym for @uref, since that's how everyone uses it.
+%
+\let\url=\uref
+
+% rms does not like angle brackets --karl, 17may97.
+% So now @email is just like @uref, unless we are pdf.
+%
+%\def\email#1{\angleleft{\tt #1}\angleright}
+\ifpdf
+  \def\email#1{\doemail#1,,\finish}
+  \def\doemail#1,#2,#3\finish{\begingroup
+    \unsepspaces
+    \pdfurl{mailto:#1}%
+    \setbox0 = \hbox{\ignorespaces #2}%
+    \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi
+    \endlink
+  \endgroup}
+\else
+  \let\email=\uref
+\fi
+
+% Check if we are currently using a typewriter font.  Since all the
+% Computer Modern typewriter fonts have zero interword stretch (and
+% shrink), and it is reasonable to expect all typewriter fonts to have
+% this property, we can check that font parameter.
+%
+\def\ifmonospace{\ifdim\fontdimen3\font=0pt }
+
+% Typeset a dimension, e.g., `in' or `pt'.  The only reason for the
+% argument is to make the input look right: @dmn{pt} instead of @dmn{}pt.
+%
+\def\dmn#1{\thinspace #1}
+
+\def\kbd#1{\def\look{#1}\expandafter\kbdfoo\look??\par}
+
+% @l was never documented to mean ``switch to the Lisp font'',
+% and it is not used as such in any manual I can find.  We need it for
+% Polish suppressed-l.  --karl, 22sep96.
+%\def\l#1{{\li #1}\null}
+
+% Explicit font changes: @r, @sc, undocumented @ii.
+\def\r#1{{\rm #1}}              % roman font
+\def\sc#1{{\smallcaps#1}}       % smallcaps font
+\def\ii#1{{\it #1}}             % italic font
+
+% @acronym for "FBI", "NATO", and the like.
+% We print this one point size smaller, since it's intended for
+% all-uppercase.
+%
+\def\acronym#1{\doacronym #1,,\finish}
+\def\doacronym#1,#2,#3\finish{%
+  {\selectfonts\lsize #1}%
+  \def\temp{#2}%
+  \ifx\temp\empty \else
+    \space ({\unsepspaces \ignorespaces \temp \unskip})%
+  \fi
+}
+
+% @abbr for "Comput. J." and the like.
+% No font change, but don't do end-of-sentence spacing.
+%
+\def\abbr#1{\doabbr #1,,\finish}
+\def\doabbr#1,#2,#3\finish{%
+  {\plainfrenchspacing #1}%
+  \def\temp{#2}%
+  \ifx\temp\empty \else
+    \space ({\unsepspaces \ignorespaces \temp \unskip})%
+  \fi
+}
+
+% @pounds{} is a sterling sign, which Knuth put in the CM italic font.
+%
+\def\pounds{{\it\$}}
+
+% @euro{} comes from a separate font, depending on the current style.
+% We use the free feym* fonts from the eurosym package by Henrik
+% Theiling, which support regular, slanted, bold and bold slanted (and
+% "outlined" (blackboard board, sort of) versions, which we don't need).
+% It is available from http://www.ctan.org/tex-archive/fonts/eurosym.
+%
+% Although only regular is the truly official Euro symbol, we ignore
+% that.  The Euro is designed to be slightly taller than the regular
+% font height.
+%
+% feymr - regular
+% feymo - slanted
+% feybr - bold
+% feybo - bold slanted
+%
+% There is no good (free) typewriter version, to my knowledge.
+% A feymr10 euro is ~7.3pt wide, while a normal cmtt10 char is ~5.25pt wide.
+% Hmm.
+%
+% Also doesn't work in math.  Do we need to do math with euro symbols?
+% Hope not.
+%
+%
+\def\euro{{\eurofont e}}
+\def\eurofont{%
+  % We set the font at each command, rather than predefining it in
+  % \textfonts and the other font-switching commands, so that
+  % installations which never need the symbol don't have to have the
+  % font installed.
+  %
+  % There is only one designed size (nominal 10pt), so we always scale
+  % that to the current nominal size.
+  %
+  % By the way, simply using "at 1em" works for cmr10 and the like, but
+  % does not work for cmbx10 and other extended/shrunken fonts.
+  %
+  \def\eurosize{\csname\curfontsize nominalsize\endcsname}%
+  %
+  \ifx\curfontstyle\bfstylename
+    % bold:
+    \font\thiseurofont = \ifusingit{feybo10}{feybr10} at \eurosize
+  \else
+    % regular:
+    \font\thiseurofont = \ifusingit{feymo10}{feymr10} at \eurosize
+  \fi
+  \thiseurofont
+}
+
+% Hacks for glyphs from the EC fonts similar to \euro.  We don't
+% use \let for the aliases, because sometimes we redefine the original
+% macro, and the alias should reflect the redefinition.
+\def\guillemetleft{{\ecfont \char"13}}
+\def\guillemotleft{\guillemetleft}
+\def\guillemetright{{\ecfont \char"14}}
+\def\guillemotright{\guillemetright}
+\def\guilsinglleft{{\ecfont \char"0E}}
+\def\guilsinglright{{\ecfont \char"0F}}
+\def\quotedblbase{{\ecfont \char"12}}
+\def\quotesinglbase{{\ecfont \char"0D}}
+%
+\def\ecfont{%
+  % We can't distinguish serif/sanserif and italic/slanted, but this
+  % is used for crude hacks anyway (like adding French and German
+  % quotes to documents typeset with CM, where we lose kerning), so
+  % hopefully nobody will notice/care.
+  \edef\ecsize{\csname\curfontsize ecsize\endcsname}%
+  \edef\nominalsize{\csname\curfontsize nominalsize\endcsname}%
+  \ifx\curfontstyle\bfstylename
+    % bold:
+    \font\thisecfont = ecb\ifusingit{i}{x}\ecsize \space at \nominalsize
+  \else
+    % regular:
+    \font\thisecfont = ec\ifusingit{ti}{rm}\ecsize \space at \nominalsize
+  \fi
+  \thisecfont
+}
+
+% @registeredsymbol - R in a circle.  The font for the R should really
+% be smaller yet, but lllsize is the best we can do for now.
+% Adapted from the plain.tex definition of \copyright.
+%
+\def\registeredsymbol{%
+  $^{{\ooalign{\hfil\raise.07ex\hbox{\selectfonts\lllsize R}%
+               \hfil\crcr\Orb}}%
+    }$%
+}
+
+% @textdegree - the normal degrees sign.
+%
+\def\textdegree{$^\circ$}
+
+% Laurent Siebenmann reports \Orb undefined with:
+%  Textures 1.7.7 (preloaded format=plain 93.10.14)  (68K)  16 APR 2004 02:38
+% so we'll define it if necessary.
+%
+\ifx\Orb\undefined
+\def\Orb{\mathhexbox20D}
+\fi
+
+% Quotes.
+\chardef\quotedblleft="5C
+\chardef\quotedblright=`\"
+\chardef\quoteleft=`\`
+\chardef\quoteright=`\'
+
+
+\message{page headings,}
+
+\newskip\titlepagetopglue \titlepagetopglue = 1.5in
+\newskip\titlepagebottomglue \titlepagebottomglue = 2pc
+
+% First the title page.  Must do @settitle before @titlepage.
+\newif\ifseenauthor
+\newif\iffinishedtitlepage
+
+% Do an implicit @contents or @shortcontents after @end titlepage if the
+% user says @setcontentsaftertitlepage or @setshortcontentsaftertitlepage.
+%
+\newif\ifsetcontentsaftertitlepage
+ \let\setcontentsaftertitlepage = \setcontentsaftertitlepagetrue
+\newif\ifsetshortcontentsaftertitlepage
+ \let\setshortcontentsaftertitlepage = \setshortcontentsaftertitlepagetrue
+
+\parseargdef\shorttitlepage{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}%
+        \endgroup\page\hbox{}\page}
+
+\envdef\titlepage{%
+  % Open one extra group, as we want to close it in the middle of \Etitlepage.
+  \begingroup
+    \parindent=0pt \textfonts
+    % Leave some space at the very top of the page.
+    \vglue\titlepagetopglue
+    % No rule at page bottom unless we print one at the top with @title.
+    \finishedtitlepagetrue
+    %
+    % Most title ``pages'' are actually two pages long, with space
+    % at the top of the second.  We don't want the ragged left on the second.
+    \let\oldpage = \page
+    \def\page{%
+      \iffinishedtitlepage\else
+        \finishtitlepage
+      \fi
+      \let\page = \oldpage
+      \page
+      \null
+    }%
+}
+
+\def\Etitlepage{%
+    \iffinishedtitlepage\else
+       \finishtitlepage
+    \fi
+    % It is important to do the page break before ending the group,
+    % because the headline and footline are only empty inside the group.
+    % If we use the new definition of \page, we always get a blank page
+    % after the title page, which we certainly don't want.
+    \oldpage
+  \endgroup
+  %
+  % Need this before the \...aftertitlepage checks so that if they are
+  % in effect the toc pages will come out with page numbers.
+  \HEADINGSon
+  %
+  % If they want short, they certainly want long too.
+  \ifsetshortcontentsaftertitlepage
+    \shortcontents
+    \contents
+    \global\let\shortcontents = \relax
+    \global\let\contents = \relax
+  \fi
+  %
+  \ifsetcontentsaftertitlepage
+    \contents
+    \global\let\contents = \relax
+    \global\let\shortcontents = \relax
+  \fi
+}
+
+\def\finishtitlepage{%
+  \vskip4pt \hrule height 2pt width \hsize
+  \vskip\titlepagebottomglue
+  \finishedtitlepagetrue
+}
+
+%%% Macros to be used within @titlepage:
+
+\let\subtitlerm=\tenrm
+\def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines}
+
+\def\authorfont{\authorrm \normalbaselineskip = 16pt \normalbaselines
+               \let\tt=\authortt}
+
+\parseargdef\title{%
+  \checkenv\titlepage
+  \leftline{\titlefonts\rm #1}
+  % print a rule at the page bottom also.
+  \finishedtitlepagefalse
+  \vskip4pt \hrule height 4pt width \hsize \vskip4pt
+}
+
+\parseargdef\subtitle{%
+  \checkenv\titlepage
+  {\subtitlefont \rightline{#1}}%
+}
+
+% @author should come last, but may come many times.
+% It can also be used inside @quotation.
+%
+\parseargdef\author{%
+  \def\temp{\quotation}%
+  \ifx\thisenv\temp
+    \def\quotationauthor{#1}% printed in \Equotation.
+  \else
+    \checkenv\titlepage
+    \ifseenauthor\else \vskip 0pt plus 1filll \seenauthortrue \fi
+    {\authorfont \leftline{#1}}%
+  \fi
+}
+
+
+%%% Set up page headings and footings.
+
+\let\thispage=\folio
+
+\newtoks\evenheadline    % headline on even pages
+\newtoks\oddheadline     % headline on odd pages
+\newtoks\evenfootline    % footline on even pages
+\newtoks\oddfootline     % footline on odd pages
+
+% Now make TeX use those variables
+\headline={{\textfonts\rm \ifodd\pageno \the\oddheadline
+                            \else \the\evenheadline \fi}}
+\footline={{\textfonts\rm \ifodd\pageno \the\oddfootline
+                            \else \the\evenfootline \fi}\HEADINGShook}
+\let\HEADINGShook=\relax
+
+% Commands to set those variables.
+% For example, this is what  @headings on  does
+% @evenheading @thistitle|@thispage|@thischapter
+% @oddheading @thischapter|@thispage|@thistitle
+% @evenfooting @thisfile||
+% @oddfooting ||@thisfile
+
+
+\def\evenheading{\parsearg\evenheadingxxx}
+\def\evenheadingxxx #1{\evenheadingyyy #1\|\|\|\|\finish}
+\def\evenheadingyyy #1\|#2\|#3\|#4\finish{%
+\global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\def\oddheading{\parsearg\oddheadingxxx}
+\def\oddheadingxxx #1{\oddheadingyyy #1\|\|\|\|\finish}
+\def\oddheadingyyy #1\|#2\|#3\|#4\finish{%
+\global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\parseargdef\everyheading{\oddheadingxxx{#1}\evenheadingxxx{#1}}%
+
+\def\evenfooting{\parsearg\evenfootingxxx}
+\def\evenfootingxxx #1{\evenfootingyyy #1\|\|\|\|\finish}
+\def\evenfootingyyy #1\|#2\|#3\|#4\finish{%
+\global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}}
+
+\def\oddfooting{\parsearg\oddfootingxxx}
+\def\oddfootingxxx #1{\oddfootingyyy #1\|\|\|\|\finish}
+\def\oddfootingyyy #1\|#2\|#3\|#4\finish{%
+  \global\oddfootline = {\rlap{\centerline{#2}}\line{#1\hfil#3}}%
+  %
+  % Leave some space for the footline.  Hopefully ok to assume
+  % @evenfooting will not be used by itself.
+  \global\advance\pageheight by -12pt
+  \global\advance\vsize by -12pt
+}
+
+\parseargdef\everyfooting{\oddfootingxxx{#1}\evenfootingxxx{#1}}
+
+% @evenheadingmarks top     \thischapter <- chapter at the top of a page
+% @evenheadingmarks bottom  \thischapter <- chapter at the bottom of a page
+%
+% The same set of arguments for:
+%
+% @oddheadingmarks
+% @evenfootingmarks
+% @oddfootingmarks
+% @everyheadingmarks
+% @everyfootingmarks
+
+\def\evenheadingmarks{\headingmarks{even}{heading}}
+\def\oddheadingmarks{\headingmarks{odd}{heading}}
+\def\evenfootingmarks{\headingmarks{even}{footing}}
+\def\oddfootingmarks{\headingmarks{odd}{footing}}
+\def\everyheadingmarks#1 {\headingmarks{even}{heading}{#1}
+                          \headingmarks{odd}{heading}{#1} }
+\def\everyfootingmarks#1 {\headingmarks{even}{footing}{#1}
+                          \headingmarks{odd}{footing}{#1} }
+% #1 = even/odd, #2 = heading/footing, #3 = top/bottom.
+\def\headingmarks#1#2#3 {%
+  \expandafter\let\expandafter\temp \csname get#3headingmarks\endcsname
+  \global\expandafter\let\csname get#1#2marks\endcsname \temp
+}
+
+\everyheadingmarks bottom
+\everyfootingmarks bottom
+
+% @headings double      turns headings on for double-sided printing.
+% @headings single      turns headings on for single-sided printing.
+% @headings off         turns them off.
+% @headings on          same as @headings double, retained for compatibility.
+% @headings after       turns on double-sided headings after this page.
+% @headings doubleafter turns on double-sided headings after this page.
+% @headings singleafter turns on single-sided headings after this page.
+% By default, they are off at the start of a document,
+% and turned `on' after @end titlepage.
+
+\def\headings #1 {\csname HEADINGS#1\endcsname}
+
+\def\HEADINGSoff{%
+\global\evenheadline={\hfil} \global\evenfootline={\hfil}
+\global\oddheadline={\hfil} \global\oddfootline={\hfil}}
+\HEADINGSoff
+% When we turn headings on, set the page number to 1.
+% For double-sided printing, put current file name in lower left corner,
+% chapter name on inside top of right hand pages, document
+% title on inside top of left hand pages, and page numbers on outside top
+% edge of all pages.
+\def\HEADINGSdouble{%
+\global\pageno=1
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\folio\hfil\thistitle}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+\global\let\contentsalignmacro = \chapoddpage
+}
+\let\contentsalignmacro = \chappager
+
+% For single-sided printing, chapter title goes across top left of page,
+% page number on top right.
+\def\HEADINGSsingle{%
+\global\pageno=1
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\thischapter\hfil\folio}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+\global\let\contentsalignmacro = \chappager
+}
+\def\HEADINGSon{\HEADINGSdouble}
+
+\def\HEADINGSafter{\let\HEADINGShook=\HEADINGSdoublex}
+\let\HEADINGSdoubleafter=\HEADINGSafter
+\def\HEADINGSdoublex{%
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\folio\hfil\thistitle}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+\global\let\contentsalignmacro = \chapoddpage
+}
+
+\def\HEADINGSsingleafter{\let\HEADINGShook=\HEADINGSsinglex}
+\def\HEADINGSsinglex{%
+\global\evenfootline={\hfil}
+\global\oddfootline={\hfil}
+\global\evenheadline={\line{\thischapter\hfil\folio}}
+\global\oddheadline={\line{\thischapter\hfil\folio}}
+\global\let\contentsalignmacro = \chappager
+}
+
+% Subroutines used in generating headings
+% This produces Day Month Year style of output.
+% Only define if not already defined, in case a txi-??.tex file has set
+% up a different format (e.g., txi-cs.tex does this).
+\ifx\today\undefined
+\def\today{%
+  \number\day\space
+  \ifcase\month
+  \or\putwordMJan\or\putwordMFeb\or\putwordMMar\or\putwordMApr
+  \or\putwordMMay\or\putwordMJun\or\putwordMJul\or\putwordMAug
+  \or\putwordMSep\or\putwordMOct\or\putwordMNov\or\putwordMDec
+  \fi
+  \space\number\year}
+\fi
+
+% @settitle line...  specifies the title of the document, for headings.
+% It generates no output of its own.
+\def\thistitle{\putwordNoTitle}
+\def\settitle{\parsearg{\gdef\thistitle}}
+
+
+\message{tables,}
+% Tables -- @table, @ftable, @vtable, @item(x).
+
+% default indentation of table text
+\newdimen\tableindent \tableindent=.8in
+% default indentation of @itemize and @enumerate text
+\newdimen\itemindent  \itemindent=.3in
+% margin between end of table item and start of table text.
+\newdimen\itemmargin  \itemmargin=.1in
+
+% used internally for \itemindent minus \itemmargin
+\newdimen\itemmax
+
+% Note @table, @ftable, and @vtable define @item, @itemx, etc., with
+% these defs.
+% They also define \itemindex
+% to index the item name in whatever manner is desired (perhaps none).
+
+\newif\ifitemxneedsnegativevskip
+
+\def\itemxpar{\par\ifitemxneedsnegativevskip\nobreak\vskip-\parskip\nobreak\fi}
+
+\def\internalBitem{\smallbreak \parsearg\itemzzz}
+\def\internalBitemx{\itemxpar \parsearg\itemzzz}
+
+\def\itemzzz #1{\begingroup %
+  \advance\hsize by -\rightskip
+  \advance\hsize by -\tableindent
+  \setbox0=\hbox{\itemindicate{#1}}%
+  \itemindex{#1}%
+  \nobreak % This prevents a break before @itemx.
+  %
+  % If the item text does not fit in the space we have, put it on a line
+  % by itself, and do not allow a page break either before or after that
+  % line.  We do not start a paragraph here because then if the next
+  % command is, e.g., @kindex, the whatsit would get put into the
+  % horizontal list on a line by itself, resulting in extra blank space.
+  \ifdim \wd0>\itemmax
+    %
+    % Make this a paragraph so we get the \parskip glue and wrapping,
+    % but leave it ragged-right.
+    \begingroup
+      \advance\leftskip by-\tableindent
+      \advance\hsize by\tableindent
+      \advance\rightskip by0pt plus1fil
+      \leavevmode\unhbox0\par
+    \endgroup
+    %
+    % We're going to be starting a paragraph, but we don't want the
+    % \parskip glue -- logically it's part of the @item we just started.
+    \nobreak \vskip-\parskip
+    %
+    % Stop a page break at the \parskip glue coming up.  However, if
+    % what follows is an environment such as @example, there will be no
+    % \parskip glue; then the negative vskip we just inserted would
+    % cause the example and the item to crash together.  So we use this
+    % bizarre value of 10001 as a signal to \aboveenvbreak to insert
+    % \parskip glue after all.  Section titles are handled this way also.
+    %
+    \penalty 10001
+    \endgroup
+    \itemxneedsnegativevskipfalse
+  \else
+    % The item text fits into the space.  Start a paragraph, so that the
+    % following text (if any) will end up on the same line.
+    \noindent
+    % Do this with kerns and \unhbox so that if there is a footnote in
+    % the item text, it can migrate to the main vertical list and
+    % eventually be printed.
+    \nobreak\kern-\tableindent
+    \dimen0 = \itemmax  \advance\dimen0 by \itemmargin \advance\dimen0 by -\wd0
+    \unhbox0
+    \nobreak\kern\dimen0
+    \endgroup
+    \itemxneedsnegativevskiptrue
+  \fi
+}
+
+\def\item{\errmessage{@item while not in a list environment}}
+\def\itemx{\errmessage{@itemx while not in a list environment}}
+
+% @table, @ftable, @vtable.
+\envdef\table{%
+  \let\itemindex\gobble
+  \tablecheck{table}%
+}
+\envdef\ftable{%
+  \def\itemindex ##1{\doind {fn}{\code{##1}}}%
+  \tablecheck{ftable}%
+}
+\envdef\vtable{%
+  \def\itemindex ##1{\doind {vr}{\code{##1}}}%
+  \tablecheck{vtable}%
+}
+\def\tablecheck#1{%
+  \ifnum \the\catcode`\^^M=\active
+    \endgroup
+    \errmessage{This command won't work in this context; perhaps the problem is
+      that we are \inenvironment\thisenv}%
+    \def\next{\doignore{#1}}%
+  \else
+    \let\next\tablex
+  \fi
+  \next
+}
+\def\tablex#1{%
+  \def\itemindicate{#1}%
+  \parsearg\tabley
+}
+\def\tabley#1{%
+  {%
+    \makevalueexpandable
+    \edef\temp{\noexpand\tablez #1\space\space\space}%
+    \expandafter
+  }\temp \endtablez
+}
+\def\tablez #1 #2 #3 #4\endtablez{%
+  \aboveenvbreak
+  \ifnum 0#1>0 \advance \leftskip by #1\mil \fi
+  \ifnum 0#2>0 \tableindent=#2\mil \fi
+  \ifnum 0#3>0 \advance \rightskip by #3\mil \fi
+  \itemmax=\tableindent
+  \advance \itemmax by -\itemmargin
+  \advance \leftskip by \tableindent
+  \exdentamount=\tableindent
+  \parindent = 0pt
+  \parskip = \smallskipamount
+  \ifdim \parskip=0pt \parskip=2pt \fi
+  \let\item = \internalBitem
+  \let\itemx = \internalBitemx
+}
+\def\Etable{\endgraf\afterenvbreak}
+\let\Eftable\Etable
+\let\Evtable\Etable
+\let\Eitemize\Etable
+\let\Eenumerate\Etable
+
+% This is the counter used by @enumerate, which is really @itemize
+
+\newcount \itemno
+
+\envdef\itemize{\parsearg\doitemize}
+
+\def\doitemize#1{%
+  \aboveenvbreak
+  \itemmax=\itemindent
+  \advance\itemmax by -\itemmargin
+  \advance\leftskip by \itemindent
+  \exdentamount=\itemindent
+  \parindent=0pt
+  \parskip=\smallskipamount
+  \ifdim\parskip=0pt \parskip=2pt \fi
+  \def\itemcontents{#1}%
+  % @itemize with no arg is equivalent to @itemize @bullet.
+  \ifx\itemcontents\empty\def\itemcontents{\bullet}\fi
+  \let\item=\itemizeitem
+}
+
+% Definition of @item while inside @itemize and @enumerate.
+%
+\def\itemizeitem{%
+  \advance\itemno by 1  % for enumerations
+  {\let\par=\endgraf \smallbreak}% reasonable place to break
+  {%
+   % If the document has an @itemize directly after a section title, a
+   % \nobreak will be last on the list, and \sectionheading will have
+   % done a \vskip-\parskip.  In that case, we don't want to zero
+   % parskip, or the item text will crash with the heading.  On the
+   % other hand, when there is normal text preceding the item (as there
+   % usually is), we do want to zero parskip, or there would be too much
+   % space.  In that case, we won't have a \nobreak before.  At least
+   % that's the theory.
+   \ifnum\lastpenalty<10000 \parskip=0in \fi
+   \noindent
+   \hbox to 0pt{\hss \itemcontents \kern\itemmargin}%
+   \vadjust{\penalty 1200}}% not good to break after first line of item.
+  \flushcr
+}
+
+% \splitoff TOKENS\endmark defines \first to be the first token in
+% TOKENS, and \rest to be the remainder.
+%
+\def\splitoff#1#2\endmark{\def\first{#1}\def\rest{#2}}%
+
+% Allow an optional argument of an uppercase letter, lowercase letter,
+% or number, to specify the first label in the enumerated list.  No
+% argument is the same as `1'.
+%
+\envparseargdef\enumerate{\enumeratey #1  \endenumeratey}
+\def\enumeratey #1 #2\endenumeratey{%
+  % If we were given no argument, pretend we were given `1'.
+  \def\thearg{#1}%
+  \ifx\thearg\empty \def\thearg{1}\fi
+  %
+  % Detect if the argument is a single token.  If so, it might be a
+  % letter.  Otherwise, the only valid thing it can be is a number.
+  % (We will always have one token, because of the test we just made.
+  % This is a good thing, since \splitoff doesn't work given nothing at
+  % all -- the first parameter is undelimited.)
+  \expandafter\splitoff\thearg\endmark
+  \ifx\rest\empty
+    % Only one token in the argument.  It could still be anything.
+    % A ``lowercase letter'' is one whose \lccode is nonzero.
+    % An ``uppercase letter'' is one whose \lccode is both nonzero, and
+    %   not equal to itself.
+    % Otherwise, we assume it's a number.
+    %
+    % We need the \relax at the end of the \ifnum lines to stop TeX from
+    % continuing to look for a <number>.
+    %
+    \ifnum\lccode\expandafter`\thearg=0\relax
+      \numericenumerate % a number (we hope)
+    \else
+      % It's a letter.
+      \ifnum\lccode\expandafter`\thearg=\expandafter`\thearg\relax
+        \lowercaseenumerate % lowercase letter
+      \else
+        \uppercaseenumerate % uppercase letter
+      \fi
+    \fi
+  \else
+    % Multiple tokens in the argument.  We hope it's a number.
+    \numericenumerate
+  \fi
+}
+
+% An @enumerate whose labels are integers.  The starting integer is
+% given in \thearg.
+%
+\def\numericenumerate{%
+  \itemno = \thearg
+  \startenumeration{\the\itemno}%
+}
+
+% The starting (lowercase) letter is in \thearg.
+\def\lowercaseenumerate{%
+  \itemno = \expandafter`\thearg
+  \startenumeration{%
+    % Be sure we're not beyond the end of the alphabet.
+    \ifnum\itemno=0
+      \errmessage{No more lowercase letters in @enumerate; get a bigger
+                  alphabet}%
+    \fi
+    \char\lccode\itemno
+  }%
+}
+
+% The starting (uppercase) letter is in \thearg.
+\def\uppercaseenumerate{%
+  \itemno = \expandafter`\thearg
+  \startenumeration{%
+    % Be sure we're not beyond the end of the alphabet.
+    \ifnum\itemno=0
+      \errmessage{No more uppercase letters in @enumerate; get a bigger
+                  alphabet}
+    \fi
+    \char\uccode\itemno
+  }%
+}
+
+% Call \doitemize, adding a period to the first argument and supplying the
+% common last two arguments.  Also subtract one from the initial value in
+% \itemno, since @item increments \itemno.
+%
+\def\startenumeration#1{%
+  \advance\itemno by -1
+  \doitemize{#1.}\flushcr
+}
+
+% @alphaenumerate and @capsenumerate are abbreviations for giving an arg
+% to @enumerate.
+%
+\def\alphaenumerate{\enumerate{a}}
+\def\capsenumerate{\enumerate{A}}
+\def\Ealphaenumerate{\Eenumerate}
+\def\Ecapsenumerate{\Eenumerate}
+
+
+% @multitable macros
+% Amy Hendrickson, 8/18/94, 3/6/96
+%
+% @multitable ... @end multitable will make as many columns as desired.
+% Contents of each column will wrap at width given in preamble.  Width
+% can be specified either with sample text given in a template line,
+% or in percent of \hsize, the current width of text on page.
+
+% Table can continue over pages but will only break between lines.
+
+% To make preamble:
+%
+% Either define widths of columns in terms of percent of \hsize:
+%   @multitable @columnfractions .25 .3 .45
+%   @item ...
+%
+%   Numbers following @columnfractions are the percent of the total
+%   current hsize to be used for each column. You may use as many
+%   columns as desired.
+
+
+% Or use a template:
+%   @multitable {Column 1 template} {Column 2 template} {Column 3 template}
+%   @item ...
+%   using the widest term desired in each column.
+
+% Each new table line starts with @item, each subsequent new column
+% starts with @tab. Empty columns may be produced by supplying @tab's
+% with nothing between them for as many times as empty columns are needed,
+% ie, @tab@tab@tab will produce two empty columns.
+
+% @item, @tab do not need to be on their own lines, but it will not hurt
+% if they are.
+
+% Sample multitable:
+
+%   @multitable {Column 1 template} {Column 2 template} {Column 3 template}
+%   @item first col stuff @tab second col stuff @tab third col
+%   @item
+%   first col stuff
+%   @tab
+%   second col stuff
+%   @tab
+%   third col
+%   @item first col stuff @tab second col stuff
+%   @tab Many paragraphs of text may be used in any column.
+%
+%         They will wrap at the width determined by the template.
+%   @item@tab@tab This will be in third column.
+%   @end multitable
+
+% Default dimensions may be reset by user.
+% @multitableparskip is vertical space between paragraphs in table.
+% @multitableparindent is paragraph indent in table.
+% @multitablecolmargin is horizontal space to be left between columns.
+% @multitablelinespace is space to leave between table items, baseline
+%                                                            to baseline.
+%   0pt means it depends on current normal line spacing.
+%
+\newskip\multitableparskip
+\newskip\multitableparindent
+\newdimen\multitablecolspace
+\newskip\multitablelinespace
+\multitableparskip=0pt
+\multitableparindent=6pt
+\multitablecolspace=12pt
+\multitablelinespace=0pt
+
+% Macros used to set up halign preamble:
+%
+\let\endsetuptable\relax
+\def\xendsetuptable{\endsetuptable}
+\let\columnfractions\relax
+\def\xcolumnfractions{\columnfractions}
+\newif\ifsetpercent
+
+% #1 is the @columnfraction, usually a decimal number like .5, but might
+% be just 1.  We just use it, whatever it is.
+%
+\def\pickupwholefraction#1 {%
+  \global\advance\colcount by 1
+  \expandafter\xdef\csname col\the\colcount\endcsname{#1\hsize}%
+  \setuptable
+}
+
+\newcount\colcount
+\def\setuptable#1{%
+  \def\firstarg{#1}%
+  \ifx\firstarg\xendsetuptable
+    \let\go = \relax
+  \else
+    \ifx\firstarg\xcolumnfractions
+      \global\setpercenttrue
+    \else
+      \ifsetpercent
+         \let\go\pickupwholefraction
+      \else
+         \global\advance\colcount by 1
+         \setbox0=\hbox{#1\unskip\space}% Add a normal word space as a
+                   % separator; typically that is always in the input, anyway.
+         \expandafter\xdef\csname col\the\colcount\endcsname{\the\wd0}%
+      \fi
+    \fi
+    \ifx\go\pickupwholefraction
+      % Put the argument back for the \pickupwholefraction call, so
+      % we'll always have a period there to be parsed.
+      \def\go{\pickupwholefraction#1}%
+    \else
+      \let\go = \setuptable
+    \fi%
+  \fi
+  \go
+}
+
+% multitable-only commands.
+%
+% @headitem starts a heading row, which we typeset in bold.
+% Assignments have to be global since we are inside the implicit group
+% of an alignment entry.  Note that \everycr resets \everytab.
+\def\headitem{\checkenv\multitable \crcr \global\everytab={\bf}\the\everytab}%
+%
+% A \tab used to include \hskip1sp.  But then the space in a template
+% line is not enough.  That is bad.  So let's go back to just `&' until
+% we encounter the problem it was intended to solve again.
+%                                      --karl, nathan@acm.org, 20apr99.
+\def\tab{\checkenv\multitable &\the\everytab}%
+
+% @multitable ... @end multitable definitions:
+%
+\newtoks\everytab  % insert after every tab.
+%
+\envdef\multitable{%
+  \vskip\parskip
+  \startsavinginserts
+  %
+  % @item within a multitable starts a normal row.
+  % We use \def instead of \let so that if one of the multitable entries
+  % contains an @itemize, we don't choke on the \item (seen as \crcr aka
+  % \endtemplate) expanding \doitemize.
+  \def\item{\crcr}%
+  %
+  \tolerance=9500
+  \hbadness=9500
+  \setmultitablespacing
+  \parskip=\multitableparskip
+  \parindent=\multitableparindent
+  \overfullrule=0pt
+  \global\colcount=0
+  %
+  \everycr = {%
+    \noalign{%
+      \global\everytab={}%
+      \global\colcount=0 % Reset the column counter.
+      % Check for saved footnotes, etc.
+      \checkinserts
+      % Keeps underfull box messages off when table breaks over pages.
+      %\filbreak
+       % Maybe so, but it also creates really weird page breaks when the
+       % table breaks over pages. Wouldn't \vfil be better?  Wait until the
+       % problem manifests itself, so it can be fixed for real --karl.
+    }%
+  }%
+  %
+  \parsearg\domultitable
+}
+\def\domultitable#1{%
+  % To parse everything between @multitable and @item:
+  \setuptable#1 \endsetuptable
+  %
+  % This preamble sets up a generic column definition, which will
+  % be used as many times as user calls for columns.
+  % \vtop will set a single line and will also let text wrap and
+  % continue for many paragraphs if desired.
+  \halign\bgroup &%
+    \global\advance\colcount by 1
+    \multistrut
+    \vtop{%
+      % Use the current \colcount to find the correct column width:
+      \hsize=\expandafter\csname col\the\colcount\endcsname
+      %
+      % In order to keep entries from bumping into each other
+      % we will add a \leftskip of \multitablecolspace to all columns after
+      % the first one.
+      %
+      % If a template has been used, we will add \multitablecolspace
+      % to the width of each template entry.
+      %
+      % If the user has set preamble in terms of percent of \hsize we will
+      % use that dimension as the width of the column, and the \leftskip
+      % will keep entries from bumping into each other.  Table will start at
+      % left margin and final column will justify at right margin.
+      %
+      % Make sure we don't inherit \rightskip from the outer environment.
+      \rightskip=0pt
+      \ifnum\colcount=1
+       % The first column will be indented with the surrounding text.
+       \advance\hsize by\leftskip
+      \else
+       \ifsetpercent \else
+         % If user has not set preamble in terms of percent of \hsize
+         % we will advance \hsize by \multitablecolspace.
+         \advance\hsize by \multitablecolspace
+       \fi
+       % In either case we will make \leftskip=\multitablecolspace:
+      \leftskip=\multitablecolspace
+      \fi
+      % Ignoring space at the beginning and end avoids an occasional spurious
+      % blank line, when TeX decides to break the line at the space before the
+      % box from the multistrut, so the strut ends up on a line by itself.
+      % For example:
+      % @multitable @columnfractions .11 .89
+      % @item @code{#}
+      % @tab Legal holiday which is valid in major parts of the whole country.
+      % Is automatically provided with highlighting sequences respectively
+      % marking characters.
+      \noindent\ignorespaces##\unskip\multistrut
+    }\cr
+}
+\def\Emultitable{%
+  \crcr
+  \egroup % end the \halign
+  \global\setpercentfalse
+}
+
+\def\setmultitablespacing{%
+  \def\multistrut{\strut}% just use the standard line spacing
+  %
+  % Compute \multitablelinespace (if not defined by user) for use in
+  % \multitableparskip calculation.  We used define \multistrut based on
+  % this, but (ironically) that caused the spacing to be off.
+  % See bug-texinfo report from Werner Lemberg, 31 Oct 2004 12:52:20 +0100.
+\ifdim\multitablelinespace=0pt
+\setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip
+\global\advance\multitablelinespace by-\ht0
+\fi
+%% Test to see if parskip is larger than space between lines of
+%% table. If not, do nothing.
+%%        If so, set to same dimension as multitablelinespace.
+\ifdim\multitableparskip>\multitablelinespace
+\global\multitableparskip=\multitablelinespace
+\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller
+                                      %% than skip between lines in the table.
+\fi%
+\ifdim\multitableparskip=0pt
+\global\multitableparskip=\multitablelinespace
+\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller
+                                      %% than skip between lines in the table.
+\fi}
+
+
+\message{conditionals,}
+
+% @iftex, @ifnotdocbook, @ifnothtml, @ifnotinfo, @ifnotplaintext,
+% @ifnotxml always succeed.  They currently do nothing; we don't
+% attempt to check whether the conditionals are properly nested.  But we
+% have to remember that they are conditionals, so that @end doesn't
+% attempt to close an environment group.
+%
+\def\makecond#1{%
+  \expandafter\let\csname #1\endcsname = \relax
+  \expandafter\let\csname iscond.#1\endcsname = 1
+}
+\makecond{iftex}
+\makecond{ifnotdocbook}
+\makecond{ifnothtml}
+\makecond{ifnotinfo}
+\makecond{ifnotplaintext}
+\makecond{ifnotxml}
+
+% Ignore @ignore, @ifhtml, @ifinfo, and the like.
+%
+\def\direntry{\doignore{direntry}}
+\def\documentdescription{\doignore{documentdescription}}
+\def\docbook{\doignore{docbook}}
+\def\html{\doignore{html}}
+\def\ifdocbook{\doignore{ifdocbook}}
+\def\ifhtml{\doignore{ifhtml}}
+\def\ifinfo{\doignore{ifinfo}}
+\def\ifnottex{\doignore{ifnottex}}
+\def\ifplaintext{\doignore{ifplaintext}}
+\def\ifxml{\doignore{ifxml}}
+\def\ignore{\doignore{ignore}}
+\def\menu{\doignore{menu}}
+\def\xml{\doignore{xml}}
+
+% Ignore text until a line `@end #1', keeping track of nested conditionals.
+%
+% A count to remember the depth of nesting.
+\newcount\doignorecount
+
+\def\doignore#1{\begingroup
+  % Scan in ``verbatim'' mode:
+  \obeylines
+  \catcode`\@ = \other
+  \catcode`\{ = \other
+  \catcode`\} = \other
+  %
+  % Make sure that spaces turn into tokens that match what \doignoretext wants.
+  \spaceisspace
+  %
+  % Count number of #1's that we've seen.
+  \doignorecount = 0
+  %
+  % Swallow text until we reach the matching `@end #1'.
+  \dodoignore{#1}%
+}
+
+{ \catcode`_=11 % We want to use \_STOP_ which cannot appear in texinfo source.
+  \obeylines %
+  %
+  \gdef\dodoignore#1{%
+    % #1 contains the command name as a string, e.g., `ifinfo'.
+    %
+    % Define a command to find the next `@end #1'.
+    \long\def\doignoretext##1^^M@end #1{%
+      \doignoretextyyy##1^^M@#1\_STOP_}%
+    %
+    % And this command to find another #1 command, at the beginning of a
+    % line.  (Otherwise, we would consider a line `@c @ifset', for
+    % example, to count as an @ifset for nesting.)
+    \long\def\doignoretextyyy##1^^M@#1##2\_STOP_{\doignoreyyy{##2}\_STOP_}%
+    %
+    % And now expand that command.
+    \doignoretext ^^M%
+  }%
+}
+
+\def\doignoreyyy#1{%
+  \def\temp{#1}%
+  \ifx\temp\empty                      % Nothing found.
+    \let\next\doignoretextzzz
+  \else                                        % Found a nested condition, ...
+    \advance\doignorecount by 1
+    \let\next\doignoretextyyy          % ..., look for another.
+    % If we're here, #1 ends with ^^M\ifinfo (for example).
+  \fi
+  \next #1% the token \_STOP_ is present just after this macro.
+}
+
+% We have to swallow the remaining "\_STOP_".
+%
+\def\doignoretextzzz#1{%
+  \ifnum\doignorecount = 0     % We have just found the outermost @end.
+    \let\next\enddoignore
+  \else                                % Still inside a nested condition.
+    \advance\doignorecount by -1
+    \let\next\doignoretext      % Look for the next @end.
+  \fi
+  \next
+}
+
+% Finish off ignored text.
+{ \obeylines%
+  % Ignore anything after the last `@end #1'; this matters in verbatim
+  % environments, where otherwise the newline after an ignored conditional
+  % would result in a blank line in the output.
+  \gdef\enddoignore#1^^M{\endgroup\ignorespaces}%
+}
+
+
+% @set VAR sets the variable VAR to an empty value.
+% @set VAR REST-OF-LINE sets VAR to the value REST-OF-LINE.
+%
+% Since we want to separate VAR from REST-OF-LINE (which might be
+% empty), we can't just use \parsearg; we have to insert a space of our
+% own to delimit the rest of the line, and then take it out again if we
+% didn't need it.
+% We rely on the fact that \parsearg sets \catcode`\ =10.
+%
+\parseargdef\set{\setyyy#1 \endsetyyy}
+\def\setyyy#1 #2\endsetyyy{%
+  {%
+    \makevalueexpandable
+    \def\temp{#2}%
+    \edef\next{\gdef\makecsname{SET#1}}%
+    \ifx\temp\empty
+      \next{}%
+    \else
+      \setzzz#2\endsetzzz
+    \fi
+  }%
+}
+% Remove the trailing space \setxxx inserted.
+\def\setzzz#1 \endsetzzz{\next{#1}}
+
+% @clear VAR clears (i.e., unsets) the variable VAR.
+%
+\parseargdef\clear{%
+  {%
+    \makevalueexpandable
+    \global\expandafter\let\csname SET#1\endcsname=\relax
+  }%
+}
+
+% @value{foo} gets the text saved in variable foo.
+\def\value{\begingroup\makevalueexpandable\valuexxx}
+\def\valuexxx#1{\expandablevalue{#1}\endgroup}
+{
+  \catcode`\- = \active \catcode`\_ = \active
+  %
+  \gdef\makevalueexpandable{%
+    \let\value = \expandablevalue
+    % We don't want these characters active, ...
+    \catcode`\-=\other \catcode`\_=\other
+    % ..., but we might end up with active ones in the argument if
+    % we're called from @code, as @code{@value{foo-bar_}}, though.
+    % So \let them to their normal equivalents.
+    \let-\realdash \let_\normalunderscore
+  }
+}
+
+% We have this subroutine so that we can handle at least some @value's
+% properly in indexes (we call \makevalueexpandable in \indexdummies).
+% The command has to be fully expandable (if the variable is set), since
+% the result winds up in the index file.  This means that if the
+% variable's value contains other Texinfo commands, it's almost certain
+% it will fail (although perhaps we could fix that with sufficient work
+% to do a one-level expansion on the result, instead of complete).
+%
+\def\expandablevalue#1{%
+  \expandafter\ifx\csname SET#1\endcsname\relax
+    {[No value for ``#1'']}%
+    \message{Variable `#1', used in @value, is not set.}%
+  \else
+    \csname SET#1\endcsname
+  \fi
+}
+
+% @ifset VAR ... @end ifset reads the `...' iff VAR has been defined
+% with @set.
+%
+% To get special treatment of `@end ifset,' call \makeond and the redefine.
+%
+\makecond{ifset}
+\def\ifset{\parsearg{\doifset{\let\next=\ifsetfail}}}
+\def\doifset#1#2{%
+  {%
+    \makevalueexpandable
+    \let\next=\empty
+    \expandafter\ifx\csname SET#2\endcsname\relax
+      #1% If not set, redefine \next.
+    \fi
+    \expandafter
+  }\next
+}
+\def\ifsetfail{\doignore{ifset}}
+
+% @ifclear VAR ... @end ifclear reads the `...' iff VAR has never been
+% defined with @set, or has been undefined with @clear.
+%
+% The `\else' inside the `\doifset' parameter is a trick to reuse the
+% above code: if the variable is not set, do nothing, if it is set,
+% then redefine \next to \ifclearfail.
+%
+\makecond{ifclear}
+\def\ifclear{\parsearg{\doifset{\else \let\next=\ifclearfail}}}
+\def\ifclearfail{\doignore{ifclear}}
+
+% @dircategory CATEGORY  -- specify a category of the dir file
+% which this file should belong to.  Ignore this in TeX.
+\let\dircategory=\comment
+
+% @defininfoenclose.
+\let\definfoenclose=\comment
+
+
+\message{indexing,}
+% Index generation facilities
+
+% Define \newwrite to be identical to plain tex's \newwrite
+% except not \outer, so it can be used within macros and \if's.
+\edef\newwrite{\makecsname{ptexnewwrite}}
+
+% \newindex {foo} defines an index named foo.
+% It automatically defines \fooindex such that
+% \fooindex ...rest of line... puts an entry in the index foo.
+% It also defines \fooindfile to be the number of the output channel for
+% the file that accumulates this index.  The file's extension is foo.
+% The name of an index should be no more than 2 characters long
+% for the sake of vms.
+%
+\def\newindex#1{%
+  \iflinks
+    \expandafter\newwrite \csname#1indfile\endcsname
+    \openout \csname#1indfile\endcsname \jobname.#1 % Open the file
+  \fi
+  \expandafter\xdef\csname#1index\endcsname{%     % Define @#1index
+    \noexpand\doindex{#1}}
+}
+
+% @defindex foo  ==  \newindex{foo}
+%
+\def\defindex{\parsearg\newindex}
+
+% Define @defcodeindex, like @defindex except put all entries in @code.
+%
+\def\defcodeindex{\parsearg\newcodeindex}
+%
+\def\newcodeindex#1{%
+  \iflinks
+    \expandafter\newwrite \csname#1indfile\endcsname
+    \openout \csname#1indfile\endcsname \jobname.#1
+  \fi
+  \expandafter\xdef\csname#1index\endcsname{%
+    \noexpand\docodeindex{#1}}%
+}
+
+
+% @synindex foo bar    makes index foo feed into index bar.
+% Do this instead of @defindex foo if you don't want it as a separate index.
+%
+% @syncodeindex foo bar   similar, but put all entries made for index foo
+% inside @code.
+%
+\def\synindex#1 #2 {\dosynindex\doindex{#1}{#2}}
+\def\syncodeindex#1 #2 {\dosynindex\docodeindex{#1}{#2}}
+
+% #1 is \doindex or \docodeindex, #2 the index getting redefined (foo),
+% #3 the target index (bar).
+\def\dosynindex#1#2#3{%
+  % Only do \closeout if we haven't already done it, else we'll end up
+  % closing the target index.
+  \expandafter \ifx\csname donesynindex#2\endcsname \undefined
+    % The \closeout helps reduce unnecessary open files; the limit on the
+    % Acorn RISC OS is a mere 16 files.
+    \expandafter\closeout\csname#2indfile\endcsname
+    \expandafter\let\csname\donesynindex#2\endcsname = 1
+  \fi
+  % redefine \fooindfile:
+  \expandafter\let\expandafter\temp\expandafter=\csname#3indfile\endcsname
+  \expandafter\let\csname#2indfile\endcsname=\temp
+  % redefine \fooindex:
+  \expandafter\xdef\csname#2index\endcsname{\noexpand#1{#3}}%
+}
+
+% Define \doindex, the driver for all \fooindex macros.
+% Argument #1 is generated by the calling \fooindex macro,
+%  and it is "foo", the name of the index.
+
+% \doindex just uses \parsearg; it calls \doind for the actual work.
+% This is because \doind is more useful to call from other macros.
+
+% There is also \dosubind {index}{topic}{subtopic}
+% which makes an entry in a two-level index such as the operation index.
+
+\def\doindex#1{\edef\indexname{#1}\parsearg\singleindexer}
+\def\singleindexer #1{\doind{\indexname}{#1}}
+
+% like the previous two, but they put @code around the argument.
+\def\docodeindex#1{\edef\indexname{#1}\parsearg\singlecodeindexer}
+\def\singlecodeindexer #1{\doind{\indexname}{\code{#1}}}
+
+% Take care of Texinfo commands that can appear in an index entry.
+% Since there are some commands we want to expand, and others we don't,
+% we have to laboriously prevent expansion for those that we don't.
+%
+\def\indexdummies{%
+  \escapechar = `\\     % use backslash in output files.
+  \def\@{@}% change to @@ when we switch to @ as escape char in index files.
+  \def\ {\realbackslash\space }%
+  %
+  % Need these in case \tex is in effect and \{ is a \delimiter again.
+  % But can't use \lbracecmd and \rbracecmd because texindex assumes
+  % braces and backslashes are used only as delimiters.
+  \let\{ = \mylbrace
+  \let\} = \myrbrace
+  %
+  % I don't entirely understand this, but when an index entry is
+  % generated from a macro call, the \endinput which \scanmacro inserts
+  % causes processing to be prematurely terminated.  This is,
+  % apparently, because \indexsorttmp is fully expanded, and \endinput
+  % is an expandable command.  The redefinition below makes \endinput
+  % disappear altogether for that purpose -- although logging shows that
+  % processing continues to some further point.  On the other hand, it
+  % seems \endinput does not hurt in the printed index arg, since that
+  % is still getting written without apparent harm.
+  %
+  % Sample source (mac-idx3.tex, reported by Graham Percival to
+  % help-texinfo, 22may06):
+  % @macro funindex {WORD}
+  % @findex xyz
+  % @end macro
+  % ...
+  % @funindex commtest
+  %
+  % The above is not enough to reproduce the bug, but it gives the flavor.
+  %
+  % Sample whatsit resulting:
+  % .@write3{\entry{xyz}{@folio }{@code {xyz@endinput }}}
+  %
+  % So:
+  \let\endinput = \empty
+  %
+  % Do the redefinitions.
+  \commondummies
+}
+
+% For the aux and toc files, @ is the escape character.  So we want to
+% redefine everything using @ as the escape character (instead of
+% \realbackslash, still used for index files).  When everything uses @,
+% this will be simpler.
+%
+\def\atdummies{%
+  \def\@{@@}%
+  \def\ {@ }%
+  \let\{ = \lbraceatcmd
+  \let\} = \rbraceatcmd
+  %
+  % Do the redefinitions.
+  \commondummies
+  \otherbackslash
+}
+
+% Called from \indexdummies and \atdummies.
+%
+\def\commondummies{%
+  %
+  % \definedummyword defines \#1 as \string\#1\space, thus effectively
+  % preventing its expansion.  This is used only for control% words,
+  % not control letters, because the \space would be incorrect for
+  % control characters, but is needed to separate the control word
+  % from whatever follows.
+  %
+  % For control letters, we have \definedummyletter, which omits the
+  % space.
+  %
+  % These can be used both for control words that take an argument and
+  % those that do not.  If it is followed by {arg} in the input, then
+  % that will dutifully get written to the index (or wherever).
+  %
+  \def\definedummyword  ##1{\def##1{\string##1\space}}%
+  \def\definedummyletter##1{\def##1{\string##1}}%
+  \let\definedummyaccent\definedummyletter
+  %
+  \commondummiesnofonts
+  %
+  \definedummyletter\_%
+  %
+  % Non-English letters.
+  \definedummyword\AA
+  \definedummyword\AE
+  \definedummyword\L
+  \definedummyword\OE
+  \definedummyword\O
+  \definedummyword\aa
+  \definedummyword\ae
+  \definedummyword\l
+  \definedummyword\oe
+  \definedummyword\o
+  \definedummyword\ss
+  \definedummyword\exclamdown
+  \definedummyword\questiondown
+  \definedummyword\ordf
+  \definedummyword\ordm
+  %
+  % Although these internal commands shouldn't show up, sometimes they do.
+  \definedummyword\bf
+  \definedummyword\gtr
+  \definedummyword\hat
+  \definedummyword\less
+  \definedummyword\sf
+  \definedummyword\sl
+  \definedummyword\tclose
+  \definedummyword\tt
+  %
+  \definedummyword\LaTeX
+  \definedummyword\TeX
+  %
+  % Assorted special characters.
+  \definedummyword\bullet
+  \definedummyword\comma
+  \definedummyword\copyright
+  \definedummyword\registeredsymbol
+  \definedummyword\dots
+  \definedummyword\enddots
+  \definedummyword\equiv
+  \definedummyword\error
+  \definedummyword\euro
+  \definedummyword\guillemetleft
+  \definedummyword\guillemetright
+  \definedummyword\guilsinglleft
+  \definedummyword\guilsinglright
+  \definedummyword\expansion
+  \definedummyword\minus
+  \definedummyword\pounds
+  \definedummyword\point
+  \definedummyword\print
+  \definedummyword\quotedblbase
+  \definedummyword\quotedblleft
+  \definedummyword\quotedblright
+  \definedummyword\quoteleft
+  \definedummyword\quoteright
+  \definedummyword\quotesinglbase
+  \definedummyword\result
+  \definedummyword\textdegree
+  %
+  % We want to disable all macros so that they are not expanded by \write.
+  \macrolist
+  %
+  \normalturnoffactive
+  %
+  % Handle some cases of @value -- where it does not contain any
+  % (non-fully-expandable) commands.
+  \makevalueexpandable
+}
+
+% \commondummiesnofonts: common to \commondummies and \indexnofonts.
+%
+\def\commondummiesnofonts{%
+  % Control letters and accents.
+  \definedummyletter\!%
+  \definedummyaccent\"%
+  \definedummyaccent\'%
+  \definedummyletter\*%
+  \definedummyaccent\,%
+  \definedummyletter\.%
+  \definedummyletter\/%
+  \definedummyletter\:%
+  \definedummyaccent\=%
+  \definedummyletter\?%
+  \definedummyaccent\^%
+  \definedummyaccent\`%
+  \definedummyaccent\~%
+  \definedummyword\u
+  \definedummyword\v
+  \definedummyword\H
+  \definedummyword\dotaccent
+  \definedummyword\ringaccent
+  \definedummyword\tieaccent
+  \definedummyword\ubaraccent
+  \definedummyword\udotaccent
+  \definedummyword\dotless
+  %
+  % Texinfo font commands.
+  \definedummyword\b
+  \definedummyword\i
+  \definedummyword\r
+  \definedummyword\sc
+  \definedummyword\t
+  %
+  % Commands that take arguments.
+  \definedummyword\acronym
+  \definedummyword\cite
+  \definedummyword\code
+  \definedummyword\command
+  \definedummyword\dfn
+  \definedummyword\emph
+  \definedummyword\env
+  \definedummyword\file
+  \definedummyword\kbd
+  \definedummyword\key
+  \definedummyword\math
+  \definedummyword\option
+  \definedummyword\pxref
+  \definedummyword\ref
+  \definedummyword\samp
+  \definedummyword\strong
+  \definedummyword\tie
+  \definedummyword\uref
+  \definedummyword\url
+  \definedummyword\var
+  \definedummyword\verb
+  \definedummyword\w
+  \definedummyword\xref
+}
+
+% \indexnofonts is used when outputting the strings to sort the index
+% by, and when constructing control sequence names.  It eliminates all
+% control sequences and just writes whatever the best ASCII sort string
+% would be for a given command (usually its argument).
+%
+\def\indexnofonts{%
+  % Accent commands should become @asis.
+  \def\definedummyaccent##1{\let##1\asis}%
+  % We can just ignore other control letters.
+  \def\definedummyletter##1{\let##1\empty}%
+  % Hopefully, all control words can become @asis.
+  \let\definedummyword\definedummyaccent
+  %
+  \commondummiesnofonts
+  %
+  % Don't no-op \tt, since it isn't a user-level command
+  % and is used in the definitions of the active chars like <, >, |, etc.
+  % Likewise with the other plain tex font commands.
+  %\let\tt=\asis
+  %
+  \def\ { }%
+  \def\@{@}%
+  % how to handle braces?
+  \def\_{\normalunderscore}%
+  %
+  % Non-English letters.
+  \def\AA{AA}%
+  \def\AE{AE}%
+  \def\L{L}%
+  \def\OE{OE}%
+  \def\O{O}%
+  \def\aa{aa}%
+  \def\ae{ae}%
+  \def\l{l}%
+  \def\oe{oe}%
+  \def\o{o}%
+  \def\ss{ss}%
+  \def\exclamdown{!}%
+  \def\questiondown{?}%
+  \def\ordf{a}%
+  \def\ordm{o}%
+  %
+  \def\LaTeX{LaTeX}%
+  \def\TeX{TeX}%
+  %
+  % Assorted special characters.
+  % (The following {} will end up in the sort string, but that's ok.)
+  \def\bullet{bullet}%
+  \def\comma{,}%
+  \def\copyright{copyright}%
+  \def\registeredsymbol{R}%
+  \def\dots{...}%
+  \def\enddots{...}%
+  \def\equiv{==}%
+  \def\error{error}%
+  \def\euro{euro}%
+  \def\guillemetleft{<<}%
+  \def\guillemetright{>>}%
+  \def\guilsinglleft{<}%
+  \def\guilsinglright{>}%
+  \def\expansion{==>}%
+  \def\minus{-}%
+  \def\pounds{pounds}%
+  \def\point{.}%
+  \def\print{-|}%
+  \def\quotedblbase{"}%
+  \def\quotedblleft{"}%
+  \def\quotedblright{"}%
+  \def\quoteleft{`}%
+  \def\quoteright{'}%
+  \def\quotesinglbase{,}%
+  \def\result{=>}%
+  \def\textdegree{degrees}%
+  %
+  % We need to get rid of all macros, leaving only the arguments (if present).
+  % Of course this is not nearly correct, but it is the best we can do for now.
+  % makeinfo does not expand macros in the argument to @deffn, which ends up
+  % writing an index entry, and texindex isn't prepared for an index sort entry
+  % that starts with \.
+  %
+  % Since macro invocations are followed by braces, we can just redefine them
+  % to take a single TeX argument.  The case of a macro invocation that
+  % goes to end-of-line is not handled.
+  %
+  \macrolist
+}
+
+\let\indexbackslash=0  %overridden during \printindex.
+\let\SETmarginindex=\relax % put index entries in margin (undocumented)?
+
+% Most index entries go through here, but \dosubind is the general case.
+% #1 is the index name, #2 is the entry text.
+\def\doind#1#2{\dosubind{#1}{#2}{}}
+
+% Workhorse for all \fooindexes.
+% #1 is name of index, #2 is stuff to put there, #3 is subentry --
+% empty if called from \doind, as we usually are (the main exception
+% is with most defuns, which call us directly).
+%
+\def\dosubind#1#2#3{%
+  \iflinks
+  {%
+    % Store the main index entry text (including the third arg).
+    \toks0 = {#2}%
+    % If third arg is present, precede it with a space.
+    \def\thirdarg{#3}%
+    \ifx\thirdarg\empty \else
+      \toks0 = \expandafter{\the\toks0 \space #3}%
+    \fi
+    %
+    \edef\writeto{\csname#1indfile\endcsname}%
+    %
+    \safewhatsit\dosubindwrite
+  }%
+  \fi
+}
+
+% Write the entry in \toks0 to the index file:
+%
+\def\dosubindwrite{%
+  % Put the index entry in the margin if desired.
+  \ifx\SETmarginindex\relax\else
+    \insert\margin{\hbox{\vrule height8pt depth3pt width0pt \the\toks0}}%
+  \fi
+  %
+  % Remember, we are within a group.
+  \indexdummies % Must do this here, since \bf, etc expand at this stage
+  \def\backslashcurfont{\indexbackslash}% \indexbackslash isn't defined now
+      % so it will be output as is; and it will print as backslash.
+  %
+  % Process the index entry with all font commands turned off, to
+  % get the string to sort by.
+  {\indexnofonts
+   \edef\temp{\the\toks0}% need full expansion
+   \xdef\indexsorttmp{\temp}%
+  }%
+  %
+  % Set up the complete index entry, with both the sort key and
+  % the original text, including any font commands.  We write
+  % three arguments to \entry to the .?? file (four in the
+  % subentry case), texindex reduces to two when writing the .??s
+  % sorted result.
+  \edef\temp{%
+    \write\writeto{%
+      \string\entry{\indexsorttmp}{\noexpand\folio}{\the\toks0}}%
+  }%
+  \temp
+}
+
+% Take care of unwanted page breaks/skips around a whatsit:
+%
+% If a skip is the last thing on the list now, preserve it
+% by backing up by \lastskip, doing the \write, then inserting
+% the skip again.  Otherwise, the whatsit generated by the
+% \write or \pdfdest will make \lastskip zero.  The result is that
+% sequences like this:
+% @end defun
+% @tindex whatever
+% @defun ...
+% will have extra space inserted, because the \medbreak in the
+% start of the @defun won't see the skip inserted by the @end of
+% the previous defun.
+%
+% But don't do any of this if we're not in vertical mode.  We
+% don't want to do a \vskip and prematurely end a paragraph.
+%
+% Avoid page breaks due to these extra skips, too.
+%
+% But wait, there is a catch there:
+% We'll have to check whether \lastskip is zero skip.  \ifdim is not
+% sufficient for this purpose, as it ignores stretch and shrink parts
+% of the skip.  The only way seems to be to check the textual
+% representation of the skip.
+%
+% The following is almost like \def\zeroskipmacro{0.0pt} except that
+% the ``p'' and ``t'' characters have catcode \other, not 11 (letter).
+%
+\edef\zeroskipmacro{\expandafter\the\csname z@skip\endcsname}
+%
+\newskip\whatsitskip
+\newcount\whatsitpenalty
+%
+% ..., ready, GO:
+%
+\def\safewhatsit#1{%
+\ifhmode
+  #1%
+\else
+  % \lastskip and \lastpenalty cannot both be nonzero simultaneously.
+  \whatsitskip = \lastskip
+  \edef\lastskipmacro{\the\lastskip}%
+  \whatsitpenalty = \lastpenalty
+  %
+  % If \lastskip is nonzero, that means the last item was a
+  % skip.  And since a skip is discardable, that means this
+  % -\whatsitskip glue we're inserting is preceded by a
+  % non-discardable item, therefore it is not a potential
+  % breakpoint, therefore no \nobreak needed.
+  \ifx\lastskipmacro\zeroskipmacro
+  \else
+    \vskip-\whatsitskip
+  \fi
+  %
+  #1%
+  %
+  \ifx\lastskipmacro\zeroskipmacro
+    % If \lastskip was zero, perhaps the last item was a penalty, and
+    % perhaps it was >=10000, e.g., a \nobreak.  In that case, we want
+    % to re-insert the same penalty (values >10000 are used for various
+    % signals); since we just inserted a non-discardable item, any
+    % following glue (such as a \parskip) would be a breakpoint.  For example:
+    %
+    %   @deffn deffn-whatever
+    %   @vindex index-whatever
+    %   Description.
+    % would allow a break between the index-whatever whatsit
+    % and the "Description." paragraph.
+    \ifnum\whatsitpenalty>9999 \penalty\whatsitpenalty \fi
+  \else
+    % On the other hand, if we had a nonzero \lastskip,
+    % this make-up glue would be preceded by a non-discardable item
+    % (the whatsit from the \write), so we must insert a \nobreak.
+    \nobreak\vskip\whatsitskip
+  \fi
+\fi
+}
+
+% The index entry written in the file actually looks like
+%  \entry {sortstring}{page}{topic}
+% or
+%  \entry {sortstring}{page}{topic}{subtopic}
+% The texindex program reads in these files and writes files
+% containing these kinds of lines:
+%  \initial {c}
+%     before the first topic whose initial is c
+%  \entry {topic}{pagelist}
+%     for a topic that is used without subtopics
+%  \primary {topic}
+%     for the beginning of a topic that is used with subtopics
+%  \secondary {subtopic}{pagelist}
+%     for each subtopic.
+
+% Define the user-accessible indexing commands
+% @findex, @vindex, @kindex, @cindex.
+
+\def\findex {\fnindex}
+\def\kindex {\kyindex}
+\def\cindex {\cpindex}
+\def\vindex {\vrindex}
+\def\tindex {\tpindex}
+\def\pindex {\pgindex}
+
+\def\cindexsub {\begingroup\obeylines\cindexsub}
+{\obeylines %
+\gdef\cindexsub "#1" #2^^M{\endgroup %
+\dosubind{cp}{#2}{#1}}}
+
+% Define the macros used in formatting output of the sorted index material.
+
+% @printindex causes a particular index (the ??s file) to get printed.
+% It does not print any chapter heading (usually an @unnumbered).
+%
+\parseargdef\printindex{\begingroup
+  \dobreak \chapheadingskip{10000}%
+  %
+  \smallfonts \rm
+  \tolerance = 9500
+  \plainfrenchspacing
+  \everypar = {}% don't want the \kern\-parindent from indentation suppression.
+  %
+  % See if the index file exists and is nonempty.
+  % Change catcode of @ here so that if the index file contains
+  % \initial {@}
+  % as its first line, TeX doesn't complain about mismatched braces
+  % (because it thinks @} is a control sequence).
+  \catcode`\@ = 11
+  \openin 1 \jobname.#1s
+  \ifeof 1
+    % \enddoublecolumns gets confused if there is no text in the index,
+    % and it loses the chapter title and the aux file entries for the
+    % index.  The easiest way to prevent this problem is to make sure
+    % there is some text.
+    \putwordIndexNonexistent
+  \else
+    %
+    % If the index file exists but is empty, then \openin leaves \ifeof
+    % false.  We have to make TeX try to read something from the file, so
+    % it can discover if there is anything in it.
+    \read 1 to \temp
+    \ifeof 1
+      \putwordIndexIsEmpty
+    \else
+      % Index files are almost Texinfo source, but we use \ as the escape
+      % character.  It would be better to use @, but that's too big a change
+      % to make right now.
+      \def\indexbackslash{\backslashcurfont}%
+      \catcode`\\ = 0
+      \escapechar = `\\
+      \begindoublecolumns
+      \input \jobname.#1s
+      \enddoublecolumns
+    \fi
+  \fi
+  \closein 1
+\endgroup}
+
+% These macros are used by the sorted index file itself.
+% Change them to control the appearance of the index.
+
+\def\initial#1{{%
+  % Some minor font changes for the special characters.
+  \let\tentt=\sectt \let\tt=\sectt \let\sf=\sectt
+  %
+  % Remove any glue we may have, we'll be inserting our own.
+  \removelastskip
+  %
+  % We like breaks before the index initials, so insert a bonus.
+  \nobreak
+  \vskip 0pt plus 3\baselineskip
+  \penalty 0
+  \vskip 0pt plus -3\baselineskip
+  %
+  % Typeset the initial.  Making this add up to a whole number of
+  % baselineskips increases the chance of the dots lining up from column
+  % to column.  It still won't often be perfect, because of the stretch
+  % we need before each entry, but it's better.
+  %
+  % No shrink because it confuses \balancecolumns.
+  \vskip 1.67\baselineskip plus .5\baselineskip
+  \leftline{\secbf #1}%
+  % Do our best not to break after the initial.
+  \nobreak
+  \vskip .33\baselineskip plus .1\baselineskip
+}}
+
+% \entry typesets a paragraph consisting of the text (#1), dot leaders, and
+% then page number (#2) flushed to the right margin.  It is used for index
+% and table of contents entries.  The paragraph is indented by \leftskip.
+%
+% A straightforward implementation would start like this:
+%      \def\entry#1#2{...
+% But this frozes the catcodes in the argument, and can cause problems to
+% @code, which sets - active.  This problem was fixed by a kludge---
+% ``-'' was active throughout whole index, but this isn't really right.
+%
+% The right solution is to prevent \entry from swallowing the whole text.
+%                                 --kasal, 21nov03
+\def\entry{%
+  \begingroup
+    %
+    % Start a new paragraph if necessary, so our assignments below can't
+    % affect previous text.
+    \par
+    %
+    % Do not fill out the last line with white space.
+    \parfillskip = 0in
+    %
+    % No extra space above this paragraph.
+    \parskip = 0in
+    %
+    % Do not prefer a separate line ending with a hyphen to fewer lines.
+    \finalhyphendemerits = 0
+    %
+    % \hangindent is only relevant when the entry text and page number
+    % don't both fit on one line.  In that case, bob suggests starting the
+    % dots pretty far over on the line.  Unfortunately, a large
+    % indentation looks wrong when the entry text itself is broken across
+    % lines.  So we use a small indentation and put up with long leaders.
+    %
+    % \hangafter is reset to 1 (which is the value we want) at the start
+    % of each paragraph, so we need not do anything with that.
+    \hangindent = 2em
+    %
+    % When the entry text needs to be broken, just fill out the first line
+    % with blank space.
+    \rightskip = 0pt plus1fil
+    %
+    % A bit of stretch before each entry for the benefit of balancing
+    % columns.
+    \vskip 0pt plus1pt
+    %
+    % Swallow the left brace of the text (first parameter):
+    \afterassignment\doentry
+    \let\temp =
+}
+\def\doentry{%
+    \bgroup % Instead of the swallowed brace.
+      \noindent
+      \aftergroup\finishentry
+      % And now comes the text of the entry.
+}
+\def\finishentry#1{%
+    % #1 is the page number.
+    %
+    % The following is kludged to not output a line of dots in the index if
+    % there are no page numbers.  The next person who breaks this will be
+    % cursed by a Unix daemon.
+    \setbox\boxA = \hbox{#1}%
+    \ifdim\wd\boxA = 0pt
+      \ %
+    \else
+      %
+      % If we must, put the page number on a line of its own, and fill out
+      % this line with blank space.  (The \hfil is overwhelmed with the
+      % fill leaders glue in \indexdotfill if the page number does fit.)
+      \hfil\penalty50
+      \null\nobreak\indexdotfill % Have leaders before the page number.
+      %
+      % The `\ ' here is removed by the implicit \unskip that TeX does as
+      % part of (the primitive) \par.  Without it, a spurious underfull
+      % \hbox ensues.
+      \ifpdf
+       \pdfgettoks#1.%
+       \ \the\toksA
+      \else
+       \ #1%
+      \fi
+    \fi
+    \par
+  \endgroup
+}
+
+% Like plain.tex's \dotfill, except uses up at least 1 em.
+\def\indexdotfill{\cleaders
+  \hbox{$\mathsurround=0pt \mkern1.5mu.\mkern1.5mu$}\hskip 1em plus 1fill}
+
+\def\primary #1{\line{#1\hfil}}
+
+\newskip\secondaryindent \secondaryindent=0.5cm
+\def\secondary#1#2{{%
+  \parfillskip=0in
+  \parskip=0in
+  \hangindent=1in
+  \hangafter=1
+  \noindent\hskip\secondaryindent\hbox{#1}\indexdotfill
+  \ifpdf
+    \pdfgettoks#2.\ \the\toksA % The page number ends the paragraph.
+  \else
+    #2
+  \fi
+  \par
+}}
+
+% Define two-column mode, which we use to typeset indexes.
+% Adapted from the TeXbook, page 416, which is to say,
+% the manmac.tex format used to print the TeXbook itself.
+\catcode`\@=11
+
+\newbox\partialpage
+\newdimen\doublecolumnhsize
+
+\def\begindoublecolumns{\begingroup % ended by \enddoublecolumns
+  % Grab any single-column material above us.
+  \output = {%
+    %
+    % Here is a possibility not foreseen in manmac: if we accumulate a
+    % whole lot of material, we might end up calling this \output
+    % routine twice in a row (see the doublecol-lose test, which is
+    % essentially a couple of indexes with @setchapternewpage off).  In
+    % that case we just ship out what is in \partialpage with the normal
+    % output routine.  Generally, \partialpage will be empty when this
+    % runs and this will be a no-op.  See the indexspread.tex test case.
+    \ifvoid\partialpage \else
+      \onepageout{\pagecontents\partialpage}%
+    \fi
+    %
+    \global\setbox\partialpage = \vbox{%
+      % Unvbox the main output page.
+      \unvbox\PAGE
+      \kern-\topskip \kern\baselineskip
+    }%
+  }%
+  \eject % run that output routine to set \partialpage
+  %
+  % Use the double-column output routine for subsequent pages.
+  \output = {\doublecolumnout}%
+  %
+  % Change the page size parameters.  We could do this once outside this
+  % routine, in each of @smallbook, @afourpaper, and the default 8.5x11
+  % format, but then we repeat the same computation.  Repeating a couple
+  % of assignments once per index is clearly meaningless for the
+  % execution time, so we may as well do it in one place.
+  %
+  % First we halve the line length, less a little for the gutter between
+  % the columns.  We compute the gutter based on the line length, so it
+  % changes automatically with the paper format.  The magic constant
+  % below is chosen so that the gutter has the same value (well, +-<1pt)
+  % as it did when we hard-coded it.
+  %
+  % We put the result in a separate register, \doublecolumhsize, so we
+  % can restore it in \pagesofar, after \hsize itself has (potentially)
+  % been clobbered.
+  %
+  \doublecolumnhsize = \hsize
+    \advance\doublecolumnhsize by -.04154\hsize
+    \divide\doublecolumnhsize by 2
+  \hsize = \doublecolumnhsize
+  %
+  % Double the \vsize as well.  (We don't need a separate register here,
+  % since nobody clobbers \vsize.)
+  \vsize = 2\vsize
+}
+
+% The double-column output routine for all double-column pages except
+% the last.
+%
+\def\doublecolumnout{%
+  \splittopskip=\topskip \splitmaxdepth=\maxdepth
+  % Get the available space for the double columns -- the normal
+  % (undoubled) page height minus any material left over from the
+  % previous page.
+  \dimen@ = \vsize
+  \divide\dimen@ by 2
+  \advance\dimen@ by -\ht\partialpage
+  %
+  % box0 will be the left-hand column, box2 the right.
+  \setbox0=\vsplit255 to\dimen@ \setbox2=\vsplit255 to\dimen@
+  \onepageout\pagesofar
+  \unvbox255
+  \penalty\outputpenalty
+}
+%
+% Re-output the contents of the output page -- any previous material,
+% followed by the two boxes we just split, in box0 and box2.
+\def\pagesofar{%
+  \unvbox\partialpage
+  %
+  \hsize = \doublecolumnhsize
+  \wd0=\hsize \wd2=\hsize
+  \hbox to\pagewidth{\box0\hfil\box2}%
+}
+%
+% All done with double columns.
+\def\enddoublecolumns{%
+  % The following penalty ensures that the page builder is exercised
+  % _before_ we change the output routine.  This is necessary in the
+  % following situation:
+  %
+  % The last section of the index consists only of a single entry.
+  % Before this section, \pagetotal is less than \pagegoal, so no
+  % break occurs before the last section starts.  However, the last
+  % section, consisting of \initial and the single \entry, does not
+  % fit on the page and has to be broken off.  Without the following
+  % penalty the page builder will not be exercised until \eject
+  % below, and by that time we'll already have changed the output
+  % routine to the \balancecolumns version, so the next-to-last
+  % double-column page will be processed with \balancecolumns, which
+  % is wrong:  The two columns will go to the main vertical list, with
+  % the broken-off section in the recent contributions.  As soon as
+  % the output routine finishes, TeX starts reconsidering the page
+  % break.  The two columns and the broken-off section both fit on the
+  % page, because the two columns now take up only half of the page
+  % goal.  When TeX sees \eject from below which follows the final
+  % section, it invokes the new output routine that we've set after
+  % \balancecolumns below; \onepageout will try to fit the two columns
+  % and the final section into the vbox of \pageheight (see
+  % \pagebody), causing an overfull box.
+  %
+  % Note that glue won't work here, because glue does not exercise the
+  % page builder, unlike penalties (see The TeXbook, pp. 280-281).
+  \penalty0
+  %
+  \output = {%
+    % Split the last of the double-column material.  Leave it on the
+    % current page, no automatic page break.
+    \balancecolumns
+    %
+    % If we end up splitting too much material for the current page,
+    % though, there will be another page break right after this \output
+    % invocation ends.  Having called \balancecolumns once, we do not
+    % want to call it again.  Therefore, reset \output to its normal
+    % definition right away.  (We hope \balancecolumns will never be
+    % called on to balance too much material, but if it is, this makes
+    % the output somewhat more palatable.)
+    \global\output = {\onepageout{\pagecontents\PAGE}}%
+  }%
+  \eject
+  \endgroup % started in \begindoublecolumns
+  %
+  % \pagegoal was set to the doubled \vsize above, since we restarted
+  % the current page.  We're now back to normal single-column
+  % typesetting, so reset \pagegoal to the normal \vsize (after the
+  % \endgroup where \vsize got restored).
+  \pagegoal = \vsize
+}
+%
+% Called at the end of the double column material.
+\def\balancecolumns{%
+  \setbox0 = \vbox{\unvbox255}% like \box255 but more efficient, see p.120.
+  \dimen@ = \ht0
+  \advance\dimen@ by \topskip
+  \advance\dimen@ by-\baselineskip
+  \divide\dimen@ by 2 % target to split to
+  %debug\message{final 2-column material height=\the\ht0, target=\the\dimen@.}%
+  \splittopskip = \topskip
+  % Loop until we get a decent breakpoint.
+  {%
+    \vbadness = 10000
+    \loop
+      \global\setbox3 = \copy0
+      \global\setbox1 = \vsplit3 to \dimen@
+    \ifdim\ht3>\dimen@
+      \global\advance\dimen@ by 1pt
+    \repeat
+  }%
+  %debug\message{split to \the\dimen@, column heights: \the\ht1, \the\ht3.}%
+  \setbox0=\vbox to\dimen@{\unvbox1}%
+  \setbox2=\vbox to\dimen@{\unvbox3}%
+  %
+  \pagesofar
+}
+\catcode`\@ = \other
+
+
+\message{sectioning,}
+% Chapters, sections, etc.
+
+% \unnumberedno is an oxymoron, of course.  But we count the unnumbered
+% sections so that we can refer to them unambiguously in the pdf
+% outlines by their "section number".  We avoid collisions with chapter
+% numbers by starting them at 10000.  (If a document ever has 10000
+% chapters, we're in trouble anyway, I'm sure.)
+\newcount\unnumberedno \unnumberedno = 10000
+\newcount\chapno
+\newcount\secno        \secno=0
+\newcount\subsecno     \subsecno=0
+\newcount\subsubsecno  \subsubsecno=0
+
+% This counter is funny since it counts through charcodes of letters A, B, ...
+\newcount\appendixno  \appendixno = `\@
+%
+% \def\appendixletter{\char\the\appendixno}
+% We do the following ugly conditional instead of the above simple
+% construct for the sake of pdftex, which needs the actual
+% letter in the expansion, not just typeset.
+%
+\def\appendixletter{%
+  \ifnum\appendixno=`A A%
+  \else\ifnum\appendixno=`B B%
+  \else\ifnum\appendixno=`C C%
+  \else\ifnum\appendixno=`D D%
+  \else\ifnum\appendixno=`E E%
+  \else\ifnum\appendixno=`F F%
+  \else\ifnum\appendixno=`G G%
+  \else\ifnum\appendixno=`H H%
+  \else\ifnum\appendixno=`I I%
+  \else\ifnum\appendixno=`J J%
+  \else\ifnum\appendixno=`K K%
+  \else\ifnum\appendixno=`L L%
+  \else\ifnum\appendixno=`M M%
+  \else\ifnum\appendixno=`N N%
+  \else\ifnum\appendixno=`O O%
+  \else\ifnum\appendixno=`P P%
+  \else\ifnum\appendixno=`Q Q%
+  \else\ifnum\appendixno=`R R%
+  \else\ifnum\appendixno=`S S%
+  \else\ifnum\appendixno=`T T%
+  \else\ifnum\appendixno=`U U%
+  \else\ifnum\appendixno=`V V%
+  \else\ifnum\appendixno=`W W%
+  \else\ifnum\appendixno=`X X%
+  \else\ifnum\appendixno=`Y Y%
+  \else\ifnum\appendixno=`Z Z%
+  % The \the is necessary, despite appearances, because \appendixletter is
+  % expanded while writing the .toc file.  \char\appendixno is not
+  % expandable, thus it is written literally, thus all appendixes come out
+  % with the same letter (or @) in the toc without it.
+  \else\char\the\appendixno
+  \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
+  \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi}
+
+% Each @chapter defines these (using marks) as the number+name, number
+% and name of the chapter.  Page headings and footings can use
+% these.  @section does likewise.
+\def\thischapter{}
+\def\thischapternum{}
+\def\thischaptername{}
+\def\thissection{}
+\def\thissectionnum{}
+\def\thissectionname{}
+
+\newcount\absseclevel % used to calculate proper heading level
+\newcount\secbase\secbase=0 % @raisesections/@lowersections modify this count
+
+% @raisesections: treat @section as chapter, @subsection as section, etc.
+\def\raisesections{\global\advance\secbase by -1}
+\let\up=\raisesections % original BFox name
+
+% @lowersections: treat @chapter as section, @section as subsection, etc.
+\def\lowersections{\global\advance\secbase by 1}
+\let\down=\lowersections % original BFox name
+
+% we only have subsub.
+\chardef\maxseclevel = 3
+%
+% A numbered section within an unnumbered changes to unnumbered too.
+% To achive this, remember the "biggest" unnum. sec. we are currently in:
+\chardef\unmlevel = \maxseclevel
+%
+% Trace whether the current chapter is an appendix or not:
+% \chapheadtype is "N" or "A", unnumbered chapters are ignored.
+\def\chapheadtype{N}
+
+% Choose a heading macro
+% #1 is heading type
+% #2 is heading level
+% #3 is text for heading
+\def\genhead#1#2#3{%
+  % Compute the abs. sec. level:
+  \absseclevel=#2
+  \advance\absseclevel by \secbase
+  % Make sure \absseclevel doesn't fall outside the range:
+  \ifnum \absseclevel < 0
+    \absseclevel = 0
+  \else
+    \ifnum \absseclevel > 3
+      \absseclevel = 3
+    \fi
+  \fi
+  % The heading type:
+  \def\headtype{#1}%
+  \if \headtype U%
+    \ifnum \absseclevel < \unmlevel
+      \chardef\unmlevel = \absseclevel
+    \fi
+  \else
+    % Check for appendix sections:
+    \ifnum \absseclevel = 0
+      \edef\chapheadtype{\headtype}%
+    \else
+      \if \headtype A\if \chapheadtype N%
+       \errmessage{@appendix... within a non-appendix chapter}%
+      \fi\fi
+    \fi
+    % Check for numbered within unnumbered:
+    \ifnum \absseclevel > \unmlevel
+      \def\headtype{U}%
+    \else
+      \chardef\unmlevel = 3
+    \fi
+  \fi
+  % Now print the heading:
+  \if \headtype U%
+    \ifcase\absseclevel
+       \unnumberedzzz{#3}%
+    \or \unnumberedseczzz{#3}%
+    \or \unnumberedsubseczzz{#3}%
+    \or \unnumberedsubsubseczzz{#3}%
+    \fi
+  \else
+    \if \headtype A%
+      \ifcase\absseclevel
+         \appendixzzz{#3}%
+      \or \appendixsectionzzz{#3}%
+      \or \appendixsubseczzz{#3}%
+      \or \appendixsubsubseczzz{#3}%
+      \fi
+    \else
+      \ifcase\absseclevel
+         \chapterzzz{#3}%
+      \or \seczzz{#3}%
+      \or \numberedsubseczzz{#3}%
+      \or \numberedsubsubseczzz{#3}%
+      \fi
+    \fi
+  \fi
+  \suppressfirstparagraphindent
+}
+
+% an interface:
+\def\numhead{\genhead N}
+\def\apphead{\genhead A}
+\def\unnmhead{\genhead U}
+
+% @chapter, @appendix, @unnumbered.  Increment top-level counter, reset
+% all lower-level sectioning counters to zero.
+%
+% Also set \chaplevelprefix, which we prepend to @float sequence numbers
+% (e.g., figures), q.v.  By default (before any chapter), that is empty.
+\let\chaplevelprefix = \empty
+%
+\outer\parseargdef\chapter{\numhead0{#1}} % normally numhead0 calls chapterzzz
+\def\chapterzzz#1{%
+  % section resetting is \global in case the chapter is in a group, such
+  % as an @include file.
+  \global\secno=0 \global\subsecno=0 \global\subsubsecno=0
+    \global\advance\chapno by 1
+  %
+  % Used for \float.
+  \gdef\chaplevelprefix{\the\chapno.}%
+  \resetallfloatnos
+  %
+  \message{\putwordChapter\space \the\chapno}%
+  %
+  % Write the actual heading.
+  \chapmacro{#1}{Ynumbered}{\the\chapno}%
+  %
+  % So @section and the like are numbered underneath this chapter.
+  \global\let\section = \numberedsec
+  \global\let\subsection = \numberedsubsec
+  \global\let\subsubsection = \numberedsubsubsec
+}
+
+\outer\parseargdef\appendix{\apphead0{#1}} % normally apphead0 calls appendixzzz
+\def\appendixzzz#1{%
+  \global\secno=0 \global\subsecno=0 \global\subsubsecno=0
+    \global\advance\appendixno by 1
+  \gdef\chaplevelprefix{\appendixletter.}%
+  \resetallfloatnos
+  %
+  \def\appendixnum{\putwordAppendix\space \appendixletter}%
+  \message{\appendixnum}%
+  %
+  \chapmacro{#1}{Yappendix}{\appendixletter}%
+  %
+  \global\let\section = \appendixsec
+  \global\let\subsection = \appendixsubsec
+  \global\let\subsubsection = \appendixsubsubsec
+}
+
+\outer\parseargdef\unnumbered{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz
+\def\unnumberedzzz#1{%
+  \global\secno=0 \global\subsecno=0 \global\subsubsecno=0
+    \global\advance\unnumberedno by 1
+  %
+  % Since an unnumbered has no number, no prefix for figures.
+  \global\let\chaplevelprefix = \empty
+  \resetallfloatnos
+  %
+  % This used to be simply \message{#1}, but TeX fully expands the
+  % argument to \message.  Therefore, if #1 contained @-commands, TeX
+  % expanded them.  For example, in `@unnumbered The @cite{Book}', TeX
+  % expanded @cite (which turns out to cause errors because \cite is meant
+  % to be executed, not expanded).
+  %
+  % Anyway, we don't want the fully-expanded definition of @cite to appear
+  % as a result of the \message, we just want `@cite' itself.  We use
+  % \the<toks register> to achieve this: TeX expands \the<toks> only once,
+  % simply yielding the contents of <toks register>.  (We also do this for
+  % the toc entries.)
+  \toks0 = {#1}%
+  \message{(\the\toks0)}%
+  %
+  \chapmacro{#1}{Ynothing}{\the\unnumberedno}%
+  %
+  \global\let\section = \unnumberedsec
+  \global\let\subsection = \unnumberedsubsec
+  \global\let\subsubsection = \unnumberedsubsubsec
+}
+
+% @centerchap is like @unnumbered, but the heading is centered.
+\outer\parseargdef\centerchap{%
+  % Well, we could do the following in a group, but that would break
+  % an assumption that \chapmacro is called at the outermost level.
+  % Thus we are safer this way:                --kasal, 24feb04
+  \let\centerparametersmaybe = \centerparameters
+  \unnmhead0{#1}%
+  \let\centerparametersmaybe = \relax
+}
+
+% @top is like @unnumbered.
+\let\top\unnumbered
+
+% Sections.
+\outer\parseargdef\numberedsec{\numhead1{#1}} % normally calls seczzz
+\def\seczzz#1{%
+  \global\subsecno=0 \global\subsubsecno=0  \global\advance\secno by 1
+  \sectionheading{#1}{sec}{Ynumbered}{\the\chapno.\the\secno}%
+}
+
+\outer\parseargdef\appendixsection{\apphead1{#1}} % normally calls appendixsectionzzz
+\def\appendixsectionzzz#1{%
+  \global\subsecno=0 \global\subsubsecno=0  \global\advance\secno by 1
+  \sectionheading{#1}{sec}{Yappendix}{\appendixletter.\the\secno}%
+}
+\let\appendixsec\appendixsection
+
+\outer\parseargdef\unnumberedsec{\unnmhead1{#1}} % normally calls unnumberedseczzz
+\def\unnumberedseczzz#1{%
+  \global\subsecno=0 \global\subsubsecno=0  \global\advance\secno by 1
+  \sectionheading{#1}{sec}{Ynothing}{\the\unnumberedno.\the\secno}%
+}
+
+% Subsections.
+\outer\parseargdef\numberedsubsec{\numhead2{#1}} % normally calls numberedsubseczzz
+\def\numberedsubseczzz#1{%
+  \global\subsubsecno=0  \global\advance\subsecno by 1
+  \sectionheading{#1}{subsec}{Ynumbered}{\the\chapno.\the\secno.\the\subsecno}%
+}
+
+\outer\parseargdef\appendixsubsec{\apphead2{#1}} % normally calls appendixsubseczzz
+\def\appendixsubseczzz#1{%
+  \global\subsubsecno=0  \global\advance\subsecno by 1
+  \sectionheading{#1}{subsec}{Yappendix}%
+                 {\appendixletter.\the\secno.\the\subsecno}%
+}
+
+\outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} %normally calls unnumberedsubseczzz
+\def\unnumberedsubseczzz#1{%
+  \global\subsubsecno=0  \global\advance\subsecno by 1
+  \sectionheading{#1}{subsec}{Ynothing}%
+                 {\the\unnumberedno.\the\secno.\the\subsecno}%
+}
+
+% Subsubsections.
+\outer\parseargdef\numberedsubsubsec{\numhead3{#1}} % normally numberedsubsubseczzz
+\def\numberedsubsubseczzz#1{%
+  \global\advance\subsubsecno by 1
+  \sectionheading{#1}{subsubsec}{Ynumbered}%
+                 {\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno}%
+}
+
+\outer\parseargdef\appendixsubsubsec{\apphead3{#1}} % normally appendixsubsubseczzz
+\def\appendixsubsubseczzz#1{%
+  \global\advance\subsubsecno by 1
+  \sectionheading{#1}{subsubsec}{Yappendix}%
+                 {\appendixletter.\the\secno.\the\subsecno.\the\subsubsecno}%
+}
+
+\outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} %normally unnumberedsubsubseczzz
+\def\unnumberedsubsubseczzz#1{%
+  \global\advance\subsubsecno by 1
+  \sectionheading{#1}{subsubsec}{Ynothing}%
+                 {\the\unnumberedno.\the\secno.\the\subsecno.\the\subsubsecno}%
+}
+
+% These macros control what the section commands do, according
+% to what kind of chapter we are in (ordinary, appendix, or unnumbered).
+% Define them by default for a numbered chapter.
+\let\section = \numberedsec
+\let\subsection = \numberedsubsec
+\let\subsubsection = \numberedsubsubsec
+
+% Define @majorheading, @heading and @subheading
+
+% NOTE on use of \vbox for chapter headings, section headings, and such:
+%       1) We use \vbox rather than the earlier \line to permit
+%          overlong headings to fold.
+%       2) \hyphenpenalty is set to 10000 because hyphenation in a
+%          heading is obnoxious; this forbids it.
+%       3) Likewise, headings look best if no \parindent is used, and
+%          if justification is not attempted.  Hence \raggedright.
+
+
+\def\majorheading{%
+  {\advance\chapheadingskip by 10pt \chapbreak }%
+  \parsearg\chapheadingzzz
+}
+
+\def\chapheading{\chapbreak \parsearg\chapheadingzzz}
+\def\chapheadingzzz#1{%
+  {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                    \parindent=0pt\raggedright
+                    \rm #1\hfill}}%
+  \bigskip \par\penalty 200\relax
+  \suppressfirstparagraphindent
+}
+
+% @heading, @subheading, @subsubheading.
+\parseargdef\heading{\sectionheading{#1}{sec}{Yomitfromtoc}{}
+  \suppressfirstparagraphindent}
+\parseargdef\subheading{\sectionheading{#1}{subsec}{Yomitfromtoc}{}
+  \suppressfirstparagraphindent}
+\parseargdef\subsubheading{\sectionheading{#1}{subsubsec}{Yomitfromtoc}{}
+  \suppressfirstparagraphindent}
+
+% These macros generate a chapter, section, etc. heading only
+% (including whitespace, linebreaking, etc. around it),
+% given all the information in convenient, parsed form.
+
+%%% Args are the skip and penalty (usually negative)
+\def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi}
+
+%%% Define plain chapter starts, and page on/off switching for it
+% Parameter controlling skip before chapter headings (if needed)
+
+\newskip\chapheadingskip
+
+\def\chapbreak{\dobreak \chapheadingskip {-4000}}
+\def\chappager{\par\vfill\supereject}
+% Because \domark is called before \chapoddpage, the filler page will
+% get the headings for the next chapter, which is wrong.  But we don't
+% care -- we just disable all headings on the filler page.
+\def\chapoddpage{%
+  \chappager
+  \ifodd\pageno \else
+    \begingroup
+      \evenheadline={\hfil}\evenfootline={\hfil}%
+      \oddheadline={\hfil}\oddfootline={\hfil}%
+      \hbox to 0pt{}%
+      \chappager
+    \endgroup
+  \fi
+}
+
+\def\setchapternewpage #1 {\csname CHAPPAG#1\endcsname}
+
+\def\CHAPPAGoff{%
+\global\let\contentsalignmacro = \chappager
+\global\let\pchapsepmacro=\chapbreak
+\global\let\pagealignmacro=\chappager}
+
+\def\CHAPPAGon{%
+\global\let\contentsalignmacro = \chappager
+\global\let\pchapsepmacro=\chappager
+\global\let\pagealignmacro=\chappager
+\global\def\HEADINGSon{\HEADINGSsingle}}
+
+\def\CHAPPAGodd{%
+\global\let\contentsalignmacro = \chapoddpage
+\global\let\pchapsepmacro=\chapoddpage
+\global\let\pagealignmacro=\chapoddpage
+\global\def\HEADINGSon{\HEADINGSdouble}}
+
+\CHAPPAGon
+
+% Chapter opening.
+%
+% #1 is the text, #2 is the section type (Ynumbered, Ynothing,
+% Yappendix, Yomitfromtoc), #3 the chapter number.
+%
+% To test against our argument.
+\def\Ynothingkeyword{Ynothing}
+\def\Yomitfromtockeyword{Yomitfromtoc}
+\def\Yappendixkeyword{Yappendix}
+%
+\def\chapmacro#1#2#3{%
+  % Insert the first mark before the heading break (see notes for \domark).
+  \let\prevchapterdefs=\lastchapterdefs
+  \let\prevsectiondefs=\lastsectiondefs
+  \gdef\lastsectiondefs{\gdef\thissectionname{}\gdef\thissectionnum{}%
+                        \gdef\thissection{}}%
+  %
+  \def\temptype{#2}%
+  \ifx\temptype\Ynothingkeyword
+    \gdef\lastchapterdefs{\gdef\thischaptername{#1}\gdef\thischapternum{}%
+                          \gdef\thischapter{\thischaptername}}%
+  \else\ifx\temptype\Yomitfromtockeyword
+    \gdef\lastchapterdefs{\gdef\thischaptername{#1}\gdef\thischapternum{}%
+                          \gdef\thischapter{}}%
+  \else\ifx\temptype\Yappendixkeyword
+    \toks0={#1}%
+    \xdef\lastchapterdefs{%
+      \gdef\noexpand\thischaptername{\the\toks0}%
+      \gdef\noexpand\thischapternum{\appendixletter}%
+      \gdef\noexpand\thischapter{\putwordAppendix{} \noexpand\thischapternum:
+                                 \noexpand\thischaptername}%
+    }%
+  \else
+    \toks0={#1}%
+    \xdef\lastchapterdefs{%
+      \gdef\noexpand\thischaptername{\the\toks0}%
+      \gdef\noexpand\thischapternum{\the\chapno}%
+      \gdef\noexpand\thischapter{\putwordChapter{} \noexpand\thischapternum:
+                                 \noexpand\thischaptername}%
+    }%
+  \fi\fi\fi
+  %
+  % Output the mark.  Pass it through \safewhatsit, to take care of
+  % the preceding space.
+  \safewhatsit\domark
+  %
+  % Insert the chapter heading break.
+  \pchapsepmacro
+  %
+  % Now the second mark, after the heading break.  No break points
+  % between here and the heading.
+  \let\prevchapterdefs=\lastchapterdefs
+  \let\prevsectiondefs=\lastsectiondefs
+  \domark
+  %
+  {%
+    \chapfonts \rm
+    %
+    % Have to define \lastsection before calling \donoderef, because the
+    % xref code eventually uses it.  On the other hand, it has to be called
+    % after \pchapsepmacro, or the headline will change too soon.
+    \gdef\lastsection{#1}%
+    %
+    % Only insert the separating space if we have a chapter/appendix
+    % number, and don't print the unnumbered ``number''.
+    \ifx\temptype\Ynothingkeyword
+      \setbox0 = \hbox{}%
+      \def\toctype{unnchap}%
+    \else\ifx\temptype\Yomitfromtockeyword
+      \setbox0 = \hbox{}% contents like unnumbered, but no toc entry
+      \def\toctype{omit}%
+    \else\ifx\temptype\Yappendixkeyword
+      \setbox0 = \hbox{\putwordAppendix{} #3\enspace}%
+      \def\toctype{app}%
+    \else
+      \setbox0 = \hbox{#3\enspace}%
+      \def\toctype{numchap}%
+    \fi\fi\fi
+    %
+    % Write the toc entry for this chapter.  Must come before the
+    % \donoderef, because we include the current node name in the toc
+    % entry, and \donoderef resets it to empty.
+    \writetocentry{\toctype}{#1}{#3}%
+    %
+    % For pdftex, we have to write out the node definition (aka, make
+    % the pdfdest) after any page break, but before the actual text has
+    % been typeset.  If the destination for the pdf outline is after the
+    % text, then jumping from the outline may wind up with the text not
+    % being visible, for instance under high magnification.
+    \donoderef{#2}%
+    %
+    % Typeset the actual heading.
+    \nobreak % Avoid page breaks at the interline glue.
+    \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright
+          \hangindent=\wd0 \centerparametersmaybe
+          \unhbox0 #1\par}%
+  }%
+  \nobreak\bigskip % no page break after a chapter title
+  \nobreak
+}
+
+% @centerchap -- centered and unnumbered.
+\let\centerparametersmaybe = \relax
+\def\centerparameters{%
+  \advance\rightskip by 3\rightskip
+  \leftskip = \rightskip
+  \parfillskip = 0pt
+}
+
+
+% I don't think this chapter style is supported any more, so I'm not
+% updating it with the new noderef stuff.  We'll see.  --karl, 11aug03.
+%
+\def\setchapterstyle #1 {\csname CHAPF#1\endcsname}
+%
+\def\unnchfopen #1{%
+\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                       \parindent=0pt\raggedright
+                       \rm #1\hfill}}\bigskip \par\nobreak
+}
+\def\chfopen #1#2{\chapoddpage {\chapfonts
+\vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}%
+\par\penalty 5000 %
+}
+\def\centerchfopen #1{%
+\chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000
+                       \parindent=0pt
+                       \hfill {\rm #1}\hfill}}\bigskip \par\nobreak
+}
+\def\CHAPFopen{%
+  \global\let\chapmacro=\chfopen
+  \global\let\centerchapmacro=\centerchfopen}
+
+
+% Section titles.  These macros combine the section number parts and
+% call the generic \sectionheading to do the printing.
+%
+\newskip\secheadingskip
+\def\secheadingbreak{\dobreak \secheadingskip{-1000}}
+
+% Subsection titles.
+\newskip\subsecheadingskip
+\def\subsecheadingbreak{\dobreak \subsecheadingskip{-500}}
+
+% Subsubsection titles.
+\def\subsubsecheadingskip{\subsecheadingskip}
+\def\subsubsecheadingbreak{\subsecheadingbreak}
+
+
+% Print any size, any type, section title.
+%
+% #1 is the text, #2 is the section level (sec/subsec/subsubsec), #3 is
+% the section type for xrefs (Ynumbered, Ynothing, Yappendix), #4 is the
+% section number.
+%
+\def\seckeyword{sec}
+%
+\def\sectionheading#1#2#3#4{%
+  {%
+    % Switch to the right set of fonts.
+    \csname #2fonts\endcsname \rm
+    %
+    \def\sectionlevel{#2}%
+    \def\temptype{#3}%
+    %
+    % Insert first mark before the heading break (see notes for \domark).
+    \let\prevsectiondefs=\lastsectiondefs
+    \ifx\temptype\Ynothingkeyword
+      \ifx\sectionlevel\seckeyword
+        \gdef\lastsectiondefs{\gdef\thissectionname{#1}\gdef\thissectionnum{}%
+                              \gdef\thissection{\thissectionname}}%
+      \fi
+    \else\ifx\temptype\Yomitfromtockeyword
+      % Don't redefine \thissection.
+    \else\ifx\temptype\Yappendixkeyword
+      \ifx\sectionlevel\seckeyword
+        \toks0={#1}%
+        \xdef\lastsectiondefs{%
+          \gdef\noexpand\thissectionname{\the\toks0}%
+          \gdef\noexpand\thissectionnum{#4}%
+          \gdef\noexpand\thissection{\putwordSection{} \noexpand\thissectionnum:
+                                     \noexpand\thissectionname}%
+        }%
+      \fi
+    \else
+      \ifx\sectionlevel\seckeyword
+        \toks0={#1}%
+        \xdef\lastsectiondefs{%
+          \gdef\noexpand\thissectionname{\the\toks0}%
+          \gdef\noexpand\thissectionnum{#4}%
+          \gdef\noexpand\thissection{\putwordSection{} \noexpand\thissectionnum:
+                                     \noexpand\thissectionname}%
+        }%
+      \fi
+    \fi\fi\fi
+    %
+    % Output the mark.  Pass it through \safewhatsit, to take care of
+    % the preceding space.
+    \safewhatsit\domark
+    %
+    % Insert space above the heading.
+    \csname #2headingbreak\endcsname
+    %
+    % Now the second mark, after the heading break.  No break points
+    % between here and the heading.
+    \let\prevsectiondefs=\lastsectiondefs
+    \domark
+    %
+    % Only insert the space after the number if we have a section number.
+    \ifx\temptype\Ynothingkeyword
+      \setbox0 = \hbox{}%
+      \def\toctype{unn}%
+      \gdef\lastsection{#1}%
+    \else\ifx\temptype\Yomitfromtockeyword
+      % for @headings -- no section number, don't include in toc,
+      % and don't redefine \lastsection.
+      \setbox0 = \hbox{}%
+      \def\toctype{omit}%
+      \let\sectionlevel=\empty
+    \else\ifx\temptype\Yappendixkeyword
+      \setbox0 = \hbox{#4\enspace}%
+      \def\toctype{app}%
+      \gdef\lastsection{#1}%
+    \else
+      \setbox0 = \hbox{#4\enspace}%
+      \def\toctype{num}%
+      \gdef\lastsection{#1}%
+    \fi\fi\fi
+    %
+    % Write the toc entry (before \donoderef).  See comments in \chapmacro.
+    \writetocentry{\toctype\sectionlevel}{#1}{#4}%
+    %
+    % Write the node reference (= pdf destination for pdftex).
+    % Again, see comments in \chapmacro.
+    \donoderef{#3}%
+    %
+    % Interline glue will be inserted when the vbox is completed.
+    % That glue will be a valid breakpoint for the page, since it'll be
+    % preceded by a whatsit (usually from the \donoderef, or from the
+    % \writetocentry if there was no node).  We don't want to allow that
+    % break, since then the whatsits could end up on page n while the
+    % section is on page n+1, thus toc/etc. are wrong.  Debian bug 276000.
+    \nobreak
+    %
+    % Output the actual section heading.
+    \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright
+          \hangindent=\wd0  % zero if no section number
+          \unhbox0 #1}%
+  }%
+  % Add extra space after the heading -- half of whatever came above it.
+  % Don't allow stretch, though.
+  \kern .5 \csname #2headingskip\endcsname
+  %
+  % Do not let the kern be a potential breakpoint, as it would be if it
+  % was followed by glue.
+  \nobreak
+  %
+  % We'll almost certainly start a paragraph next, so don't let that
+  % glue accumulate.  (Not a breakpoint because it's preceded by a
+  % discardable item.)
+  \vskip-\parskip
+  %
+  % This is purely so the last item on the list is a known \penalty >
+  % 10000.  This is so \startdefun can avoid allowing breakpoints after
+  % section headings.  Otherwise, it would insert a valid breakpoint between:
+  %
+  %   @section sec-whatever
+  %   @deffn def-whatever
+  \penalty 10001
+}
+
+
+\message{toc,}
+% Table of contents.
+\newwrite\tocfile
+
+% Write an entry to the toc file, opening it if necessary.
+% Called from @chapter, etc.
+%
+% Example usage: \writetocentry{sec}{Section Name}{\the\chapno.\the\secno}
+% We append the current node name (if any) and page number as additional
+% arguments for the \{chap,sec,...}entry macros which will eventually
+% read this.  The node name is used in the pdf outlines as the
+% destination to jump to.
+%
+% We open the .toc file for writing here instead of at @setfilename (or
+% any other fixed time) so that @contents can be anywhere in the document.
+% But if #1 is `omit', then we don't do anything.  This is used for the
+% table of contents chapter openings themselves.
+%
+\newif\iftocfileopened
+\def\omitkeyword{omit}%
+%
+\def\writetocentry#1#2#3{%
+  \edef\writetoctype{#1}%
+  \ifx\writetoctype\omitkeyword \else
+    \iftocfileopened\else
+      \immediate\openout\tocfile = \jobname.toc
+      \global\tocfileopenedtrue
+    \fi
+    %
+    \iflinks
+      {\atdummies
+       \edef\temp{%
+         \write\tocfile{@#1entry{#2}{#3}{\lastnode}{\noexpand\folio}}}%
+       \temp
+      }%
+    \fi
+  \fi
+  %
+  % Tell \shipout to create a pdf destination on each page, if we're
+  % writing pdf.  These are used in the table of contents.  We can't
+  % just write one on every page because the title pages are numbered
+  % 1 and 2 (the page numbers aren't printed), and so are the first
+  % two pages of the document.  Thus, we'd have two destinations named
+  % `1', and two named `2'.
+  \ifpdf \global\pdfmakepagedesttrue \fi
+}
+
+
+% These characters do not print properly in the Computer Modern roman
+% fonts, so we must take special care.  This is more or less redundant
+% with the Texinfo input format setup at the end of this file.
+%
+\def\activecatcodes{%
+  \catcode`\"=\active
+  \catcode`\$=\active
+  \catcode`\<=\active
+  \catcode`\>=\active
+  \catcode`\\=\active
+  \catcode`\^=\active
+  \catcode`\_=\active
+  \catcode`\|=\active
+  \catcode`\~=\active
+}
+
+
+% Read the toc file, which is essentially Texinfo input.
+\def\readtocfile{%
+  \setupdatafile
+  \activecatcodes
+  \input \tocreadfilename
+}
+
+\newskip\contentsrightmargin \contentsrightmargin=1in
+\newcount\savepageno
+\newcount\lastnegativepageno \lastnegativepageno = -1
+
+% Prepare to read what we've written to \tocfile.
+%
+\def\startcontents#1{%
+  % If @setchapternewpage on, and @headings double, the contents should
+  % start on an odd page, unlike chapters.  Thus, we maintain
+  % \contentsalignmacro in parallel with \pagealignmacro.
+  % From: Torbjorn Granlund <tege@matematik.su.se>
+  \contentsalignmacro
+  \immediate\closeout\tocfile
+  %
+  % Don't need to put `Contents' or `Short Contents' in the headline.
+  % It is abundantly clear what they are.
+  \chapmacro{#1}{Yomitfromtoc}{}%
+  %
+  \savepageno = \pageno
+  \begingroup                  % Set up to handle contents files properly.
+    \raggedbottom              % Worry more about breakpoints than the bottom.
+    \advance\hsize by -\contentsrightmargin % Don't use the full line length.
+    %
+    % Roman numerals for page numbers.
+    \ifnum \pageno>0 \global\pageno = \lastnegativepageno \fi
+}
+
+% redefined for the two-volume lispref.  We always output on
+% \jobname.toc even if this is redefined.
+%
+\def\tocreadfilename{\jobname.toc}
+
+% Normal (long) toc.
+%
+\def\contents{%
+  \startcontents{\putwordTOC}%
+    \openin 1 \tocreadfilename\space
+    \ifeof 1 \else
+      \readtocfile
+    \fi
+    \vfill \eject
+    \contentsalignmacro % in case @setchapternewpage odd is in effect
+    \ifeof 1 \else
+      \pdfmakeoutlines
+    \fi
+    \closein 1
+  \endgroup
+  \lastnegativepageno = \pageno
+  \global\pageno = \savepageno
+}
+
+% And just the chapters.
+\def\summarycontents{%
+  \startcontents{\putwordShortTOC}%
+    %
+    \let\numchapentry = \shortchapentry
+    \let\appentry = \shortchapentry
+    \let\unnchapentry = \shortunnchapentry
+    % We want a true roman here for the page numbers.
+    \secfonts
+    \let\rm=\shortcontrm \let\bf=\shortcontbf
+    \let\sl=\shortcontsl \let\tt=\shortconttt
+    \rm
+    \hyphenpenalty = 10000
+    \advance\baselineskip by 1pt % Open it up a little.
+    \def\numsecentry##1##2##3##4{}
+    \let\appsecentry = \numsecentry
+    \let\unnsecentry = \numsecentry
+    \let\numsubsecentry = \numsecentry
+    \let\appsubsecentry = \numsecentry
+    \let\unnsubsecentry = \numsecentry
+    \let\numsubsubsecentry = \numsecentry
+    \let\appsubsubsecentry = \numsecentry
+    \let\unnsubsubsecentry = \numsecentry
+    \openin 1 \tocreadfilename\space
+    \ifeof 1 \else
+      \readtocfile
+    \fi
+    \closein 1
+    \vfill \eject
+    \contentsalignmacro % in case @setchapternewpage odd is in effect
+  \endgroup
+  \lastnegativepageno = \pageno
+  \global\pageno = \savepageno
+}
+\let\shortcontents = \summarycontents
+
+% Typeset the label for a chapter or appendix for the short contents.
+% The arg is, e.g., `A' for an appendix, or `3' for a chapter.
+%
+\def\shortchaplabel#1{%
+  % This space should be enough, since a single number is .5em, and the
+  % widest letter (M) is 1em, at least in the Computer Modern fonts.
+  % But use \hss just in case.
+  % (This space doesn't include the extra space that gets added after
+  % the label; that gets put in by \shortchapentry above.)
+  %
+  % We'd like to right-justify chapter numbers, but that looks strange
+  % with appendix letters.  And right-justifying numbers and
+  % left-justifying letters looks strange when there is less than 10
+  % chapters.  Have to read the whole toc once to know how many chapters
+  % there are before deciding ...
+  \hbox to 1em{#1\hss}%
+}
+
+% These macros generate individual entries in the table of contents.
+% The first argument is the chapter or section name.
+% The last argument is the page number.
+% The arguments in between are the chapter number, section number, ...
+
+% Chapters, in the main contents.
+\def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}}
+%
+% Chapters, in the short toc.
+% See comments in \dochapentry re vbox and related settings.
+\def\shortchapentry#1#2#3#4{%
+  \tocentry{\shortchaplabel{#2}\labelspace #1}{\doshortpageno\bgroup#4\egroup}%
+}
+
+% Appendices, in the main contents.
+% Need the word Appendix, and a fixed-size box.
+%
+\def\appendixbox#1{%
+  % We use M since it's probably the widest letter.
+  \setbox0 = \hbox{\putwordAppendix{} M}%
+  \hbox to \wd0{\putwordAppendix{} #1\hss}}
+%
+\def\appentry#1#2#3#4{\dochapentry{\appendixbox{#2}\labelspace#1}{#4}}
+
+% Unnumbered chapters.
+\def\unnchapentry#1#2#3#4{\dochapentry{#1}{#4}}
+\def\shortunnchapentry#1#2#3#4{\tocentry{#1}{\doshortpageno\bgroup#4\egroup}}
+
+% Sections.
+\def\numsecentry#1#2#3#4{\dosecentry{#2\labelspace#1}{#4}}
+\let\appsecentry=\numsecentry
+\def\unnsecentry#1#2#3#4{\dosecentry{#1}{#4}}
+
+% Subsections.
+\def\numsubsecentry#1#2#3#4{\dosubsecentry{#2\labelspace#1}{#4}}
+\let\appsubsecentry=\numsubsecentry
+\def\unnsubsecentry#1#2#3#4{\dosubsecentry{#1}{#4}}
+
+% And subsubsections.
+\def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#2\labelspace#1}{#4}}
+\let\appsubsubsecentry=\numsubsubsecentry
+\def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#4}}
+
+% This parameter controls the indentation of the various levels.
+% Same as \defaultparindent.
+\newdimen\tocindent \tocindent = 15pt
+
+% Now for the actual typesetting. In all these, #1 is the text and #2 is the
+% page number.
+%
+% If the toc has to be broken over pages, we want it to be at chapters
+% if at all possible; hence the \penalty.
+\def\dochapentry#1#2{%
+   \penalty-300 \vskip1\baselineskip plus.33\baselineskip minus.25\baselineskip
+   \begingroup
+     \chapentryfonts
+     \tocentry{#1}{\dopageno\bgroup#2\egroup}%
+   \endgroup
+   \nobreak\vskip .25\baselineskip plus.1\baselineskip
+}
+
+\def\dosecentry#1#2{\begingroup
+  \secentryfonts \leftskip=\tocindent
+  \tocentry{#1}{\dopageno\bgroup#2\egroup}%
+\endgroup}
+
+\def\dosubsecentry#1#2{\begingroup
+  \subsecentryfonts \leftskip=2\tocindent
+  \tocentry{#1}{\dopageno\bgroup#2\egroup}%
+\endgroup}
+
+\def\dosubsubsecentry#1#2{\begingroup
+  \subsubsecentryfonts \leftskip=3\tocindent
+  \tocentry{#1}{\dopageno\bgroup#2\egroup}%
+\endgroup}
+
+% We use the same \entry macro as for the index entries.
+\let\tocentry = \entry
+
+% Space between chapter (or whatever) number and the title.
+\def\labelspace{\hskip1em \relax}
+
+\def\dopageno#1{{\rm #1}}
+\def\doshortpageno#1{{\rm #1}}
+
+\def\chapentryfonts{\secfonts \rm}
+\def\secentryfonts{\textfonts}
+\def\subsecentryfonts{\textfonts}
+\def\subsubsecentryfonts{\textfonts}
+
+
+\message{environments,}
+% @foo ... @end foo.
+
+% @point{}, @result{}, @expansion{}, @print{}, @equiv{}.
+%
+% Since these characters are used in examples, it should be an even number of
+% \tt widths. Each \tt character is 1en, so two makes it 1em.
+%
+\def\point{$\star$}
+\def\result{\leavevmode\raise.15ex\hbox to 1em{\hfil$\Rightarrow$\hfil}}
+\def\expansion{\leavevmode\raise.1ex\hbox to 1em{\hfil$\mapsto$\hfil}}
+\def\print{\leavevmode\lower.1ex\hbox to 1em{\hfil$\dashv$\hfil}}
+\def\equiv{\leavevmode\lower.1ex\hbox to 1em{\hfil$\ptexequiv$\hfil}}
+
+% The @error{} command.
+% Adapted from the TeXbook's \boxit.
+%
+\newbox\errorbox
+%
+{\tentt \global\dimen0 = 3em}% Width of the box.
+\dimen2 = .55pt % Thickness of rules
+% The text. (`r' is open on the right, `e' somewhat less so on the left.)
+\setbox0 = \hbox{\kern-.75pt \reducedsf error\kern-1.5pt}
+%
+\setbox\errorbox=\hbox to \dimen0{\hfil
+   \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right.
+   \advance\hsize by -2\dimen2 % Rules.
+   \vbox{%
+      \hrule height\dimen2
+      \hbox{\vrule width\dimen2 \kern3pt          % Space to left of text.
+         \vtop{\kern2.4pt \box0 \kern2.4pt}% Space above/below.
+         \kern3pt\vrule width\dimen2}% Space to right.
+      \hrule height\dimen2}
+    \hfil}
+%
+\def\error{\leavevmode\lower.7ex\copy\errorbox}
+
+% @tex ... @end tex    escapes into raw Tex temporarily.
+% One exception: @ is still an escape character, so that @end tex works.
+% But \@ or @@ will get a plain tex @ character.
+
+\envdef\tex{%
+  \catcode `\\=0 \catcode `\{=1 \catcode `\}=2
+  \catcode `\$=3 \catcode `\&=4 \catcode `\#=6
+  \catcode `\^=7 \catcode `\_=8 \catcode `\~=\active \let~=\tie
+  \catcode `\%=14
+  \catcode `\+=\other
+  \catcode `\"=\other
+  \catcode `\|=\other
+  \catcode `\<=\other
+  \catcode `\>=\other
+  \escapechar=`\\
+  %
+  \let\b=\ptexb
+  \let\bullet=\ptexbullet
+  \let\c=\ptexc
+  \let\,=\ptexcomma
+  \let\.=\ptexdot
+  \let\dots=\ptexdots
+  \let\equiv=\ptexequiv
+  \let\!=\ptexexclam
+  \let\i=\ptexi
+  \let\indent=\ptexindent
+  \let\noindent=\ptexnoindent
+  \let\{=\ptexlbrace
+  \let\+=\tabalign
+  \let\}=\ptexrbrace
+  \let\/=\ptexslash
+  \let\*=\ptexstar
+  \let\t=\ptext
+  \let\frenchspacing=\plainfrenchspacing
+  %
+  \def\endldots{\mathinner{\ldots\ldots\ldots\ldots}}%
+  \def\enddots{\relax\ifmmode\endldots\else$\mathsurround=0pt \endldots\,$\fi}%
+  \def\@{@}%
+}
+% There is no need to define \Etex.
+
+% Define @lisp ... @end lisp.
+% @lisp environment forms a group so it can rebind things,
+% including the definition of @end lisp (which normally is erroneous).
+
+% Amount to narrow the margins by for @lisp.
+\newskip\lispnarrowing \lispnarrowing=0.4in
+
+% This is the definition that ^^M gets inside @lisp, @example, and other
+% such environments.  \null is better than a space, since it doesn't
+% have any width.
+\def\lisppar{\null\endgraf}
+
+% This space is always present above and below environments.
+\newskip\envskipamount \envskipamount = 0pt
+
+% Make spacing and below environment symmetrical.  We use \parskip here
+% to help in doing that, since in @example-like environments \parskip
+% is reset to zero; thus the \afterenvbreak inserts no space -- but the
+% start of the next paragraph will insert \parskip.
+%
+\def\aboveenvbreak{{%
+  % =10000 instead of <10000 because of a special case in \itemzzz and
+  % \sectionheading, q.v.
+  \ifnum \lastpenalty=10000 \else
+    \advance\envskipamount by \parskip
+    \endgraf
+    \ifdim\lastskip<\envskipamount
+      \removelastskip
+      % it's not a good place to break if the last penalty was \nobreak
+      % or better ...
+      \ifnum\lastpenalty<10000 \penalty-50 \fi
+      \vskip\envskipamount
+    \fi
+  \fi
+}}
+
+\let\afterenvbreak = \aboveenvbreak
+
+% \nonarrowing is a flag.  If "set", @lisp etc don't narrow margins; it will
+% also clear it, so that its embedded environments do the narrowing again.
+\let\nonarrowing=\relax
+
+% @cartouche ... @end cartouche: draw rectangle w/rounded corners around
+% environment contents.
+\font\circle=lcircle10
+\newdimen\circthick
+\newdimen\cartouter\newdimen\cartinner
+\newskip\normbskip\newskip\normpskip\newskip\normlskip
+\circthick=\fontdimen8\circle
+%
+\def\ctl{{\circle\char'013\hskip -6pt}}% 6pt from pl file: 1/2charwidth
+\def\ctr{{\hskip 6pt\circle\char'010}}
+\def\cbl{{\circle\char'012\hskip -6pt}}
+\def\cbr{{\hskip 6pt\circle\char'011}}
+\def\carttop{\hbox to \cartouter{\hskip\lskip
+        \ctl\leaders\hrule height\circthick\hfil\ctr
+        \hskip\rskip}}
+\def\cartbot{\hbox to \cartouter{\hskip\lskip
+        \cbl\leaders\hrule height\circthick\hfil\cbr
+        \hskip\rskip}}
+%
+\newskip\lskip\newskip\rskip
+
+\envdef\cartouche{%
+  \ifhmode\par\fi  % can't be in the midst of a paragraph.
+  \startsavinginserts
+  \lskip=\leftskip \rskip=\rightskip
+  \leftskip=0pt\rightskip=0pt % we want these *outside*.
+  \cartinner=\hsize \advance\cartinner by-\lskip
+  \advance\cartinner by-\rskip
+  \cartouter=\hsize
+  \advance\cartouter by 18.4pt % allow for 3pt kerns on either
+                               % side, and for 6pt waste from
+                               % each corner char, and rule thickness
+  \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip
+  % Flag to tell @lisp, etc., not to narrow margin.
+  \let\nonarrowing = t%
+  \vbox\bgroup
+      \baselineskip=0pt\parskip=0pt\lineskip=0pt
+      \carttop
+      \hbox\bgroup
+         \hskip\lskip
+         \vrule\kern3pt
+         \vbox\bgroup
+             \kern3pt
+             \hsize=\cartinner
+             \baselineskip=\normbskip
+             \lineskip=\normlskip
+             \parskip=\normpskip
+             \vskip -\parskip
+             \comment % For explanation, see the end of \def\group.
+}
+\def\Ecartouche{%
+              \ifhmode\par\fi
+             \kern3pt
+         \egroup
+         \kern3pt\vrule
+         \hskip\rskip
+      \egroup
+      \cartbot
+  \egroup
+  \checkinserts
+}
+
+
+% This macro is called at the beginning of all the @example variants,
+% inside a group.
+\def\nonfillstart{%
+  \aboveenvbreak
+  \hfuzz = 12pt % Don't be fussy
+  \sepspaces % Make spaces be word-separators rather than space tokens.
+  \let\par = \lisppar % don't ignore blank lines
+  \obeylines % each line of input is a line of output
+  \parskip = 0pt
+  \parindent = 0pt
+  \emergencystretch = 0pt % don't try to avoid overfull boxes
+  \ifx\nonarrowing\relax
+    \advance \leftskip by \lispnarrowing
+    \exdentamount=\lispnarrowing
+  \else
+    \let\nonarrowing = \relax
+  \fi
+  \let\exdent=\nofillexdent
+}
+
+% If you want all examples etc. small: @set dispenvsize small.
+% If you want even small examples the full size: @set dispenvsize nosmall.
+% This affects the following displayed environments:
+%    @example, @display, @format, @lisp
+%
+\def\smallword{small}
+\def\nosmallword{nosmall}
+\let\SETdispenvsize\relax
+\def\setnormaldispenv{%
+  \ifx\SETdispenvsize\smallword
+    % end paragraph for sake of leading, in case document has no blank
+    % line.  This is redundant with what happens in \aboveenvbreak, but
+    % we need to do it before changing the fonts, and it's inconvenient
+    % to change the fonts afterward.
+    \ifnum \lastpenalty=10000 \else \endgraf \fi
+    \smallexamplefonts \rm
+  \fi
+}
+\def\setsmalldispenv{%
+  \ifx\SETdispenvsize\nosmallword
+  \else
+    \ifnum \lastpenalty=10000 \else \endgraf \fi
+    \smallexamplefonts \rm
+  \fi
+}
+
+% We often define two environments, @foo and @smallfoo.
+% Let's do it by one command:
+\def\makedispenv #1#2{
+  \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2}
+  \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2}
+  \expandafter\let\csname E#1\endcsname \afterenvbreak
+  \expandafter\let\csname Esmall#1\endcsname \afterenvbreak
+}
+
+% Define two synonyms:
+\def\maketwodispenvs #1#2#3{
+  \makedispenv{#1}{#3}
+  \makedispenv{#2}{#3}
+}
+
+% @lisp: indented, narrowed, typewriter font; @example: same as @lisp.
+%
+% @smallexample and @smalllisp: use smaller fonts.
+% Originally contributed by Pavel@xerox.
+%
+\maketwodispenvs {lisp}{example}{%
+  \nonfillstart
+  \tt\quoteexpand
+  \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special.
+  \gobble       % eat return
+}
+% @display/@smalldisplay: same as @lisp except keep current font.
+%
+\makedispenv {display}{%
+  \nonfillstart
+  \gobble
+}
+
+% @format/@smallformat: same as @display except don't narrow margins.
+%
+\makedispenv{format}{%
+  \let\nonarrowing = t%
+  \nonfillstart
+  \gobble
+}
+
+% @flushleft: same as @format, but doesn't obey \SETdispenvsize.
+\envdef\flushleft{%
+  \let\nonarrowing = t%
+  \nonfillstart
+  \gobble
+}
+\let\Eflushleft = \afterenvbreak
+
+% @flushright.
+%
+\envdef\flushright{%
+  \let\nonarrowing = t%
+  \nonfillstart
+  \advance\leftskip by 0pt plus 1fill
+  \gobble
+}
+\let\Eflushright = \afterenvbreak
+
+
+% @quotation does normal linebreaking (hence we can't use \nonfillstart)
+% and narrows the margins.  We keep \parskip nonzero in general, since
+% we're doing normal filling.  So, when using \aboveenvbreak and
+% \afterenvbreak, temporarily make \parskip 0.
+%
+\envdef\quotation{%
+  {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip
+  \parindent=0pt
+  %
+  % @cartouche defines \nonarrowing to inhibit narrowing at next level down.
+  \ifx\nonarrowing\relax
+    \advance\leftskip by \lispnarrowing
+    \advance\rightskip by \lispnarrowing
+    \exdentamount = \lispnarrowing
+  \else
+    \let\nonarrowing = \relax
+  \fi
+  \parsearg\quotationlabel
+}
+
+% We have retained a nonzero parskip for the environment, since we're
+% doing normal filling.
+%
+\def\Equotation{%
+  \par
+  \ifx\quotationauthor\undefined\else
+    % indent a bit.
+    \leftline{\kern 2\leftskip \sl ---\quotationauthor}%
+  \fi
+  {\parskip=0pt \afterenvbreak}%
+}
+
+% If we're given an argument, typeset it in bold with a colon after.
+\def\quotationlabel#1{%
+  \def\temp{#1}%
+  \ifx\temp\empty \else
+    {\bf #1: }%
+  \fi
+}
+
+
+% LaTeX-like @verbatim...@end verbatim and @verb{<char>...<char>}
+% If we want to allow any <char> as delimiter,
+% we need the curly braces so that makeinfo sees the @verb command, eg:
+% `@verbx...x' would look like the '@verbx' command.  --janneke@gnu.org
+%
+% [Knuth]: Donald Ervin Knuth, 1996.  The TeXbook.
+%
+% [Knuth] p.344; only we need to do the other characters Texinfo sets
+% active too.  Otherwise, they get lost as the first character on a
+% verbatim line.
+\def\dospecials{%
+  \do\ \do\\\do\{\do\}\do\$\do\&%
+  \do\#\do\^\do\^^K\do\_\do\^^A\do\%\do\~%
+  \do\<\do\>\do\|\do\@\do+\do\"%
+}
+%
+% [Knuth] p. 380
+\def\uncatcodespecials{%
+  \def\do##1{\catcode`##1=\other}\dospecials}
+%
+% [Knuth] pp. 380,381,391
+% Disable Spanish ligatures ?` and !` of \tt font
+\begingroup
+  \catcode`\`=\active\gdef`{\relax\lq}
+\endgroup
+%
+% Setup for the @verb command.
+%
+% Eight spaces for a tab
+\begingroup
+  \catcode`\^^I=\active
+  \gdef\tabeightspaces{\catcode`\^^I=\active\def^^I{\ \ \ \ \ \ \ \ }}
+\endgroup
+%
+\def\setupverb{%
+  \tt  % easiest (and conventionally used) font for verbatim
+  \def\par{\leavevmode\endgraf}%
+  \catcode`\`=\active
+  \tabeightspaces
+  % Respect line breaks,
+  % print special symbols as themselves, and
+  % make each space count
+  % must do in this order:
+  \obeylines \uncatcodespecials \sepspaces
+}
+
+% Setup for the @verbatim environment
+%
+% Real tab expansion
+\newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount
+%
+\def\starttabbox{\setbox0=\hbox\bgroup}
+
+% Allow an option to not replace quotes with a regular directed right
+% quote/apostrophe (char 0x27), but instead use the undirected quote
+% from cmtt (char 0x0d).  The undirected quote is ugly, so don't make it
+% the default, but it works for pasting with more pdf viewers (at least
+% evince), the lilypond developers report.  xpdf does work with the
+% regular 0x27.
+%
+\def\codequoteright{%
+  \expandafter\ifx\csname SETtxicodequoteundirected\endcsname\relax
+    \expandafter\ifx\csname SETcodequoteundirected\endcsname\relax
+      '%
+    \else \char'15 \fi
+  \else \char'15 \fi
+}
+%
+% and a similar option for the left quote char vs. a grave accent.
+% Modern fonts display ASCII 0x60 as a grave accent, so some people like
+% the code environments to do likewise.
+%
+\def\codequoteleft{%
+  \expandafter\ifx\csname SETtxicodequotebacktick\endcsname\relax
+    \expandafter\ifx\csname SETcodequotebacktick\endcsname\relax
+      `%
+    \else \char'22 \fi
+  \else \char'22 \fi
+}
+%
+\begingroup
+  \catcode`\^^I=\active
+  \gdef\tabexpand{%
+    \catcode`\^^I=\active
+    \def^^I{\leavevmode\egroup
+      \dimen0=\wd0 % the width so far, or since the previous tab
+      \divide\dimen0 by\tabw
+      \multiply\dimen0 by\tabw % compute previous multiple of \tabw
+      \advance\dimen0 by\tabw  % advance to next multiple of \tabw
+      \wd0=\dimen0 \box0 \starttabbox
+    }%
+  }
+  \catcode`\'=\active
+  \gdef\rquoteexpand{\catcode\rquoteChar=\active \def'{\codequoteright}}%
+  %
+  \catcode`\`=\active
+  \gdef\lquoteexpand{\catcode\lquoteChar=\active \def`{\codequoteleft}}%
+  %
+  \gdef\quoteexpand{\rquoteexpand \lquoteexpand}%
+\endgroup
+
+% start the verbatim environment.
+\def\setupverbatim{%
+  \let\nonarrowing = t%
+  \nonfillstart
+  % Easiest (and conventionally used) font for verbatim
+  \tt
+  \def\par{\leavevmode\egroup\box0\endgraf}%
+  \catcode`\`=\active
+  \tabexpand
+  \quoteexpand
+  % Respect line breaks,
+  % print special symbols as themselves, and
+  % make each space count
+  % must do in this order:
+  \obeylines \uncatcodespecials \sepspaces
+  \everypar{\starttabbox}%
+}
+
+% Do the @verb magic: verbatim text is quoted by unique
+% delimiter characters.  Before first delimiter expect a
+% right brace, after last delimiter expect closing brace:
+%
+%    \def\doverb'{'<char>#1<char>'}'{#1}
+%
+% [Knuth] p. 382; only eat outer {}
+\begingroup
+  \catcode`[=1\catcode`]=2\catcode`\{=\other\catcode`\}=\other
+  \gdef\doverb{#1[\def\next##1#1}[##1\endgroup]\next]
+\endgroup
+%
+\def\verb{\begingroup\setupverb\doverb}
+%
+%
+% Do the @verbatim magic: define the macro \doverbatim so that
+% the (first) argument ends when '@end verbatim' is reached, ie:
+%
+%     \def\doverbatim#1@end verbatim{#1}
+%
+% For Texinfo it's a lot easier than for LaTeX,
+% because texinfo's \verbatim doesn't stop at '\end{verbatim}':
+% we need not redefine '\', '{' and '}'.
+%
+% Inspired by LaTeX's verbatim command set [latex.ltx]
+%
+\begingroup
+  \catcode`\ =\active
+  \obeylines %
+  % ignore everything up to the first ^^M, that's the newline at the end
+  % of the @verbatim input line itself.  Otherwise we get an extra blank
+  % line in the output.
+  \xdef\doverbatim#1^^M#2@end verbatim{#2\noexpand\end\gobble verbatim}%
+  % We really want {...\end verbatim} in the body of the macro, but
+  % without the active space; thus we have to use \xdef and \gobble.
+\endgroup
+%
+\envdef\verbatim{%
+    \setupverbatim\doverbatim
+}
+\let\Everbatim = \afterenvbreak
+
+
+% @verbatiminclude FILE - insert text of file in verbatim environment.
+%
+\def\verbatiminclude{\parseargusing\filenamecatcodes\doverbatiminclude}
+%
+\def\doverbatiminclude#1{%
+  {%
+    \makevalueexpandable
+    \setupverbatim
+    \input #1
+    \afterenvbreak
+  }%
+}
+
+% @copying ... @end copying.
+% Save the text away for @insertcopying later.
+%
+% We save the uninterpreted tokens, rather than creating a box.
+% Saving the text in a box would be much easier, but then all the
+% typesetting commands (@smallbook, font changes, etc.) have to be done
+% beforehand -- and a) we want @copying to be done first in the source
+% file; b) letting users define the frontmatter in as flexible order as
+% possible is very desirable.
+%
+\def\copying{\checkenv{}\begingroup\scanargctxt\docopying}
+\def\docopying#1@end copying{\endgroup\def\copyingtext{#1}}
+%
+\def\insertcopying{%
+  \begingroup
+    \parindent = 0pt  % paragraph indentation looks wrong on title page
+    \scanexp\copyingtext
+  \endgroup
+}
+
+
+\message{defuns,}
+% @defun etc.
+
+\newskip\defbodyindent \defbodyindent=.4in
+\newskip\defargsindent \defargsindent=50pt
+\newskip\deflastargmargin \deflastargmargin=18pt
+\newcount\defunpenalty
+
+% Start the processing of @deffn:
+\def\startdefun{%
+  \ifnum\lastpenalty<10000
+    \medbreak
+    \defunpenalty=10003 % Will keep this @deffn together with the
+                        % following @def command, see below.
+  \else
+    % If there are two @def commands in a row, we'll have a \nobreak,
+    % which is there to keep the function description together with its
+    % header.  But if there's nothing but headers, we need to allow a
+    % break somewhere.  Check specifically for penalty 10002, inserted
+    % by \printdefunline, instead of 10000, since the sectioning
+    % commands also insert a nobreak penalty, and we don't want to allow
+    % a break between a section heading and a defun.
+    %
+    % As a minor refinement, we avoid "club" headers by signalling
+    % with penalty of 10003 after the very first @deffn in the
+    % sequence (see above), and penalty of 10002 after any following
+    % @def command.
+    \ifnum\lastpenalty=10002 \penalty2000 \else \defunpenalty=10002 \fi
+    %
+    % Similarly, after a section heading, do not allow a break.
+    % But do insert the glue.
+    \medskip  % preceded by discardable penalty, so not a breakpoint
+  \fi
+  %
+  \parindent=0in
+  \advance\leftskip by \defbodyindent
+  \exdentamount=\defbodyindent
+}
+
+\def\dodefunx#1{%
+  % First, check whether we are in the right environment:
+  \checkenv#1%
+  %
+  % As above, allow line break if we have multiple x headers in a row.
+  % It's not a great place, though.
+  \ifnum\lastpenalty=10002 \penalty3000 \else \defunpenalty=10002 \fi
+  %
+  % And now, it's time to reuse the body of the original defun:
+  \expandafter\gobbledefun#1%
+}
+\def\gobbledefun#1\startdefun{}
+
+% \printdefunline \deffnheader{text}
+%
+\def\printdefunline#1#2{%
+  \begingroup
+    % call \deffnheader:
+    #1#2 \endheader
+    % common ending:
+    \interlinepenalty = 10000
+    \advance\rightskip by 0pt plus 1fil
+    \endgraf
+    \nobreak\vskip -\parskip
+    \penalty\defunpenalty  % signal to \startdefun and \dodefunx
+    % Some of the @defun-type tags do not enable magic parentheses,
+    % rendering the following check redundant.  But we don't optimize.
+    \checkparencounts
+  \endgroup
+}
+
+\def\Edefun{\endgraf\medbreak}
+
+% \makedefun{deffn} creates \deffn, \deffnx and \Edeffn;
+% the only thing remainnig is to define \deffnheader.
+%
+\def\makedefun#1{%
+  \expandafter\let\csname E#1\endcsname = \Edefun
+  \edef\temp{\noexpand\domakedefun
+    \makecsname{#1}\makecsname{#1x}\makecsname{#1header}}%
+  \temp
+}
+
+% \domakedefun \deffn \deffnx \deffnheader
+%
+% Define \deffn and \deffnx, without parameters.
+% \deffnheader has to be defined explicitly.
+%
+\def\domakedefun#1#2#3{%
+  \envdef#1{%
+    \startdefun
+    \parseargusing\activeparens{\printdefunline#3}%
+  }%
+  \def#2{\dodefunx#1}%
+  \def#3%
+}
+
+%%% Untyped functions:
+
+% @deffn category name args
+\makedefun{deffn}{\deffngeneral{}}
+
+% @deffn category class name args
+\makedefun{defop}#1 {\defopon{#1\ \putwordon}}
+
+% \defopon {category on}class name args
+\def\defopon#1#2 {\deffngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} }
+
+% \deffngeneral {subind}category name args
+%
+\def\deffngeneral#1#2 #3 #4\endheader{%
+  % Remember that \dosubind{fn}{foo}{} is equivalent to \doind{fn}{foo}.
+  \dosubind{fn}{\code{#3}}{#1}%
+  \defname{#2}{}{#3}\magicamp\defunargs{#4\unskip}%
+}
+
+%%% Typed functions:
+
+% @deftypefn category type name args
+\makedefun{deftypefn}{\deftypefngeneral{}}
+
+% @deftypeop category class type name args
+\makedefun{deftypeop}#1 {\deftypeopon{#1\ \putwordon}}
+
+% \deftypeopon {category on}class type name args
+\def\deftypeopon#1#2 {\deftypefngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} }
+
+% \deftypefngeneral {subind}category type name args
+%
+\def\deftypefngeneral#1#2 #3 #4 #5\endheader{%
+  \dosubind{fn}{\code{#4}}{#1}%
+  \defname{#2}{#3}{#4}\defunargs{#5\unskip}%
+}
+
+%%% Typed variables:
+
+% @deftypevr category type var args
+\makedefun{deftypevr}{\deftypecvgeneral{}}
+
+% @deftypecv category class type var args
+\makedefun{deftypecv}#1 {\deftypecvof{#1\ \putwordof}}
+
+% \deftypecvof {category of}class type var args
+\def\deftypecvof#1#2 {\deftypecvgeneral{\putwordof\ \code{#2}}{#1\ \code{#2}} }
+
+% \deftypecvgeneral {subind}category type var args
+%
+\def\deftypecvgeneral#1#2 #3 #4 #5\endheader{%
+  \dosubind{vr}{\code{#4}}{#1}%
+  \defname{#2}{#3}{#4}\defunargs{#5\unskip}%
+}
+
+%%% Untyped variables:
+
+% @defvr category var args
+\makedefun{defvr}#1 {\deftypevrheader{#1} {} }
+
+% @defcv category class var args
+\makedefun{defcv}#1 {\defcvof{#1\ \putwordof}}
+
+% \defcvof {category of}class var args
+\def\defcvof#1#2 {\deftypecvof{#1}#2 {} }
+
+%%% Type:
+% @deftp category name args
+\makedefun{deftp}#1 #2 #3\endheader{%
+  \doind{tp}{\code{#2}}%
+  \defname{#1}{}{#2}\defunargs{#3\unskip}%
+}
+
+% Remaining @defun-like shortcuts:
+\makedefun{defun}{\deffnheader{\putwordDeffunc} }
+\makedefun{defmac}{\deffnheader{\putwordDefmac} }
+\makedefun{defspec}{\deffnheader{\putwordDefspec} }
+\makedefun{deftypefun}{\deftypefnheader{\putwordDeffunc} }
+\makedefun{defvar}{\defvrheader{\putwordDefvar} }
+\makedefun{defopt}{\defvrheader{\putwordDefopt} }
+\makedefun{deftypevar}{\deftypevrheader{\putwordDefvar} }
+\makedefun{defmethod}{\defopon\putwordMethodon}
+\makedefun{deftypemethod}{\deftypeopon\putwordMethodon}
+\makedefun{defivar}{\defcvof\putwordInstanceVariableof}
+\makedefun{deftypeivar}{\deftypecvof\putwordInstanceVariableof}
+
+% \defname, which formats the name of the @def (not the args).
+% #1 is the category, such as "Function".
+% #2 is the return type, if any.
+% #3 is the function name.
+%
+% We are followed by (but not passed) the arguments, if any.
+%
+\def\defname#1#2#3{%
+  % Get the values of \leftskip and \rightskip as they were outside the @def...
+  \advance\leftskip by -\defbodyindent
+  %
+  % How we'll format the type name.  Putting it in brackets helps
+  % distinguish it from the body text that may end up on the next line
+  % just below it.
+  \def\temp{#1}%
+  \setbox0=\hbox{\kern\deflastargmargin \ifx\temp\empty\else [\rm\temp]\fi}
+  %
+  % Figure out line sizes for the paragraph shape.
+  % The first line needs space for \box0; but if \rightskip is nonzero,
+  % we need only space for the part of \box0 which exceeds it:
+  \dimen0=\hsize  \advance\dimen0 by -\wd0  \advance\dimen0 by \rightskip
+  % The continuations:
+  \dimen2=\hsize  \advance\dimen2 by -\defargsindent
+  % (plain.tex says that \dimen1 should be used only as global.)
+  \parshape 2 0in \dimen0 \defargsindent \dimen2
+  %
+  % Put the type name to the right margin.
+  \noindent
+  \hbox to 0pt{%
+    \hfil\box0 \kern-\hsize
+    % \hsize has to be shortened this way:
+    \kern\leftskip
+    % Intentionally do not respect \rightskip, since we need the space.
+  }%
+  %
+  % Allow all lines to be underfull without complaint:
+  \tolerance=10000 \hbadness=10000
+  \exdentamount=\defbodyindent
+  {%
+    % defun fonts. We use typewriter by default (used to be bold) because:
+    % . we're printing identifiers, they should be in tt in principle.
+    % . in languages with many accents, such as Czech or French, it's
+    %   common to leave accents off identifiers.  The result looks ok in
+    %   tt, but exceedingly strange in rm.
+    % . we don't want -- and --- to be treated as ligatures.
+    % . this still does not fix the ?` and !` ligatures, but so far no
+    %   one has made identifiers using them :).
+    \df \tt
+    \def\temp{#2}% return value type
+    \ifx\temp\empty\else \tclose{\temp} \fi
+    #3% output function name
+  }%
+  {\rm\enskip}% hskip 0.5 em of \tenrm
+  %
+  \boldbrax
+  % arguments will be output next, if any.
+}
+
+% Print arguments in slanted roman (not ttsl), inconsistently with using
+% tt for the name.  This is because literal text is sometimes needed in
+% the argument list (groff manual), and ttsl and tt are not very
+% distinguishable.  Prevent hyphenation at `-' chars.
+%
+\def\defunargs#1{%
+  % use sl by default (not ttsl),
+  % tt for the names.
+  \df \sl \hyphenchar\font=0
+  %
+  % On the other hand, if an argument has two dashes (for instance), we
+  % want a way to get ttsl.  Let's try @var for that.
+  \let\var=\ttslanted
+  #1%
+  \sl\hyphenchar\font=45
+}
+
+% We want ()&[] to print specially on the defun line.
+%
+\def\activeparens{%
+  \catcode`\(=\active \catcode`\)=\active
+  \catcode`\[=\active \catcode`\]=\active
+  \catcode`\&=\active
+}
+
+% Make control sequences which act like normal parenthesis chars.
+\let\lparen = ( \let\rparen = )
+
+% Be sure that we always have a definition for `(', etc.  For example,
+% if the fn name has parens in it, \boldbrax will not be in effect yet,
+% so TeX would otherwise complain about undefined control sequence.
+{
+  \activeparens
+  \global\let(=\lparen \global\let)=\rparen
+  \global\let[=\lbrack \global\let]=\rbrack
+  \global\let& = \&
+
+  \gdef\boldbrax{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb}
+  \gdef\magicamp{\let&=\amprm}
+}
+
+\newcount\parencount
+
+% If we encounter &foo, then turn on ()-hacking afterwards
+\newif\ifampseen
+\def\amprm#1 {\ampseentrue{\bf\&#1 }}
+
+\def\parenfont{%
+  \ifampseen
+    % At the first level, print parens in roman,
+    % otherwise use the default font.
+    \ifnum \parencount=1 \rm \fi
+  \else
+    % The \sf parens (in \boldbrax) actually are a little bolder than
+    % the contained text.  This is especially needed for [ and ] .
+    \sf
+  \fi
+}
+\def\infirstlevel#1{%
+  \ifampseen
+    \ifnum\parencount=1
+      #1%
+    \fi
+  \fi
+}
+\def\bfafterword#1 {#1 \bf}
+
+\def\opnr{%
+  \global\advance\parencount by 1
+  {\parenfont(}%
+  \infirstlevel \bfafterword
+}
+\def\clnr{%
+  {\parenfont)}%
+  \infirstlevel \sl
+  \global\advance\parencount by -1
+}
+
+\newcount\brackcount
+\def\lbrb{%
+  \global\advance\brackcount by 1
+  {\bf[}%
+}
+\def\rbrb{%
+  {\bf]}%
+  \global\advance\brackcount by -1
+}
+
+\def\checkparencounts{%
+  \ifnum\parencount=0 \else \badparencount \fi
+  \ifnum\brackcount=0 \else \badbrackcount \fi
+}
+% these should not use \errmessage; the glibc manual, at least, actually
+% has such constructs (when documenting function pointers).
+\def\badparencount{%
+  \message{Warning: unbalanced parentheses in @def...}%
+  \global\parencount=0
+}
+\def\badbrackcount{%
+  \message{Warning: unbalanced square brackets in @def...}%
+  \global\brackcount=0
+}
+
+
+\message{macros,}
+% @macro.
+
+% To do this right we need a feature of e-TeX, \scantokens,
+% which we arrange to emulate with a temporary file in ordinary TeX.
+\ifx\eTeXversion\undefined
+  \newwrite\macscribble
+  \def\scantokens#1{%
+    \toks0={#1}%
+    \immediate\openout\macscribble=\jobname.tmp
+    \immediate\write\macscribble{\the\toks0}%
+    \immediate\closeout\macscribble
+    \input \jobname.tmp
+  }
+\fi
+
+\def\scanmacro#1{%
+  \begingroup
+    \newlinechar`\^^M
+    \let\xeatspaces\eatspaces
+    % Undo catcode changes of \startcontents and \doprintindex
+    % When called from @insertcopying or (short)caption, we need active
+    % backslash to get it printed correctly.  Previously, we had
+    % \catcode`\\=\other instead.  We'll see whether a problem appears
+    % with macro expansion.                            --kasal, 19aug04
+    \catcode`\@=0 \catcode`\\=\active \escapechar=`\@
+    % ... and \example
+    \spaceisspace
+    %
+    % Append \endinput to make sure that TeX does not see the ending newline.
+    % I've verified that it is necessary both for e-TeX and for ordinary TeX
+    %                                                  --kasal, 29nov03
+    \scantokens{#1\endinput}%
+  \endgroup
+}
+
+\def\scanexp#1{%
+  \edef\temp{\noexpand\scanmacro{#1}}%
+  \temp
+}
+
+\newcount\paramno   % Count of parameters
+\newtoks\macname    % Macro name
+\newif\ifrecursive  % Is it recursive?
+
+% List of all defined macros in the form
+%    \definedummyword\macro1\definedummyword\macro2...
+% Currently is also contains all @aliases; the list can be split
+% if there is a need.
+\def\macrolist{}
+
+% Add the macro to \macrolist
+\def\addtomacrolist#1{\expandafter \addtomacrolistxxx \csname#1\endcsname}
+\def\addtomacrolistxxx#1{%
+     \toks0 = \expandafter{\macrolist\definedummyword#1}%
+     \xdef\macrolist{\the\toks0}%
+}
+
+% Utility routines.
+% This does \let #1 = #2, with \csnames; that is,
+%   \let \csname#1\endcsname = \csname#2\endcsname
+% (except of course we have to play expansion games).
+%
+\def\cslet#1#2{%
+  \expandafter\let
+  \csname#1\expandafter\endcsname
+  \csname#2\endcsname
+}
+
+% Trim leading and trailing spaces off a string.
+% Concepts from aro-bend problem 15 (see CTAN).
+{\catcode`\@=11
+\gdef\eatspaces #1{\expandafter\trim@\expandafter{#1 }}
+\gdef\trim@ #1{\trim@@ @#1 @ #1 @ @@}
+\gdef\trim@@ #1@ #2@ #3@@{\trim@@@\empty #2 @}
+\def\unbrace#1{#1}
+\unbrace{\gdef\trim@@@ #1 } #2@{#1}
+}
+
+% Trim a single trailing ^^M off a string.
+{\catcode`\^^M=\other \catcode`\Q=3%
+\gdef\eatcr #1{\eatcra #1Q^^MQ}%
+\gdef\eatcra#1^^MQ{\eatcrb#1Q}%
+\gdef\eatcrb#1Q#2Q{#1}%
+}
+
+% Macro bodies are absorbed as an argument in a context where
+% all characters are catcode 10, 11 or 12, except \ which is active
+% (as in normal texinfo). It is necessary to change the definition of \.
+
+% Non-ASCII encodings make 8-bit characters active, so un-activate
+% them to avoid their expansion.  Must do this non-globally, to
+% confine the change to the current group.
+
+% It's necessary to have hard CRs when the macro is executed. This is
+% done by  making ^^M (\endlinechar) catcode 12 when reading the macro
+% body, and then making it the \newlinechar in \scanmacro.
+
+\def\scanctxt{%
+  \catcode`\"=\other
+  \catcode`\+=\other
+  \catcode`\<=\other
+  \catcode`\>=\other
+  \catcode`\@=\other
+  \catcode`\^=\other
+  \catcode`\_=\other
+  \catcode`\|=\other
+  \catcode`\~=\other
+  \ifx\declaredencoding\ascii \else \setnonasciicharscatcodenonglobal\other \fi
+}
+
+\def\scanargctxt{%
+  \scanctxt
+  \catcode`\\=\other
+  \catcode`\^^M=\other
+}
+
+\def\macrobodyctxt{%
+  \scanctxt
+  \catcode`\{=\other
+  \catcode`\}=\other
+  \catcode`\^^M=\other
+  \usembodybackslash
+}
+
+\def\macroargctxt{%
+  \scanctxt
+  \catcode`\\=\other
+}
+
+% \mbodybackslash is the definition of \ in @macro bodies.
+% It maps \foo\ => \csname macarg.foo\endcsname => #N
+% where N is the macro parameter number.
+% We define \csname macarg.\endcsname to be \realbackslash, so
+% \\ in macro replacement text gets you a backslash.
+
+{\catcode`@=0 @catcode`@\=@active
+ @gdef@usembodybackslash{@let\=@mbodybackslash}
+ @gdef@mbodybackslash#1\{@csname macarg.#1@endcsname}
+}
+\expandafter\def\csname macarg.\endcsname{\realbackslash}
+
+\def\macro{\recursivefalse\parsearg\macroxxx}
+\def\rmacro{\recursivetrue\parsearg\macroxxx}
+
+\def\macroxxx#1{%
+  \getargs{#1}%           now \macname is the macname and \argl the arglist
+  \ifx\argl\empty       % no arguments
+     \paramno=0%
+  \else
+     \expandafter\parsemargdef \argl;%
+  \fi
+  \if1\csname ismacro.\the\macname\endcsname
+     \message{Warning: redefining \the\macname}%
+  \else
+     \expandafter\ifx\csname \the\macname\endcsname \relax
+     \else \errmessage{Macro name \the\macname\space already defined}\fi
+     \global\cslet{macsave.\the\macname}{\the\macname}%
+     \global\expandafter\let\csname ismacro.\the\macname\endcsname=1%
+     \addtomacrolist{\the\macname}%
+  \fi
+  \begingroup \macrobodyctxt
+  \ifrecursive \expandafter\parsermacbody
+  \else \expandafter\parsemacbody
+  \fi}
+
+\parseargdef\unmacro{%
+  \if1\csname ismacro.#1\endcsname
+    \global\cslet{#1}{macsave.#1}%
+    \global\expandafter\let \csname ismacro.#1\endcsname=0%
+    % Remove the macro name from \macrolist:
+    \begingroup
+      \expandafter\let\csname#1\endcsname \relax
+      \let\definedummyword\unmacrodo
+      \xdef\macrolist{\macrolist}%
+    \endgroup
+  \else
+    \errmessage{Macro #1 not defined}%
+  \fi
+}
+
+% Called by \do from \dounmacro on each macro.  The idea is to omit any
+% macro definitions that have been changed to \relax.
+%
+\def\unmacrodo#1{%
+  \ifx #1\relax
+    % remove this
+  \else
+    \noexpand\definedummyword \noexpand#1%
+  \fi
+}
+
+% This makes use of the obscure feature that if the last token of a
+% <parameter list> is #, then the preceding argument is delimited by
+% an opening brace, and that opening brace is not consumed.
+\def\getargs#1{\getargsxxx#1{}}
+\def\getargsxxx#1#{\getmacname #1 \relax\getmacargs}
+\def\getmacname #1 #2\relax{\macname={#1}}
+\def\getmacargs#1{\def\argl{#1}}
+
+% Parse the optional {params} list.  Set up \paramno and \paramlist
+% so \defmacro knows what to do.  Define \macarg.blah for each blah
+% in the params list, to be ##N where N is the position in that list.
+% That gets used by \mbodybackslash (above).
+
+% We need to get `macro parameter char #' into several definitions.
+% The technique used is stolen from LaTeX:  let \hash be something
+% unexpandable, insert that wherever you need a #, and then redefine
+% it to # just before using the token list produced.
+%
+% The same technique is used to protect \eatspaces till just before
+% the macro is used.
+
+\def\parsemargdef#1;{\paramno=0\def\paramlist{}%
+        \let\hash\relax\let\xeatspaces\relax\parsemargdefxxx#1,;,}
+\def\parsemargdefxxx#1,{%
+  \if#1;\let\next=\relax
+  \else \let\next=\parsemargdefxxx
+    \advance\paramno by 1%
+    \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname
+        {\xeatspaces{\hash\the\paramno}}%
+    \edef\paramlist{\paramlist\hash\the\paramno,}%
+  \fi\next}
+
+% These two commands read recursive and nonrecursive macro bodies.
+% (They're different since rec and nonrec macros end differently.)
+
+\long\def\parsemacbody#1@end macro%
+{\xdef\temp{\eatcr{#1}}\endgroup\defmacro}%
+\long\def\parsermacbody#1@end rmacro%
+{\xdef\temp{\eatcr{#1}}\endgroup\defmacro}%
+
+% This defines the macro itself. There are six cases: recursive and
+% nonrecursive macros of zero, one, and many arguments.
+% Much magic with \expandafter here.
+% \xdef is used so that macro definitions will survive the file
+% they're defined in; @include reads the file inside a group.
+\def\defmacro{%
+  \let\hash=##% convert placeholders to macro parameter chars
+  \ifrecursive
+    \ifcase\paramno
+    % 0
+      \expandafter\xdef\csname\the\macname\endcsname{%
+        \noexpand\scanmacro{\temp}}%
+    \or % 1
+      \expandafter\xdef\csname\the\macname\endcsname{%
+         \bgroup\noexpand\macroargctxt
+         \noexpand\braceorline
+         \expandafter\noexpand\csname\the\macname xxx\endcsname}%
+      \expandafter\xdef\csname\the\macname xxx\endcsname##1{%
+         \egroup\noexpand\scanmacro{\temp}}%
+    \else % many
+      \expandafter\xdef\csname\the\macname\endcsname{%
+         \bgroup\noexpand\macroargctxt
+         \noexpand\csname\the\macname xx\endcsname}%
+      \expandafter\xdef\csname\the\macname xx\endcsname##1{%
+          \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}%
+      \expandafter\expandafter
+      \expandafter\xdef
+      \expandafter\expandafter
+        \csname\the\macname xxx\endcsname
+          \paramlist{\egroup\noexpand\scanmacro{\temp}}%
+    \fi
+  \else
+    \ifcase\paramno
+    % 0
+      \expandafter\xdef\csname\the\macname\endcsname{%
+        \noexpand\norecurse{\the\macname}%
+        \noexpand\scanmacro{\temp}\egroup}%
+    \or % 1
+      \expandafter\xdef\csname\the\macname\endcsname{%
+         \bgroup\noexpand\macroargctxt
+         \noexpand\braceorline
+         \expandafter\noexpand\csname\the\macname xxx\endcsname}%
+      \expandafter\xdef\csname\the\macname xxx\endcsname##1{%
+        \egroup
+        \noexpand\norecurse{\the\macname}%
+        \noexpand\scanmacro{\temp}\egroup}%
+    \else % many
+      \expandafter\xdef\csname\the\macname\endcsname{%
+         \bgroup\noexpand\macroargctxt
+         \expandafter\noexpand\csname\the\macname xx\endcsname}%
+      \expandafter\xdef\csname\the\macname xx\endcsname##1{%
+          \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}%
+      \expandafter\expandafter
+      \expandafter\xdef
+      \expandafter\expandafter
+      \csname\the\macname xxx\endcsname
+      \paramlist{%
+          \egroup
+          \noexpand\norecurse{\the\macname}%
+          \noexpand\scanmacro{\temp}\egroup}%
+    \fi
+  \fi}
+
+\def\norecurse#1{\bgroup\cslet{#1}{macsave.#1}}
+
+% \braceorline decides whether the next nonwhitespace character is a
+% {.  If so it reads up to the closing }, if not, it reads the whole
+% line.  Whatever was read is then fed to the next control sequence
+% as an argument (by \parsebrace or \parsearg)
+\def\braceorline#1{\let\macnamexxx=#1\futurelet\nchar\braceorlinexxx}
+\def\braceorlinexxx{%
+  \ifx\nchar\bgroup\else
+    \expandafter\parsearg
+  \fi \macnamexxx}
+
+
+% @alias.
+% We need some trickery to remove the optional spaces around the equal
+% sign.  Just make them active and then expand them all to nothing.
+\def\alias{\parseargusing\obeyspaces\aliasxxx}
+\def\aliasxxx #1{\aliasyyy#1\relax}
+\def\aliasyyy #1=#2\relax{%
+  {%
+    \expandafter\let\obeyedspace=\empty
+    \addtomacrolist{#1}%
+    \xdef\next{\global\let\makecsname{#1}=\makecsname{#2}}%
+  }%
+  \next
+}
+
+
+\message{cross references,}
+
+\newwrite\auxfile
+\newif\ifhavexrefs    % True if xref values are known.
+\newif\ifwarnedxrefs  % True if we warned once that they aren't known.
+
+% @inforef is relatively simple.
+\def\inforef #1{\inforefzzz #1,,,,**}
+\def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}},
+  node \samp{\ignorespaces#1{}}}
+
+% @node's only job in TeX is to define \lastnode, which is used in
+% cross-references.  The @node line might or might not have commas, and
+% might or might not have spaces before the first comma, like:
+% @node foo , bar , ...
+% We don't want such trailing spaces in the node name.
+%
+\parseargdef\node{\checkenv{}\donode #1 ,\finishnodeparse}
+%
+% also remove a trailing comma, in case of something like this:
+% @node Help-Cross,  ,  , Cross-refs
+\def\donode#1 ,#2\finishnodeparse{\dodonode #1,\finishnodeparse}
+\def\dodonode#1,#2\finishnodeparse{\gdef\lastnode{#1}}
+
+\let\nwnode=\node
+\let\lastnode=\empty
+
+% Write a cross-reference definition for the current node.  #1 is the
+% type (Ynumbered, Yappendix, Ynothing).
+%
+\def\donoderef#1{%
+  \ifx\lastnode\empty\else
+    \setref{\lastnode}{#1}%
+    \global\let\lastnode=\empty
+  \fi
+}
+
+% @anchor{NAME} -- define xref target at arbitrary point.
+%
+\newcount\savesfregister
+%
+\def\savesf{\relax \ifhmode \savesfregister=\spacefactor \fi}
+\def\restoresf{\relax \ifhmode \spacefactor=\savesfregister \fi}
+\def\anchor#1{\savesf \setref{#1}{Ynothing}\restoresf \ignorespaces}
+
+% \setref{NAME}{SNT} defines a cross-reference point NAME (a node or an
+% anchor), which consists of three parts:
+% 1) NAME-title - the current sectioning name taken from \lastsection,
+%                 or the anchor name.
+% 2) NAME-snt   - section number and type, passed as the SNT arg, or
+%                 empty for anchors.
+% 3) NAME-pg    - the page number.
+%
+% This is called from \donoderef, \anchor, and \dofloat.  In the case of
+% floats, there is an additional part, which is not written here:
+% 4) NAME-lof   - the text as it should appear in a @listoffloats.
+%
+\def\setref#1#2{%
+  \pdfmkdest{#1}%
+  \iflinks
+    {%
+      \atdummies  % preserve commands, but don't expand them
+      \edef\writexrdef##1##2{%
+       \write\auxfile{@xrdef{#1-% #1 of \setref, expanded by the \edef
+         ##1}{##2}}% these are parameters of \writexrdef
+      }%
+      \toks0 = \expandafter{\lastsection}%
+      \immediate \writexrdef{title}{\the\toks0 }%
+      \immediate \writexrdef{snt}{\csname #2\endcsname}% \Ynumbered etc.
+      \safewhatsit{\writexrdef{pg}{\folio}}% will be written later, during \shipout
+    }%
+  \fi
+}
+
+% @xref, @pxref, and @ref generate cross-references.  For \xrefX, #1 is
+% the node name, #2 the name of the Info cross-reference, #3 the printed
+% node name, #4 the name of the Info file, #5 the name of the printed
+% manual.  All but the node name can be omitted.
+%
+\def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]}
+\def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]}
+\def\ref#1{\xrefX[#1,,,,,,,]}
+\def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup
+  \unsepspaces
+  \def\printedmanual{\ignorespaces #5}%
+  \def\printedrefname{\ignorespaces #3}%
+  \setbox1=\hbox{\printedmanual\unskip}%
+  \setbox0=\hbox{\printedrefname\unskip}%
+  \ifdim \wd0 = 0pt
+    % No printed node name was explicitly given.
+    \expandafter\ifx\csname SETxref-automatic-section-title\endcsname\relax
+      % Use the node name inside the square brackets.
+      \def\printedrefname{\ignorespaces #1}%
+    \else
+      % Use the actual chapter/section title appear inside
+      % the square brackets.  Use the real section title if we have it.
+      \ifdim \wd1 > 0pt
+        % It is in another manual, so we don't have it.
+        \def\printedrefname{\ignorespaces #1}%
+      \else
+        \ifhavexrefs
+          % We know the real title if we have the xref values.
+          \def\printedrefname{\refx{#1-title}{}}%
+        \else
+          % Otherwise just copy the Info node name.
+          \def\printedrefname{\ignorespaces #1}%
+        \fi%
+      \fi
+    \fi
+  \fi
+  %
+  % Make link in pdf output.
+  \ifpdf
+    \leavevmode
+    \getfilename{#4}%
+    {\indexnofonts
+     \turnoffactive
+     % See comments at \activebackslashdouble.
+     {\activebackslashdouble \xdef\pdfxrefdest{#1}%
+      \backslashparens\pdfxrefdest}%
+     %
+     \ifnum\filenamelength>0
+       \startlink attr{/Border [0 0 0]}%
+         goto file{\the\filename.pdf} name{\pdfxrefdest}%
+     \else
+       \startlink attr{/Border [0 0 0]}%
+         goto name{\pdfmkpgn{\pdfxrefdest}}%
+     \fi
+    }%
+    \setcolor{\linkcolor}%
+  \fi
+  %
+  % Float references are printed completely differently: "Figure 1.2"
+  % instead of "[somenode], p.3".  We distinguish them by the
+  % LABEL-title being set to a magic string.
+  {%
+    % Have to otherify everything special to allow the \csname to
+    % include an _ in the xref name, etc.
+    \indexnofonts
+    \turnoffactive
+    \expandafter\global\expandafter\let\expandafter\Xthisreftitle
+      \csname XR#1-title\endcsname
+  }%
+  \iffloat\Xthisreftitle
+    % If the user specified the print name (third arg) to the ref,
+    % print it instead of our usual "Figure 1.2".
+    \ifdim\wd0 = 0pt
+      \refx{#1-snt}{}%
+    \else
+      \printedrefname
+    \fi
+    %
+    % if the user also gave the printed manual name (fifth arg), append
+    % "in MANUALNAME".
+    \ifdim \wd1 > 0pt
+      \space \putwordin{} \cite{\printedmanual}%
+    \fi
+  \else
+    % node/anchor (non-float) references.
+    %
+    % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not
+    % insert empty discretionaries after hyphens, which means that it will
+    % not find a line break at a hyphen in a node names.  Since some manuals
+    % are best written with fairly long node names, containing hyphens, this
+    % is a loss.  Therefore, we give the text of the node name again, so it
+    % is as if TeX is seeing it for the first time.
+    \ifdim \wd1 > 0pt
+      \putwordSection{} ``\printedrefname'' \putwordin{} \cite{\printedmanual}%
+    \else
+      % _ (for example) has to be the character _ for the purposes of the
+      % control sequence corresponding to the node, but it has to expand
+      % into the usual \leavevmode...\vrule stuff for purposes of
+      % printing. So we \turnoffactive for the \refx-snt, back on for the
+      % printing, back off for the \refx-pg.
+      {\turnoffactive
+       % Only output a following space if the -snt ref is nonempty; for
+       % @unnumbered and @anchor, it won't be.
+       \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}%
+       \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi
+      }%
+      % output the `[mynode]' via a macro so it can be overridden.
+      \xrefprintnodename\printedrefname
+      %
+      % But we always want a comma and a space:
+      ,\space
+      %
+      % output the `page 3'.
+      \turnoffactive \putwordpage\tie\refx{#1-pg}{}%
+    \fi
+  \fi
+  \endlink
+\endgroup}
+
+% This macro is called from \xrefX for the `[nodename]' part of xref
+% output.  It's a separate macro only so it can be changed more easily,
+% since square brackets don't work well in some documents.  Particularly
+% one that Bob is working on :).
+%
+\def\xrefprintnodename#1{[#1]}
+
+% Things referred to by \setref.
+%
+\def\Ynothing{}
+\def\Yomitfromtoc{}
+\def\Ynumbered{%
+  \ifnum\secno=0
+    \putwordChapter@tie \the\chapno
+  \else \ifnum\subsecno=0
+    \putwordSection@tie \the\chapno.\the\secno
+  \else \ifnum\subsubsecno=0
+    \putwordSection@tie \the\chapno.\the\secno.\the\subsecno
+  \else
+    \putwordSection@tie \the\chapno.\the\secno.\the\subsecno.\the\subsubsecno
+  \fi\fi\fi
+}
+\def\Yappendix{%
+  \ifnum\secno=0
+     \putwordAppendix@tie @char\the\appendixno{}%
+  \else \ifnum\subsecno=0
+     \putwordSection@tie @char\the\appendixno.\the\secno
+  \else \ifnum\subsubsecno=0
+    \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno
+  \else
+    \putwordSection@tie
+      @char\the\appendixno.\the\secno.\the\subsecno.\the\subsubsecno
+  \fi\fi\fi
+}
+
+% Define \refx{NAME}{SUFFIX} to reference a cross-reference string named NAME.
+% If its value is nonempty, SUFFIX is output afterward.
+%
+\def\refx#1#2{%
+  {%
+    \indexnofonts
+    \otherbackslash
+    \expandafter\global\expandafter\let\expandafter\thisrefX
+      \csname XR#1\endcsname
+  }%
+  \ifx\thisrefX\relax
+    % If not defined, say something at least.
+    \angleleft un\-de\-fined\angleright
+    \iflinks
+      \ifhavexrefs
+        \message{\linenumber Undefined cross reference `#1'.}%
+      \else
+        \ifwarnedxrefs\else
+          \global\warnedxrefstrue
+          \message{Cross reference values unknown; you must run TeX again.}%
+        \fi
+      \fi
+    \fi
+  \else
+    % It's defined, so just use it.
+    \thisrefX
+  \fi
+  #2% Output the suffix in any case.
+}
+
+% This is the macro invoked by entries in the aux file.  Usually it's
+% just a \def (we prepend XR to the control sequence name to avoid
+% collisions).  But if this is a float type, we have more work to do.
+%
+\def\xrdef#1#2{%
+  {% The node name might contain 8-bit characters, which in our current
+   % implementation are changed to commands like @'e.  Don't let these
+   % mess up the control sequence name.
+    \indexnofonts
+    \turnoffactive
+    \xdef\safexrefname{#1}%
+  }%
+  %
+  \expandafter\gdef\csname XR\safexrefname\endcsname{#2}% remember this xref
+  %
+  % Was that xref control sequence that we just defined for a float?
+  \expandafter\iffloat\csname XR\safexrefname\endcsname
+    % it was a float, and we have the (safe) float type in \iffloattype.
+    \expandafter\let\expandafter\floatlist
+      \csname floatlist\iffloattype\endcsname
+    %
+    % Is this the first time we've seen this float type?
+    \expandafter\ifx\floatlist\relax
+      \toks0 = {\do}% yes, so just \do
+    \else
+      % had it before, so preserve previous elements in list.
+      \toks0 = \expandafter{\floatlist\do}%
+    \fi
+    %
+    % Remember this xref in the control sequence \floatlistFLOATTYPE,
+    % for later use in \listoffloats.
+    \expandafter\xdef\csname floatlist\iffloattype\endcsname{\the\toks0
+      {\safexrefname}}%
+  \fi
+}
+
+% Read the last existing aux file, if any.  No error if none exists.
+%
+\def\tryauxfile{%
+  \openin 1 \jobname.aux
+  \ifeof 1 \else
+    \readdatafile{aux}%
+    \global\havexrefstrue
+  \fi
+  \closein 1
+}
+
+\def\setupdatafile{%
+  \catcode`\^^@=\other
+  \catcode`\^^A=\other
+  \catcode`\^^B=\other
+  \catcode`\^^C=\other
+  \catcode`\^^D=\other
+  \catcode`\^^E=\other
+  \catcode`\^^F=\other
+  \catcode`\^^G=\other
+  \catcode`\^^H=\other
+  \catcode`\^^K=\other
+  \catcode`\^^L=\other
+  \catcode`\^^N=\other
+  \catcode`\^^P=\other
+  \catcode`\^^Q=\other
+  \catcode`\^^R=\other
+  \catcode`\^^S=\other
+  \catcode`\^^T=\other
+  \catcode`\^^U=\other
+  \catcode`\^^V=\other
+  \catcode`\^^W=\other
+  \catcode`\^^X=\other
+  \catcode`\^^Z=\other
+  \catcode`\^^[=\other
+  \catcode`\^^\=\other
+  \catcode`\^^]=\other
+  \catcode`\^^^=\other
+  \catcode`\^^_=\other
+  % It was suggested to set the catcode of ^ to 7, which would allow ^^e4 etc.
+  % in xref tags, i.e., node names.  But since ^^e4 notation isn't
+  % supported in the main text, it doesn't seem desirable.  Furthermore,
+  % that is not enough: for node names that actually contain a ^
+  % character, we would end up writing a line like this: 'xrdef {'hat
+  % b-title}{'hat b} and \xrdef does a \csname...\endcsname on the first
+  % argument, and \hat is not an expandable control sequence.  It could
+  % all be worked out, but why?  Either we support ^^ or we don't.
+  %
+  % The other change necessary for this was to define \auxhat:
+  % \def\auxhat{\def^{'hat }}% extra space so ok if followed by letter
+  % and then to call \auxhat in \setq.
+  %
+  \catcode`\^=\other
+  %
+  % Special characters.  Should be turned off anyway, but...
+  \catcode`\~=\other
+  \catcode`\[=\other
+  \catcode`\]=\other
+  \catcode`\"=\other
+  \catcode`\_=\other
+  \catcode`\|=\other
+  \catcode`\<=\other
+  \catcode`\>=\other
+  \catcode`\$=\other
+  \catcode`\#=\other
+  \catcode`\&=\other
+  \catcode`\%=\other
+  \catcode`+=\other % avoid \+ for paranoia even though we've turned it off
+  %
+  % This is to support \ in node names and titles, since the \
+  % characters end up in a \csname.  It's easier than
+  % leaving it active and making its active definition an actual \
+  % character.  What I don't understand is why it works in the *value*
+  % of the xrdef.  Seems like it should be a catcode12 \, and that
+  % should not typeset properly.  But it works, so I'm moving on for
+  % now.  --karl, 15jan04.
+  \catcode`\\=\other
+  %
+  % Make the characters 128-255 be printing characters.
+  {%
+    \count1=128
+    \def\loop{%
+      \catcode\count1=\other
+      \advance\count1 by 1
+      \ifnum \count1<256 \loop \fi
+    }%
+  }%
+  %
+  % @ is our escape character in .aux files, and we need braces.
+  \catcode`\{=1
+  \catcode`\}=2
+  \catcode`\@=0
+}
+
+\def\readdatafile#1{%
+\begingroup
+  \setupdatafile
+  \input\jobname.#1
+\endgroup}
+
+
+\message{insertions,}
+% including footnotes.
+
+\newcount \footnoteno
+
+% The trailing space in the following definition for supereject is
+% vital for proper filling; pages come out unaligned when you do a
+% pagealignmacro call if that space before the closing brace is
+% removed. (Generally, numeric constants should always be followed by a
+% space to prevent strange expansion errors.)
+\def\supereject{\par\penalty -20000\footnoteno =0 }
+
+% @footnotestyle is meaningful for info output only.
+\let\footnotestyle=\comment
+
+{\catcode `\@=11
+%
+% Auto-number footnotes.  Otherwise like plain.
+\gdef\footnote{%
+  \let\indent=\ptexindent
+  \let\noindent=\ptexnoindent
+  \global\advance\footnoteno by \@ne
+  \edef\thisfootno{$^{\the\footnoteno}$}%
+  %
+  % In case the footnote comes at the end of a sentence, preserve the
+  % extra spacing after we do the footnote number.
+  \let\@sf\empty
+  \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\ptexslash\fi
+  %
+  % Remove inadvertent blank space before typesetting the footnote number.
+  \unskip
+  \thisfootno\@sf
+  \dofootnote
+}%
+
+% Don't bother with the trickery in plain.tex to not require the
+% footnote text as a parameter.  Our footnotes don't need to be so general.
+%
+% Oh yes, they do; otherwise, @ifset (and anything else that uses
+% \parseargline) fails inside footnotes because the tokens are fixed when
+% the footnote is read.  --karl, 16nov96.
+%
+\gdef\dofootnote{%
+  \insert\footins\bgroup
+  % We want to typeset this text as a normal paragraph, even if the
+  % footnote reference occurs in (for example) a display environment.
+  % So reset some parameters.
+  \hsize=\pagewidth
+  \interlinepenalty\interfootnotelinepenalty
+  \splittopskip\ht\strutbox % top baseline for broken footnotes
+  \splitmaxdepth\dp\strutbox
+  \floatingpenalty\@MM
+  \leftskip\z@skip
+  \rightskip\z@skip
+  \spaceskip\z@skip
+  \xspaceskip\z@skip
+  \parindent\defaultparindent
+  %
+  \smallfonts \rm
+  %
+  % Because we use hanging indentation in footnotes, a @noindent appears
+  % to exdent this text, so make it be a no-op.  makeinfo does not use
+  % hanging indentation so @noindent can still be needed within footnote
+  % text after an @example or the like (not that this is good style).
+  \let\noindent = \relax
+  %
+  % Hang the footnote text off the number.  Use \everypar in case the
+  % footnote extends for more than one paragraph.
+  \everypar = {\hang}%
+  \textindent{\thisfootno}%
+  %
+  % Don't crash into the line above the footnote text.  Since this
+  % expands into a box, it must come within the paragraph, lest it
+  % provide a place where TeX can split the footnote.
+  \footstrut
+  \futurelet\next\fo@t
+}
+}%end \catcode `\@=11
+
+% In case a @footnote appears in a vbox, save the footnote text and create
+% the real \insert just after the vbox finished.  Otherwise, the insertion
+% would be lost.
+% Similarly, if a @footnote appears inside an alignment, save the footnote
+% text to a box and make the \insert when a row of the table is finished.
+% And the same can be done for other insert classes.  --kasal, 16nov03.
+
+% Replace the \insert primitive by a cheating macro.
+% Deeper inside, just make sure that the saved insertions are not spilled
+% out prematurely.
+%
+\def\startsavinginserts{%
+  \ifx \insert\ptexinsert
+    \let\insert\saveinsert
+  \else
+    \let\checkinserts\relax
+  \fi
+}
+
+% This \insert replacement works for both \insert\footins{foo} and
+% \insert\footins\bgroup foo\egroup, but it doesn't work for \insert27{foo}.
+%
+\def\saveinsert#1{%
+  \edef\next{\noexpand\savetobox \makeSAVEname#1}%
+  \afterassignment\next
+  % swallow the left brace
+  \let\temp =
+}
+\def\makeSAVEname#1{\makecsname{SAVE\expandafter\gobble\string#1}}
+\def\savetobox#1{\global\setbox#1 = \vbox\bgroup \unvbox#1}
+
+\def\checksaveins#1{\ifvoid#1\else \placesaveins#1\fi}
+
+\def\placesaveins#1{%
+  \ptexinsert \csname\expandafter\gobblesave\string#1\endcsname
+    {\box#1}%
+}
+
+% eat @SAVE -- beware, all of them have catcode \other:
+{
+  \def\dospecials{\do S\do A\do V\do E} \uncatcodespecials  %  ;-)
+  \gdef\gobblesave @SAVE{}
+}
+
+% initialization:
+\def\newsaveins #1{%
+  \edef\next{\noexpand\newsaveinsX \makeSAVEname#1}%
+  \next
+}
+\def\newsaveinsX #1{%
+  \csname newbox\endcsname #1%
+  \expandafter\def\expandafter\checkinserts\expandafter{\checkinserts
+    \checksaveins #1}%
+}
+
+% initialize:
+\let\checkinserts\empty
+\newsaveins\footins
+\newsaveins\margin
+
+
+% @image.  We use the macros from epsf.tex to support this.
+% If epsf.tex is not installed and @image is used, we complain.
+%
+% Check for and read epsf.tex up front.  If we read it only at @image
+% time, we might be inside a group, and then its definitions would get
+% undone and the next image would fail.
+\openin 1 = epsf.tex
+\ifeof 1 \else
+  % Do not bother showing banner with epsf.tex v2.7k (available in
+  % doc/epsf.tex and on ctan).
+  \def\epsfannounce{\toks0 = }%
+  \input epsf.tex
+\fi
+\closein 1
+%
+% We will only complain once about lack of epsf.tex.
+\newif\ifwarnednoepsf
+\newhelp\noepsfhelp{epsf.tex must be installed for images to
+  work.  It is also included in the Texinfo distribution, or you can get
+  it from ftp://tug.org/tex/epsf.tex.}
+%
+\def\image#1{%
+  \ifx\epsfbox\undefined
+    \ifwarnednoepsf \else
+      \errhelp = \noepsfhelp
+      \errmessage{epsf.tex not found, images will be ignored}%
+      \global\warnednoepsftrue
+    \fi
+  \else
+    \imagexxx #1,,,,,\finish
+  \fi
+}
+%
+% Arguments to @image:
+% #1 is (mandatory) image filename; we tack on .eps extension.
+% #2 is (optional) width, #3 is (optional) height.
+% #4 is (ignored optional) html alt text.
+% #5 is (ignored optional) extension.
+% #6 is just the usual extra ignored arg for parsing this stuff.
+\newif\ifimagevmode
+\def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup
+  \catcode`\^^M = 5     % in case we're inside an example
+  \normalturnoffactive  % allow _ et al. in names
+  % If the image is by itself, center it.
+  \ifvmode
+    \imagevmodetrue
+    \nobreak\bigskip
+    % Usually we'll have text after the image which will insert
+    % \parskip glue, so insert it here too to equalize the space
+    % above and below.
+    \nobreak\vskip\parskip
+    \nobreak
+    \line\bgroup
+  \fi
+  %
+  % Output the image.
+  \ifpdf
+    \dopdfimage{#1}{#2}{#3}%
+  \else
+    % \epsfbox itself resets \epsf?size at each figure.
+    \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \epsfxsize=#2\relax \fi
+    \setbox0 = \hbox{\ignorespaces #3}\ifdim\wd0 > 0pt \epsfysize=#3\relax \fi
+    \epsfbox{#1.eps}%
+  \fi
+  %
+  \ifimagevmode \egroup \bigbreak \fi  % space after the image
+\endgroup}
+
+
+% @float FLOATTYPE,LABEL,LOC ... @end float for displayed figures, tables,
+% etc.  We don't actually implement floating yet, we always include the
+% float "here".  But it seemed the best name for the future.
+%
+\envparseargdef\float{\eatcommaspace\eatcommaspace\dofloat#1, , ,\finish}
+
+% There may be a space before second and/or third parameter; delete it.
+\def\eatcommaspace#1, {#1,}
+
+% #1 is the optional FLOATTYPE, the text label for this float, typically
+% "Figure", "Table", "Example", etc.  Can't contain commas.  If omitted,
+% this float will not be numbered and cannot be referred to.
+%
+% #2 is the optional xref label.  Also must be present for the float to
+% be referable.
+%
+% #3 is the optional positioning argument; for now, it is ignored.  It
+% will somehow specify the positions allowed to float to (here, top, bottom).
+%
+% We keep a separate counter for each FLOATTYPE, which we reset at each
+% chapter-level command.
+\let\resetallfloatnos=\empty
+%
+\def\dofloat#1,#2,#3,#4\finish{%
+  \let\thiscaption=\empty
+  \let\thisshortcaption=\empty
+  %
+  % don't lose footnotes inside @float.
+  %
+  % BEWARE: when the floats start float, we have to issue warning whenever an
+  % insert appears inside a float which could possibly float. --kasal, 26may04
+  %
+  \startsavinginserts
+  %
+  % We can't be used inside a paragraph.
+  \par
+  %
+  \vtop\bgroup
+    \def\floattype{#1}%
+    \def\floatlabel{#2}%
+    \def\floatloc{#3}% we do nothing with this yet.
+    %
+    \ifx\floattype\empty
+      \let\safefloattype=\empty
+    \else
+      {%
+        % the floattype might have accents or other special characters,
+        % but we need to use it in a control sequence name.
+        \indexnofonts
+        \turnoffactive
+        \xdef\safefloattype{\floattype}%
+      }%
+    \fi
+    %
+    % If label is given but no type, we handle that as the empty type.
+    \ifx\floatlabel\empty \else
+      % We want each FLOATTYPE to be numbered separately (Figure 1,
+      % Table 1, Figure 2, ...).  (And if no label, no number.)
+      %
+      \expandafter\getfloatno\csname\safefloattype floatno\endcsname
+      \global\advance\floatno by 1
+      %
+      {%
+        % This magic value for \lastsection is output by \setref as the
+        % XREFLABEL-title value.  \xrefX uses it to distinguish float
+        % labels (which have a completely different output format) from
+        % node and anchor labels.  And \xrdef uses it to construct the
+        % lists of floats.
+        %
+        \edef\lastsection{\floatmagic=\safefloattype}%
+        \setref{\floatlabel}{Yfloat}%
+      }%
+    \fi
+    %
+    % start with \parskip glue, I guess.
+    \vskip\parskip
+    %
+    % Don't suppress indentation if a float happens to start a section.
+    \restorefirstparagraphindent
+}
+
+% we have these possibilities:
+% @float Foo,lbl & @caption{Cap}: Foo 1.1: Cap
+% @float Foo,lbl & no caption:    Foo 1.1
+% @float Foo & @caption{Cap}:     Foo: Cap
+% @float Foo & no caption:        Foo
+% @float ,lbl & Caption{Cap}:     1.1: Cap
+% @float ,lbl & no caption:       1.1
+% @float & @caption{Cap}:         Cap
+% @float & no caption:
+%
+\def\Efloat{%
+    \let\floatident = \empty
+    %
+    % In all cases, if we have a float type, it comes first.
+    \ifx\floattype\empty \else \def\floatident{\floattype}\fi
+    %
+    % If we have an xref label, the number comes next.
+    \ifx\floatlabel\empty \else
+      \ifx\floattype\empty \else % if also had float type, need tie first.
+        \appendtomacro\floatident{\tie}%
+      \fi
+      % the number.
+      \appendtomacro\floatident{\chaplevelprefix\the\floatno}%
+    \fi
+    %
+    % Start the printed caption with what we've constructed in
+    % \floatident, but keep it separate; we need \floatident again.
+    \let\captionline = \floatident
+    %
+    \ifx\thiscaption\empty \else
+      \ifx\floatident\empty \else
+       \appendtomacro\captionline{: }% had ident, so need a colon between
+      \fi
+      %
+      % caption text.
+      \appendtomacro\captionline{\scanexp\thiscaption}%
+    \fi
+    %
+    % If we have anything to print, print it, with space before.
+    % Eventually this needs to become an \insert.
+    \ifx\captionline\empty \else
+      \vskip.5\parskip
+      \captionline
+      %
+      % Space below caption.
+      \vskip\parskip
+    \fi
+    %
+    % If have an xref label, write the list of floats info.  Do this
+    % after the caption, to avoid chance of it being a breakpoint.
+    \ifx\floatlabel\empty \else
+      % Write the text that goes in the lof to the aux file as
+      % \floatlabel-lof.  Besides \floatident, we include the short
+      % caption if specified, else the full caption if specified, else nothing.
+      {%
+        \atdummies
+        %
+        % since we read the caption text in the macro world, where ^^M
+        % is turned into a normal character, we have to scan it back, so
+        % we don't write the literal three characters "^^M" into the aux file.
+       \scanexp{%
+         \xdef\noexpand\gtemp{%
+           \ifx\thisshortcaption\empty
+             \thiscaption
+           \else
+             \thisshortcaption
+           \fi
+         }%
+       }%
+        \immediate\write\auxfile{@xrdef{\floatlabel-lof}{\floatident
+         \ifx\gtemp\empty \else : \gtemp \fi}}%
+      }%
+    \fi
+  \egroup  % end of \vtop
+  %
+  % place the captured inserts
+  %
+  % BEWARE: when the floats start floating, we have to issue warning
+  % whenever an insert appears inside a float which could possibly
+  % float. --kasal, 26may04
+  %
+  \checkinserts
+}
+
+% Append the tokens #2 to the definition of macro #1, not expanding either.
+%
+\def\appendtomacro#1#2{%
+  \expandafter\def\expandafter#1\expandafter{#1#2}%
+}
+
+% @caption, @shortcaption
+%
+\def\caption{\docaption\thiscaption}
+\def\shortcaption{\docaption\thisshortcaption}
+\def\docaption{\checkenv\float \bgroup\scanargctxt\defcaption}
+\def\defcaption#1#2{\egroup \def#1{#2}}
+
+% The parameter is the control sequence identifying the counter we are
+% going to use.  Create it if it doesn't exist and assign it to \floatno.
+\def\getfloatno#1{%
+  \ifx#1\relax
+      % Haven't seen this figure type before.
+      \csname newcount\endcsname #1%
+      %
+      % Remember to reset this floatno at the next chap.
+      \expandafter\gdef\expandafter\resetallfloatnos
+        \expandafter{\resetallfloatnos #1=0 }%
+  \fi
+  \let\floatno#1%
+}
+
+% \setref calls this to get the XREFLABEL-snt value.  We want an @xref
+% to the FLOATLABEL to expand to "Figure 3.1".  We call \setref when we
+% first read the @float command.
+%
+\def\Yfloat{\floattype@tie \chaplevelprefix\the\floatno}%
+
+% Magic string used for the XREFLABEL-title value, so \xrefX can
+% distinguish floats from other xref types.
+\def\floatmagic{!!float!!}
+
+% #1 is the control sequence we are passed; we expand into a conditional
+% which is true if #1 represents a float ref.  That is, the magic
+% \lastsection value which we \setref above.
+%
+\def\iffloat#1{\expandafter\doiffloat#1==\finish}
+%
+% #1 is (maybe) the \floatmagic string.  If so, #2 will be the
+% (safe) float type for this float.  We set \iffloattype to #2.
+%
+\def\doiffloat#1=#2=#3\finish{%
+  \def\temp{#1}%
+  \def\iffloattype{#2}%
+  \ifx\temp\floatmagic
+}
+
+% @listoffloats FLOATTYPE - print a list of floats like a table of contents.
+%
+\parseargdef\listoffloats{%
+  \def\floattype{#1}% floattype
+  {%
+    % the floattype might have accents or other special characters,
+    % but we need to use it in a control sequence name.
+    \indexnofonts
+    \turnoffactive
+    \xdef\safefloattype{\floattype}%
+  }%
+  %
+  % \xrdef saves the floats as a \do-list in \floatlistSAFEFLOATTYPE.
+  \expandafter\ifx\csname floatlist\safefloattype\endcsname \relax
+    \ifhavexrefs
+      % if the user said @listoffloats foo but never @float foo.
+      \message{\linenumber No `\safefloattype' floats to list.}%
+    \fi
+  \else
+    \begingroup
+      \leftskip=\tocindent  % indent these entries like a toc
+      \let\do=\listoffloatsdo
+      \csname floatlist\safefloattype\endcsname
+    \endgroup
+  \fi
+}
+
+% This is called on each entry in a list of floats.  We're passed the
+% xref label, in the form LABEL-title, which is how we save it in the
+% aux file.  We strip off the -title and look up \XRLABEL-lof, which
+% has the text we're supposed to typeset here.
+%
+% Figures without xref labels will not be included in the list (since
+% they won't appear in the aux file).
+%
+\def\listoffloatsdo#1{\listoffloatsdoentry#1\finish}
+\def\listoffloatsdoentry#1-title\finish{{%
+  % Can't fully expand XR#1-lof because it can contain anything.  Just
+  % pass the control sequence.  On the other hand, XR#1-pg is just the
+  % page number, and we want to fully expand that so we can get a link
+  % in pdf output.
+  \toksA = \expandafter{\csname XR#1-lof\endcsname}%
+  %
+  % use the same \entry macro we use to generate the TOC and index.
+  \edef\writeentry{\noexpand\entry{\the\toksA}{\csname XR#1-pg\endcsname}}%
+  \writeentry
+}}
+
+
+\message{localization,}
+
+% @documentlanguage is usually given very early, just after
+% @setfilename.  If done too late, it may not override everything
+% properly.  Single argument is the language (de) or locale (de_DE)
+% abbreviation.  It would be nice if we could set up a hyphenation file.
+%
+{
+  \catcode`\_ = \active
+  \globaldefs=1
+\parseargdef\documentlanguage{\begingroup
+  \let_=\normalunderscore  % normal _ character for filenames
+  \tex % read txi-??.tex file in plain TeX.
+    % Read the file by the name they passed if it exists.
+    \openin 1 txi-#1.tex
+    \ifeof 1
+      \documentlanguagetrywithoutunderscore{#1_\finish}%
+    \else
+      \input txi-#1.tex
+    \fi
+    \closein 1
+  \endgroup
+\endgroup}
+}
+%
+% If they passed de_DE, and txi-de_DE.tex doesn't exist,
+% try txi-de.tex.
+%
+\def\documentlanguagetrywithoutunderscore#1_#2\finish{%
+  \openin 1 txi-#1.tex
+  \ifeof 1
+    \errhelp = \nolanghelp
+    \errmessage{Cannot read language file txi-#1.tex}%
+  \else
+    \input txi-#1.tex
+  \fi
+  \closein 1
+}
+%
+\newhelp\nolanghelp{The given language definition file cannot be found or
+is empty.  Maybe you need to install it?  In the current directory
+should work if nowhere else does.}
+
+% Set the catcode of characters 128 through 255 to the specified number.
+%
+\def\setnonasciicharscatcode#1{%
+   \count255=128
+   \loop\ifnum\count255<256
+      \global\catcode\count255=#1\relax
+      \advance\count255 by 1
+   \repeat
+}
+
+\def\setnonasciicharscatcodenonglobal#1{%
+   \count255=128
+   \loop\ifnum\count255<256
+      \catcode\count255=#1\relax
+      \advance\count255 by 1
+   \repeat
+}
+
+% @documentencoding sets the definition of non-ASCII characters
+% according to the specified encoding.
+%
+\parseargdef\documentencoding{%
+  % Encoding being declared for the document.
+  \def\declaredencoding{\csname #1.enc\endcsname}%
+  %
+  % Supported encodings: names converted to tokens in order to be able
+  % to compare them with \ifx.
+  \def\ascii{\csname US-ASCII.enc\endcsname}%
+  \def\latnine{\csname ISO-8859-15.enc\endcsname}%
+  \def\latone{\csname ISO-8859-1.enc\endcsname}%
+  \def\lattwo{\csname ISO-8859-2.enc\endcsname}%
+  \def\utfeight{\csname UTF-8.enc\endcsname}%
+  %
+  \ifx \declaredencoding \ascii
+     \asciichardefs
+  %
+  \else \ifx \declaredencoding \lattwo
+     \setnonasciicharscatcode\active
+     \lattwochardefs
+  %
+  \else \ifx \declaredencoding \latone
+     \setnonasciicharscatcode\active
+     \latonechardefs
+  %
+  \else \ifx \declaredencoding \latnine
+     \setnonasciicharscatcode\active
+     \latninechardefs
+  %
+  \else \ifx \declaredencoding \utfeight
+     \setnonasciicharscatcode\active
+     \utfeightchardefs
+  %
+  \else
+    \message{Unknown document encoding #1, ignoring.}%
+  %
+  \fi % utfeight
+  \fi % latnine
+  \fi % latone
+  \fi % lattwo
+  \fi % ascii
+}
+
+% A message to be logged when using a character that isn't available
+% the default font encoding (OT1).
+%
+\def\missingcharmsg#1{\message{Character missing in OT1 encoding: #1.}}
+
+% Take account of \c (plain) vs. \, (Texinfo) difference.
+\def\cedilla#1{\ifx\c\ptexc\c{#1}\else\,{#1}\fi}
+
+% First, make active non-ASCII characters in order for them to be
+% correctly categorized when TeX reads the replacement text of
+% macros containing the character definitions.
+\setnonasciicharscatcode\active
+%
+% Latin1 (ISO-8859-1) character definitions.
+\def\latonechardefs{%
+  \gdef^^a0{~}
+  \gdef^^a1{\exclamdown}
+  \gdef^^a2{\missingcharmsg{CENT SIGN}}
+  \gdef^^a3{{\pounds}}
+  \gdef^^a4{\missingcharmsg{CURRENCY SIGN}}
+  \gdef^^a5{\missingcharmsg{YEN SIGN}}
+  \gdef^^a6{\missingcharmsg{BROKEN BAR}}
+  \gdef^^a7{\S}
+  \gdef^^a8{\"{}}
+  \gdef^^a9{\copyright}
+  \gdef^^aa{\ordf}
+  \gdef^^ab{\missingcharmsg{LEFT-POINTING DOUBLE ANGLE QUOTATION MARK}}
+  \gdef^^ac{$\lnot$}
+  \gdef^^ad{\-}
+  \gdef^^ae{\registeredsymbol}
+  \gdef^^af{\={}}
+  %
+  \gdef^^b0{\textdegree}
+  \gdef^^b1{$\pm$}
+  \gdef^^b2{$^2$}
+  \gdef^^b3{$^3$}
+  \gdef^^b4{\'{}}
+  \gdef^^b5{$\mu$}
+  \gdef^^b6{\P}
+  %
+  \gdef^^b7{$^.$}
+  \gdef^^b8{\cedilla\ }
+  \gdef^^b9{$^1$}
+  \gdef^^ba{\ordm}
+  %
+  \gdef^^bb{\missingcharmsg{RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK}}
+  \gdef^^bc{$1\over4$}
+  \gdef^^bd{$1\over2$}
+  \gdef^^be{$3\over4$}
+  \gdef^^bf{\questiondown}
+  %
+  \gdef^^c0{\`A}
+  \gdef^^c1{\'A}
+  \gdef^^c2{\^A}
+  \gdef^^c3{\~A}
+  \gdef^^c4{\"A}
+  \gdef^^c5{\ringaccent A}
+  \gdef^^c6{\AE}
+  \gdef^^c7{\cedilla C}
+  \gdef^^c8{\`E}
+  \gdef^^c9{\'E}
+  \gdef^^ca{\^E}
+  \gdef^^cb{\"E}
+  \gdef^^cc{\`I}
+  \gdef^^cd{\'I}
+  \gdef^^ce{\^I}
+  \gdef^^cf{\"I}
+  %
+  \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER ETH}}
+  \gdef^^d1{\~N}
+  \gdef^^d2{\`O}
+  \gdef^^d3{\'O}
+  \gdef^^d4{\^O}
+  \gdef^^d5{\~O}
+  \gdef^^d6{\"O}
+  \gdef^^d7{$\times$}
+  \gdef^^d8{\O}
+  \gdef^^d9{\`U}
+  \gdef^^da{\'U}
+  \gdef^^db{\^U}
+  \gdef^^dc{\"U}
+  \gdef^^dd{\'Y}
+  \gdef^^de{\missingcharmsg{LATIN CAPITAL LETTER THORN}}
+  \gdef^^df{\ss}
+  %
+  \gdef^^e0{\`a}
+  \gdef^^e1{\'a}
+  \gdef^^e2{\^a}
+  \gdef^^e3{\~a}
+  \gdef^^e4{\"a}
+  \gdef^^e5{\ringaccent a}
+  \gdef^^e6{\ae}
+  \gdef^^e7{\cedilla c}
+  \gdef^^e8{\`e}
+  \gdef^^e9{\'e}
+  \gdef^^ea{\^e}
+  \gdef^^eb{\"e}
+  \gdef^^ec{\`{\dotless i}}
+  \gdef^^ed{\'{\dotless i}}
+  \gdef^^ee{\^{\dotless i}}
+  \gdef^^ef{\"{\dotless i}}
+  %
+  \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER ETH}}
+  \gdef^^f1{\~n}
+  \gdef^^f2{\`o}
+  \gdef^^f3{\'o}
+  \gdef^^f4{\^o}
+  \gdef^^f5{\~o}
+  \gdef^^f6{\"o}
+  \gdef^^f7{$\div$}
+  \gdef^^f8{\o}
+  \gdef^^f9{\`u}
+  \gdef^^fa{\'u}
+  \gdef^^fb{\^u}
+  \gdef^^fc{\"u}
+  \gdef^^fd{\'y}
+  \gdef^^fe{\missingcharmsg{LATIN SMALL LETTER THORN}}
+  \gdef^^ff{\"y}
+}
+
+% Latin9 (ISO-8859-15) encoding character definitions.
+\def\latninechardefs{%
+  % Encoding is almost identical to Latin1.
+  \latonechardefs
+  %
+  \gdef^^a4{\euro}
+  \gdef^^a6{\v S}
+  \gdef^^a8{\v s}
+  \gdef^^b4{\v Z}
+  \gdef^^b8{\v z}
+  \gdef^^bc{\OE}
+  \gdef^^bd{\oe}
+  \gdef^^be{\"Y}
+}
+
+% Latin2 (ISO-8859-2) character definitions.
+\def\lattwochardefs{%
+  \gdef^^a0{~}
+  \gdef^^a1{\missingcharmsg{LATIN CAPITAL LETTER A WITH OGONEK}}
+  \gdef^^a2{\u{}}
+  \gdef^^a3{\L}
+  \gdef^^a4{\missingcharmsg{CURRENCY SIGN}}
+  \gdef^^a5{\v L}
+  \gdef^^a6{\'S}
+  \gdef^^a7{\S}
+  \gdef^^a8{\"{}}
+  \gdef^^a9{\v S}
+  \gdef^^aa{\cedilla S}
+  \gdef^^ab{\v T}
+  \gdef^^ac{\'Z}
+  \gdef^^ad{\-}
+  \gdef^^ae{\v Z}
+  \gdef^^af{\dotaccent Z}
+  %
+  \gdef^^b0{\textdegree}
+  \gdef^^b1{\missingcharmsg{LATIN SMALL LETTER A WITH OGONEK}}
+  \gdef^^b2{\missingcharmsg{OGONEK}}
+  \gdef^^b3{\l}
+  \gdef^^b4{\'{}}
+  \gdef^^b5{\v l}
+  \gdef^^b6{\'s}
+  \gdef^^b7{\v{}}
+  \gdef^^b8{\cedilla\ }
+  \gdef^^b9{\v s}
+  \gdef^^ba{\cedilla s}
+  \gdef^^bb{\v t}
+  \gdef^^bc{\'z}
+  \gdef^^bd{\H{}}
+  \gdef^^be{\v z}
+  \gdef^^bf{\dotaccent z}
+  %
+  \gdef^^c0{\'R}
+  \gdef^^c1{\'A}
+  \gdef^^c2{\^A}
+  \gdef^^c3{\u A}
+  \gdef^^c4{\"A}
+  \gdef^^c5{\'L}
+  \gdef^^c6{\'C}
+  \gdef^^c7{\cedilla C}
+  \gdef^^c8{\v C}
+  \gdef^^c9{\'E}
+  \gdef^^ca{\missingcharmsg{LATIN CAPITAL LETTER E WITH OGONEK}}
+  \gdef^^cb{\"E}
+  \gdef^^cc{\v E}
+  \gdef^^cd{\'I}
+  \gdef^^ce{\^I}
+  \gdef^^cf{\v D}
+  %
+  \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER D WITH STROKE}}
+  \gdef^^d1{\'N}
+  \gdef^^d2{\v N}
+  \gdef^^d3{\'O}
+  \gdef^^d4{\^O}
+  \gdef^^d5{\H O}
+  \gdef^^d6{\"O}
+  \gdef^^d7{$\times$}
+  \gdef^^d8{\v R}
+  \gdef^^d9{\ringaccent U}
+  \gdef^^da{\'U}
+  \gdef^^db{\H U}
+  \gdef^^dc{\"U}
+  \gdef^^dd{\'Y}
+  \gdef^^de{\cedilla T}
+  \gdef^^df{\ss}
+  %
+  \gdef^^e0{\'r}
+  \gdef^^e1{\'a}
+  \gdef^^e2{\^a}
+  \gdef^^e3{\u a}
+  \gdef^^e4{\"a}
+  \gdef^^e5{\'l}
+  \gdef^^e6{\'c}
+  \gdef^^e7{\cedilla c}
+  \gdef^^e8{\v c}
+  \gdef^^e9{\'e}
+  \gdef^^ea{\missingcharmsg{LATIN SMALL LETTER E WITH OGONEK}}
+  \gdef^^eb{\"e}
+  \gdef^^ec{\v e}
+  \gdef^^ed{\'\i}
+  \gdef^^ee{\^\i}
+  \gdef^^ef{\v d}
+  %
+  \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER D WITH STROKE}}
+  \gdef^^f1{\'n}
+  \gdef^^f2{\v n}
+  \gdef^^f3{\'o}
+  \gdef^^f4{\^o}
+  \gdef^^f5{\H o}
+  \gdef^^f6{\"o}
+  \gdef^^f7{$\div$}
+  \gdef^^f8{\v r}
+  \gdef^^f9{\ringaccent u}
+  \gdef^^fa{\'u}
+  \gdef^^fb{\H u}
+  \gdef^^fc{\"u}
+  \gdef^^fd{\'y}
+  \gdef^^fe{\cedilla t}
+  \gdef^^ff{\dotaccent{}}
+}
+
+% UTF-8 character definitions.
+%
+% This code to support UTF-8 is based on LaTeX's utf8.def, with some
+% changes for Texinfo conventions.  It is included here under the GPL by
+% permission from Frank Mittelbach and the LaTeX team.
+%
+\newcount\countUTFx
+\newcount\countUTFy
+\newcount\countUTFz
+
+\gdef\UTFviiiTwoOctets#1#2{\expandafter
+   \UTFviiiDefined\csname u8:#1\string #2\endcsname}
+%
+\gdef\UTFviiiThreeOctets#1#2#3{\expandafter
+   \UTFviiiDefined\csname u8:#1\string #2\string #3\endcsname}
+%
+\gdef\UTFviiiFourOctets#1#2#3#4{\expandafter
+   \UTFviiiDefined\csname u8:#1\string #2\string #3\string #4\endcsname}
+
+\gdef\UTFviiiDefined#1{%
+  \ifx #1\relax
+    \message{\linenumber Unicode char \string #1 not defined for Texinfo}%
+  \else
+    \expandafter #1%
+  \fi
+}
+
+\begingroup
+  \catcode`\~13
+  \catcode`\"12
+
+  \def\UTFviiiLoop{%
+    \global\catcode\countUTFx\active
+    \uccode`\~\countUTFx
+    \uppercase\expandafter{\UTFviiiTmp}%
+    \advance\countUTFx by 1
+    \ifnum\countUTFx < \countUTFy
+      \expandafter\UTFviiiLoop
+    \fi}
+
+  \countUTFx = "C2
+  \countUTFy = "E0
+  \def\UTFviiiTmp{%
+    \xdef~{\noexpand\UTFviiiTwoOctets\string~}}
+  \UTFviiiLoop
+
+  \countUTFx = "E0
+  \countUTFy = "F0
+  \def\UTFviiiTmp{%
+    \xdef~{\noexpand\UTFviiiThreeOctets\string~}}
+  \UTFviiiLoop
+
+  \countUTFx = "F0
+  \countUTFy = "F4
+  \def\UTFviiiTmp{%
+    \xdef~{\noexpand\UTFviiiFourOctets\string~}}
+  \UTFviiiLoop
+\endgroup
+
+\begingroup
+  \catcode`\"=12
+  \catcode`\<=12
+  \catcode`\.=12
+  \catcode`\,=12
+  \catcode`\;=12
+  \catcode`\!=12
+  \catcode`\~=13
+
+  \gdef\DeclareUnicodeCharacter#1#2{%
+    \countUTFz = "#1\relax
+    \wlog{\space\space defining Unicode char U+#1 (decimal \the\countUTFz)}%
+    \begingroup
+      \parseXMLCharref
+      \def\UTFviiiTwoOctets##1##2{%
+        \csname u8:##1\string ##2\endcsname}%
+      \def\UTFviiiThreeOctets##1##2##3{%
+        \csname u8:##1\string ##2\string ##3\endcsname}%
+      \def\UTFviiiFourOctets##1##2##3##4{%
+        \csname u8:##1\string ##2\string ##3\string ##4\endcsname}%
+      \expandafter\expandafter\expandafter\expandafter
+       \expandafter\expandafter\expandafter
+       \gdef\UTFviiiTmp{#2}%
+    \endgroup}
+
+  \gdef\parseXMLCharref{%
+    \ifnum\countUTFz < "A0\relax
+      \errhelp = \EMsimple
+      \errmessage{Cannot define Unicode char value < 00A0}%
+    \else\ifnum\countUTFz < "800\relax
+      \parseUTFviiiA,%
+      \parseUTFviiiB C\UTFviiiTwoOctets.,%
+    \else\ifnum\countUTFz < "10000\relax
+      \parseUTFviiiA;%
+      \parseUTFviiiA,%
+      \parseUTFviiiB E\UTFviiiThreeOctets.{,;}%
+    \else
+      \parseUTFviiiA;%
+      \parseUTFviiiA,%
+      \parseUTFviiiA!%
+      \parseUTFviiiB F\UTFviiiFourOctets.{!,;}%
+    \fi\fi\fi
+  }
+
+  \gdef\parseUTFviiiA#1{%
+    \countUTFx = \countUTFz
+    \divide\countUTFz by 64
+    \countUTFy = \countUTFz
+    \multiply\countUTFz by 64
+    \advance\countUTFx by -\countUTFz
+    \advance\countUTFx by 128
+    \uccode `#1\countUTFx
+    \countUTFz = \countUTFy}
+
+  \gdef\parseUTFviiiB#1#2#3#4{%
+    \advance\countUTFz by "#10\relax
+    \uccode `#3\countUTFz
+    \uppercase{\gdef\UTFviiiTmp{#2#3#4}}}
+\endgroup
+
+\def\utfeightchardefs{%
+  \DeclareUnicodeCharacter{00A0}{\tie}
+  \DeclareUnicodeCharacter{00A1}{\exclamdown}
+  \DeclareUnicodeCharacter{00A3}{\pounds}
+  \DeclareUnicodeCharacter{00A8}{\"{ }}
+  \DeclareUnicodeCharacter{00A9}{\copyright}
+  \DeclareUnicodeCharacter{00AA}{\ordf}
+  \DeclareUnicodeCharacter{00AB}{\guillemetleft}
+  \DeclareUnicodeCharacter{00AD}{\-}
+  \DeclareUnicodeCharacter{00AE}{\registeredsymbol}
+  \DeclareUnicodeCharacter{00AF}{\={ }}
+
+  \DeclareUnicodeCharacter{00B0}{\ringaccent{ }}
+  \DeclareUnicodeCharacter{00B4}{\'{ }}
+  \DeclareUnicodeCharacter{00B8}{\cedilla{ }}
+  \DeclareUnicodeCharacter{00BA}{\ordm}
+  \DeclareUnicodeCharacter{00BB}{\guillemetright}
+  \DeclareUnicodeCharacter{00BF}{\questiondown}
+
+  \DeclareUnicodeCharacter{00C0}{\`A}
+  \DeclareUnicodeCharacter{00C1}{\'A}
+  \DeclareUnicodeCharacter{00C2}{\^A}
+  \DeclareUnicodeCharacter{00C3}{\~A}
+  \DeclareUnicodeCharacter{00C4}{\"A}
+  \DeclareUnicodeCharacter{00C5}{\AA}
+  \DeclareUnicodeCharacter{00C6}{\AE}
+  \DeclareUnicodeCharacter{00C7}{\cedilla{C}}
+  \DeclareUnicodeCharacter{00C8}{\`E}
+  \DeclareUnicodeCharacter{00C9}{\'E}
+  \DeclareUnicodeCharacter{00CA}{\^E}
+  \DeclareUnicodeCharacter{00CB}{\"E}
+  \DeclareUnicodeCharacter{00CC}{\`I}
+  \DeclareUnicodeCharacter{00CD}{\'I}
+  \DeclareUnicodeCharacter{00CE}{\^I}
+  \DeclareUnicodeCharacter{00CF}{\"I}
+
+  \DeclareUnicodeCharacter{00D1}{\~N}
+  \DeclareUnicodeCharacter{00D2}{\`O}
+  \DeclareUnicodeCharacter{00D3}{\'O}
+  \DeclareUnicodeCharacter{00D4}{\^O}
+  \DeclareUnicodeCharacter{00D5}{\~O}
+  \DeclareUnicodeCharacter{00D6}{\"O}
+  \DeclareUnicodeCharacter{00D8}{\O}
+  \DeclareUnicodeCharacter{00D9}{\`U}
+  \DeclareUnicodeCharacter{00DA}{\'U}
+  \DeclareUnicodeCharacter{00DB}{\^U}
+  \DeclareUnicodeCharacter{00DC}{\"U}
+  \DeclareUnicodeCharacter{00DD}{\'Y}
+  \DeclareUnicodeCharacter{00DF}{\ss}
+
+  \DeclareUnicodeCharacter{00E0}{\`a}
+  \DeclareUnicodeCharacter{00E1}{\'a}
+  \DeclareUnicodeCharacter{00E2}{\^a}
+  \DeclareUnicodeCharacter{00E3}{\~a}
+  \DeclareUnicodeCharacter{00E4}{\"a}
+  \DeclareUnicodeCharacter{00E5}{\aa}
+  \DeclareUnicodeCharacter{00E6}{\ae}
+  \DeclareUnicodeCharacter{00E7}{\cedilla{c}}
+  \DeclareUnicodeCharacter{00E8}{\`e}
+  \DeclareUnicodeCharacter{00E9}{\'e}
+  \DeclareUnicodeCharacter{00EA}{\^e}
+  \DeclareUnicodeCharacter{00EB}{\"e}
+  \DeclareUnicodeCharacter{00EC}{\`{\dotless{i}}}
+  \DeclareUnicodeCharacter{00ED}{\'{\dotless{i}}}
+  \DeclareUnicodeCharacter{00EE}{\^{\dotless{i}}}
+  \DeclareUnicodeCharacter{00EF}{\"{\dotless{i}}}
+
+  \DeclareUnicodeCharacter{00F1}{\~n}
+  \DeclareUnicodeCharacter{00F2}{\`o}
+  \DeclareUnicodeCharacter{00F3}{\'o}
+  \DeclareUnicodeCharacter{00F4}{\^o}
+  \DeclareUnicodeCharacter{00F5}{\~o}
+  \DeclareUnicodeCharacter{00F6}{\"o}
+  \DeclareUnicodeCharacter{00F8}{\o}
+  \DeclareUnicodeCharacter{00F9}{\`u}
+  \DeclareUnicodeCharacter{00FA}{\'u}
+  \DeclareUnicodeCharacter{00FB}{\^u}
+  \DeclareUnicodeCharacter{00FC}{\"u}
+  \DeclareUnicodeCharacter{00FD}{\'y}
+  \DeclareUnicodeCharacter{00FF}{\"y}
+
+  \DeclareUnicodeCharacter{0100}{\=A}
+  \DeclareUnicodeCharacter{0101}{\=a}
+  \DeclareUnicodeCharacter{0102}{\u{A}}
+  \DeclareUnicodeCharacter{0103}{\u{a}}
+  \DeclareUnicodeCharacter{0106}{\'C}
+  \DeclareUnicodeCharacter{0107}{\'c}
+  \DeclareUnicodeCharacter{0108}{\^C}
+  \DeclareUnicodeCharacter{0109}{\^c}
+  \DeclareUnicodeCharacter{010A}{\dotaccent{C}}
+  \DeclareUnicodeCharacter{010B}{\dotaccent{c}}
+  \DeclareUnicodeCharacter{010C}{\v{C}}
+  \DeclareUnicodeCharacter{010D}{\v{c}}
+  \DeclareUnicodeCharacter{010E}{\v{D}}
+
+  \DeclareUnicodeCharacter{0112}{\=E}
+  \DeclareUnicodeCharacter{0113}{\=e}
+  \DeclareUnicodeCharacter{0114}{\u{E}}
+  \DeclareUnicodeCharacter{0115}{\u{e}}
+  \DeclareUnicodeCharacter{0116}{\dotaccent{E}}
+  \DeclareUnicodeCharacter{0117}{\dotaccent{e}}
+  \DeclareUnicodeCharacter{011A}{\v{E}}
+  \DeclareUnicodeCharacter{011B}{\v{e}}
+  \DeclareUnicodeCharacter{011C}{\^G}
+  \DeclareUnicodeCharacter{011D}{\^g}
+  \DeclareUnicodeCharacter{011E}{\u{G}}
+  \DeclareUnicodeCharacter{011F}{\u{g}}
+
+  \DeclareUnicodeCharacter{0120}{\dotaccent{G}}
+  \DeclareUnicodeCharacter{0121}{\dotaccent{g}}
+  \DeclareUnicodeCharacter{0124}{\^H}
+  \DeclareUnicodeCharacter{0125}{\^h}
+  \DeclareUnicodeCharacter{0128}{\~I}
+  \DeclareUnicodeCharacter{0129}{\~{\dotless{i}}}
+  \DeclareUnicodeCharacter{012A}{\=I}
+  \DeclareUnicodeCharacter{012B}{\={\dotless{i}}}
+  \DeclareUnicodeCharacter{012C}{\u{I}}
+  \DeclareUnicodeCharacter{012D}{\u{\dotless{i}}}
+
+  \DeclareUnicodeCharacter{0130}{\dotaccent{I}}
+  \DeclareUnicodeCharacter{0131}{\dotless{i}}
+  \DeclareUnicodeCharacter{0132}{IJ}
+  \DeclareUnicodeCharacter{0133}{ij}
+  \DeclareUnicodeCharacter{0134}{\^J}
+  \DeclareUnicodeCharacter{0135}{\^{\dotless{j}}}
+  \DeclareUnicodeCharacter{0139}{\'L}
+  \DeclareUnicodeCharacter{013A}{\'l}
+
+  \DeclareUnicodeCharacter{0141}{\L}
+  \DeclareUnicodeCharacter{0142}{\l}
+  \DeclareUnicodeCharacter{0143}{\'N}
+  \DeclareUnicodeCharacter{0144}{\'n}
+  \DeclareUnicodeCharacter{0147}{\v{N}}
+  \DeclareUnicodeCharacter{0148}{\v{n}}
+  \DeclareUnicodeCharacter{014C}{\=O}
+  \DeclareUnicodeCharacter{014D}{\=o}
+  \DeclareUnicodeCharacter{014E}{\u{O}}
+  \DeclareUnicodeCharacter{014F}{\u{o}}
+
+  \DeclareUnicodeCharacter{0150}{\H{O}}
+  \DeclareUnicodeCharacter{0151}{\H{o}}
+  \DeclareUnicodeCharacter{0152}{\OE}
+  \DeclareUnicodeCharacter{0153}{\oe}
+  \DeclareUnicodeCharacter{0154}{\'R}
+  \DeclareUnicodeCharacter{0155}{\'r}
+  \DeclareUnicodeCharacter{0158}{\v{R}}
+  \DeclareUnicodeCharacter{0159}{\v{r}}
+  \DeclareUnicodeCharacter{015A}{\'S}
+  \DeclareUnicodeCharacter{015B}{\'s}
+  \DeclareUnicodeCharacter{015C}{\^S}
+  \DeclareUnicodeCharacter{015D}{\^s}
+  \DeclareUnicodeCharacter{015E}{\cedilla{S}}
+  \DeclareUnicodeCharacter{015F}{\cedilla{s}}
+
+  \DeclareUnicodeCharacter{0160}{\v{S}}
+  \DeclareUnicodeCharacter{0161}{\v{s}}
+  \DeclareUnicodeCharacter{0162}{\cedilla{t}}
+  \DeclareUnicodeCharacter{0163}{\cedilla{T}}
+  \DeclareUnicodeCharacter{0164}{\v{T}}
+
+  \DeclareUnicodeCharacter{0168}{\~U}
+  \DeclareUnicodeCharacter{0169}{\~u}
+  \DeclareUnicodeCharacter{016A}{\=U}
+  \DeclareUnicodeCharacter{016B}{\=u}
+  \DeclareUnicodeCharacter{016C}{\u{U}}
+  \DeclareUnicodeCharacter{016D}{\u{u}}
+  \DeclareUnicodeCharacter{016E}{\ringaccent{U}}
+  \DeclareUnicodeCharacter{016F}{\ringaccent{u}}
+
+  \DeclareUnicodeCharacter{0170}{\H{U}}
+  \DeclareUnicodeCharacter{0171}{\H{u}}
+  \DeclareUnicodeCharacter{0174}{\^W}
+  \DeclareUnicodeCharacter{0175}{\^w}
+  \DeclareUnicodeCharacter{0176}{\^Y}
+  \DeclareUnicodeCharacter{0177}{\^y}
+  \DeclareUnicodeCharacter{0178}{\"Y}
+  \DeclareUnicodeCharacter{0179}{\'Z}
+  \DeclareUnicodeCharacter{017A}{\'z}
+  \DeclareUnicodeCharacter{017B}{\dotaccent{Z}}
+  \DeclareUnicodeCharacter{017C}{\dotaccent{z}}
+  \DeclareUnicodeCharacter{017D}{\v{Z}}
+  \DeclareUnicodeCharacter{017E}{\v{z}}
+
+  \DeclareUnicodeCharacter{01C4}{D\v{Z}}
+  \DeclareUnicodeCharacter{01C5}{D\v{z}}
+  \DeclareUnicodeCharacter{01C6}{d\v{z}}
+  \DeclareUnicodeCharacter{01C7}{LJ}
+  \DeclareUnicodeCharacter{01C8}{Lj}
+  \DeclareUnicodeCharacter{01C9}{lj}
+  \DeclareUnicodeCharacter{01CA}{NJ}
+  \DeclareUnicodeCharacter{01CB}{Nj}
+  \DeclareUnicodeCharacter{01CC}{nj}
+  \DeclareUnicodeCharacter{01CD}{\v{A}}
+  \DeclareUnicodeCharacter{01CE}{\v{a}}
+  \DeclareUnicodeCharacter{01CF}{\v{I}}
+
+  \DeclareUnicodeCharacter{01D0}{\v{\dotless{i}}}
+  \DeclareUnicodeCharacter{01D1}{\v{O}}
+  \DeclareUnicodeCharacter{01D2}{\v{o}}
+  \DeclareUnicodeCharacter{01D3}{\v{U}}
+  \DeclareUnicodeCharacter{01D4}{\v{u}}
+
+  \DeclareUnicodeCharacter{01E2}{\={\AE}}
+  \DeclareUnicodeCharacter{01E3}{\={\ae}}
+  \DeclareUnicodeCharacter{01E6}{\v{G}}
+  \DeclareUnicodeCharacter{01E7}{\v{g}}
+  \DeclareUnicodeCharacter{01E8}{\v{K}}
+  \DeclareUnicodeCharacter{01E9}{\v{k}}
+
+  \DeclareUnicodeCharacter{01F0}{\v{\dotless{j}}}
+  \DeclareUnicodeCharacter{01F1}{DZ}
+  \DeclareUnicodeCharacter{01F2}{Dz}
+  \DeclareUnicodeCharacter{01F3}{dz}
+  \DeclareUnicodeCharacter{01F4}{\'G}
+  \DeclareUnicodeCharacter{01F5}{\'g}
+  \DeclareUnicodeCharacter{01F8}{\`N}
+  \DeclareUnicodeCharacter{01F9}{\`n}
+  \DeclareUnicodeCharacter{01FC}{\'{\AE}}
+  \DeclareUnicodeCharacter{01FD}{\'{\ae}}
+  \DeclareUnicodeCharacter{01FE}{\'{\O}}
+  \DeclareUnicodeCharacter{01FF}{\'{\o}}
+
+  \DeclareUnicodeCharacter{021E}{\v{H}}
+  \DeclareUnicodeCharacter{021F}{\v{h}}
+
+  \DeclareUnicodeCharacter{0226}{\dotaccent{A}}
+  \DeclareUnicodeCharacter{0227}{\dotaccent{a}}
+  \DeclareUnicodeCharacter{0228}{\cedilla{E}}
+  \DeclareUnicodeCharacter{0229}{\cedilla{e}}
+  \DeclareUnicodeCharacter{022E}{\dotaccent{O}}
+  \DeclareUnicodeCharacter{022F}{\dotaccent{o}}
+
+  \DeclareUnicodeCharacter{0232}{\=Y}
+  \DeclareUnicodeCharacter{0233}{\=y}
+  \DeclareUnicodeCharacter{0237}{\dotless{j}}
+
+  \DeclareUnicodeCharacter{1E02}{\dotaccent{B}}
+  \DeclareUnicodeCharacter{1E03}{\dotaccent{b}}
+  \DeclareUnicodeCharacter{1E04}{\udotaccent{B}}
+  \DeclareUnicodeCharacter{1E05}{\udotaccent{b}}
+  \DeclareUnicodeCharacter{1E06}{\ubaraccent{B}}
+  \DeclareUnicodeCharacter{1E07}{\ubaraccent{b}}
+  \DeclareUnicodeCharacter{1E0A}{\dotaccent{D}}
+  \DeclareUnicodeCharacter{1E0B}{\dotaccent{d}}
+  \DeclareUnicodeCharacter{1E0C}{\udotaccent{D}}
+  \DeclareUnicodeCharacter{1E0D}{\udotaccent{d}}
+  \DeclareUnicodeCharacter{1E0E}{\ubaraccent{D}}
+  \DeclareUnicodeCharacter{1E0F}{\ubaraccent{d}}
+
+  \DeclareUnicodeCharacter{1E1E}{\dotaccent{F}}
+  \DeclareUnicodeCharacter{1E1F}{\dotaccent{f}}
+
+  \DeclareUnicodeCharacter{1E20}{\=G}
+  \DeclareUnicodeCharacter{1E21}{\=g}
+  \DeclareUnicodeCharacter{1E22}{\dotaccent{H}}
+  \DeclareUnicodeCharacter{1E23}{\dotaccent{h}}
+  \DeclareUnicodeCharacter{1E24}{\udotaccent{H}}
+  \DeclareUnicodeCharacter{1E25}{\udotaccent{h}}
+  \DeclareUnicodeCharacter{1E26}{\"H}
+  \DeclareUnicodeCharacter{1E27}{\"h}
+
+  \DeclareUnicodeCharacter{1E30}{\'K}
+  \DeclareUnicodeCharacter{1E31}{\'k}
+  \DeclareUnicodeCharacter{1E32}{\udotaccent{K}}
+  \DeclareUnicodeCharacter{1E33}{\udotaccent{k}}
+  \DeclareUnicodeCharacter{1E34}{\ubaraccent{K}}
+  \DeclareUnicodeCharacter{1E35}{\ubaraccent{k}}
+  \DeclareUnicodeCharacter{1E36}{\udotaccent{L}}
+  \DeclareUnicodeCharacter{1E37}{\udotaccent{l}}
+  \DeclareUnicodeCharacter{1E3A}{\ubaraccent{L}}
+  \DeclareUnicodeCharacter{1E3B}{\ubaraccent{l}}
+  \DeclareUnicodeCharacter{1E3E}{\'M}
+  \DeclareUnicodeCharacter{1E3F}{\'m}
+
+  \DeclareUnicodeCharacter{1E40}{\dotaccent{M}}
+  \DeclareUnicodeCharacter{1E41}{\dotaccent{m}}
+  \DeclareUnicodeCharacter{1E42}{\udotaccent{M}}
+  \DeclareUnicodeCharacter{1E43}{\udotaccent{m}}
+  \DeclareUnicodeCharacter{1E44}{\dotaccent{N}}
+  \DeclareUnicodeCharacter{1E45}{\dotaccent{n}}
+  \DeclareUnicodeCharacter{1E46}{\udotaccent{N}}
+  \DeclareUnicodeCharacter{1E47}{\udotaccent{n}}
+  \DeclareUnicodeCharacter{1E48}{\ubaraccent{N}}
+  \DeclareUnicodeCharacter{1E49}{\ubaraccent{n}}
+
+  \DeclareUnicodeCharacter{1E54}{\'P}
+  \DeclareUnicodeCharacter{1E55}{\'p}
+  \DeclareUnicodeCharacter{1E56}{\dotaccent{P}}
+  \DeclareUnicodeCharacter{1E57}{\dotaccent{p}}
+  \DeclareUnicodeCharacter{1E58}{\dotaccent{R}}
+  \DeclareUnicodeCharacter{1E59}{\dotaccent{r}}
+  \DeclareUnicodeCharacter{1E5A}{\udotaccent{R}}
+  \DeclareUnicodeCharacter{1E5B}{\udotaccent{r}}
+  \DeclareUnicodeCharacter{1E5E}{\ubaraccent{R}}
+  \DeclareUnicodeCharacter{1E5F}{\ubaraccent{r}}
+
+  \DeclareUnicodeCharacter{1E60}{\dotaccent{S}}
+  \DeclareUnicodeCharacter{1E61}{\dotaccent{s}}
+  \DeclareUnicodeCharacter{1E62}{\udotaccent{S}}
+  \DeclareUnicodeCharacter{1E63}{\udotaccent{s}}
+  \DeclareUnicodeCharacter{1E6A}{\dotaccent{T}}
+  \DeclareUnicodeCharacter{1E6B}{\dotaccent{t}}
+  \DeclareUnicodeCharacter{1E6C}{\udotaccent{T}}
+  \DeclareUnicodeCharacter{1E6D}{\udotaccent{t}}
+  \DeclareUnicodeCharacter{1E6E}{\ubaraccent{T}}
+  \DeclareUnicodeCharacter{1E6F}{\ubaraccent{t}}
+
+  \DeclareUnicodeCharacter{1E7C}{\~V}
+  \DeclareUnicodeCharacter{1E7D}{\~v}
+  \DeclareUnicodeCharacter{1E7E}{\udotaccent{V}}
+  \DeclareUnicodeCharacter{1E7F}{\udotaccent{v}}
+
+  \DeclareUnicodeCharacter{1E80}{\`W}
+  \DeclareUnicodeCharacter{1E81}{\`w}
+  \DeclareUnicodeCharacter{1E82}{\'W}
+  \DeclareUnicodeCharacter{1E83}{\'w}
+  \DeclareUnicodeCharacter{1E84}{\"W}
+  \DeclareUnicodeCharacter{1E85}{\"w}
+  \DeclareUnicodeCharacter{1E86}{\dotaccent{W}}
+  \DeclareUnicodeCharacter{1E87}{\dotaccent{w}}
+  \DeclareUnicodeCharacter{1E88}{\udotaccent{W}}
+  \DeclareUnicodeCharacter{1E89}{\udotaccent{w}}
+  \DeclareUnicodeCharacter{1E8A}{\dotaccent{X}}
+  \DeclareUnicodeCharacter{1E8B}{\dotaccent{x}}
+  \DeclareUnicodeCharacter{1E8C}{\"X}
+  \DeclareUnicodeCharacter{1E8D}{\"x}
+  \DeclareUnicodeCharacter{1E8E}{\dotaccent{Y}}
+  \DeclareUnicodeCharacter{1E8F}{\dotaccent{y}}
+
+  \DeclareUnicodeCharacter{1E90}{\^Z}
+  \DeclareUnicodeCharacter{1E91}{\^z}
+  \DeclareUnicodeCharacter{1E92}{\udotaccent{Z}}
+  \DeclareUnicodeCharacter{1E93}{\udotaccent{z}}
+  \DeclareUnicodeCharacter{1E94}{\ubaraccent{Z}}
+  \DeclareUnicodeCharacter{1E95}{\ubaraccent{z}}
+  \DeclareUnicodeCharacter{1E96}{\ubaraccent{h}}
+  \DeclareUnicodeCharacter{1E97}{\"t}
+  \DeclareUnicodeCharacter{1E98}{\ringaccent{w}}
+  \DeclareUnicodeCharacter{1E99}{\ringaccent{y}}
+
+  \DeclareUnicodeCharacter{1EA0}{\udotaccent{A}}
+  \DeclareUnicodeCharacter{1EA1}{\udotaccent{a}}
+
+  \DeclareUnicodeCharacter{1EB8}{\udotaccent{E}}
+  \DeclareUnicodeCharacter{1EB9}{\udotaccent{e}}
+  \DeclareUnicodeCharacter{1EBC}{\~E}
+  \DeclareUnicodeCharacter{1EBD}{\~e}
+
+  \DeclareUnicodeCharacter{1ECA}{\udotaccent{I}}
+  \DeclareUnicodeCharacter{1ECB}{\udotaccent{i}}
+  \DeclareUnicodeCharacter{1ECC}{\udotaccent{O}}
+  \DeclareUnicodeCharacter{1ECD}{\udotaccent{o}}
+
+  \DeclareUnicodeCharacter{1EE4}{\udotaccent{U}}
+  \DeclareUnicodeCharacter{1EE5}{\udotaccent{u}}
+
+  \DeclareUnicodeCharacter{1EF2}{\`Y}
+  \DeclareUnicodeCharacter{1EF3}{\`y}
+  \DeclareUnicodeCharacter{1EF4}{\udotaccent{Y}}
+
+  \DeclareUnicodeCharacter{1EF8}{\~Y}
+  \DeclareUnicodeCharacter{1EF9}{\~y}
+
+  \DeclareUnicodeCharacter{2013}{--}
+  \DeclareUnicodeCharacter{2014}{---}
+  \DeclareUnicodeCharacter{2018}{\quoteleft}
+  \DeclareUnicodeCharacter{2019}{\quoteright}
+  \DeclareUnicodeCharacter{201A}{\quotesinglbase}
+  \DeclareUnicodeCharacter{201C}{\quotedblleft}
+  \DeclareUnicodeCharacter{201D}{\quotedblright}
+  \DeclareUnicodeCharacter{201E}{\quotedblbase}
+  \DeclareUnicodeCharacter{2022}{\bullet}
+  \DeclareUnicodeCharacter{2026}{\dots}
+  \DeclareUnicodeCharacter{2039}{\guilsinglleft}
+  \DeclareUnicodeCharacter{203A}{\guilsinglright}
+  \DeclareUnicodeCharacter{20AC}{\euro}
+
+  \DeclareUnicodeCharacter{2192}{\expansion}
+  \DeclareUnicodeCharacter{21D2}{\result}
+
+  \DeclareUnicodeCharacter{2212}{\minus}
+  \DeclareUnicodeCharacter{2217}{\point}
+  \DeclareUnicodeCharacter{2261}{\equiv}
+}% end of \utfeightchardefs
+
+
+% US-ASCII character definitions.
+\def\asciichardefs{% nothing need be done
+   \relax
+}
+
+% Make non-ASCII characters printable again for compatibility with
+% existing Texinfo documents that may use them, even without declaring a
+% document encoding.
+%
+\setnonasciicharscatcode \other
+
+
+\message{formatting,}
+
+\newdimen\defaultparindent \defaultparindent = 15pt
+
+\chapheadingskip = 15pt plus 4pt minus 2pt
+\secheadingskip = 12pt plus 3pt minus 2pt
+\subsecheadingskip = 9pt plus 2pt minus 2pt
+
+% Prevent underfull vbox error messages.
+\vbadness = 10000
+
+% Don't be so finicky about underfull hboxes, either.
+\hbadness = 2000
+
+% Following George Bush, get rid of widows and orphans.
+\widowpenalty=10000
+\clubpenalty=10000
+
+% Use TeX 3.0's \emergencystretch to help line breaking, but if we're
+% using an old version of TeX, don't do anything.  We want the amount of
+% stretch added to depend on the line length, hence the dependence on
+% \hsize.  We call this whenever the paper size is set.
+%
+\def\setemergencystretch{%
+  \ifx\emergencystretch\thisisundefined
+    % Allow us to assign to \emergencystretch anyway.
+    \def\emergencystretch{\dimen0}%
+  \else
+    \emergencystretch = .15\hsize
+  \fi
+}
+
+% Parameters in order: 1) textheight; 2) textwidth;
+% 3) voffset; 4) hoffset; 5) binding offset; 6) topskip;
+% 7) physical page height; 8) physical page width.
+%
+% We also call \setleading{\textleading}, so the caller should define
+% \textleading.  The caller should also set \parskip.
+%
+\def\internalpagesizes#1#2#3#4#5#6#7#8{%
+  \voffset = #3\relax
+  \topskip = #6\relax
+  \splittopskip = \topskip
+  %
+  \vsize = #1\relax
+  \advance\vsize by \topskip
+  \outervsize = \vsize
+  \advance\outervsize by 2\topandbottommargin
+  \pageheight = \vsize
+  %
+  \hsize = #2\relax
+  \outerhsize = \hsize
+  \advance\outerhsize by 0.5in
+  \pagewidth = \hsize
+  %
+  \normaloffset = #4\relax
+  \bindingoffset = #5\relax
+  %
+  \ifpdf
+    \pdfpageheight #7\relax
+    \pdfpagewidth #8\relax
+    % if we don't reset these, they will remain at "1 true in" of
+    % whatever layout pdftex was dumped with.
+    \pdfhorigin = 1 true in
+    \pdfvorigin = 1 true in
+  \fi
+  %
+  \setleading{\textleading}
+  %
+  \parindent = \defaultparindent
+  \setemergencystretch
+}
+
+% @letterpaper (the default).
+\def\letterpaper{{\globaldefs = 1
+  \parskip = 3pt plus 2pt minus 1pt
+  \textleading = 13.2pt
+  %
+  % If page is nothing but text, make it come out even.
+  \internalpagesizes{607.2pt}{6in}% that's 46 lines
+                    {\voffset}{.25in}%
+                    {\bindingoffset}{36pt}%
+                    {11in}{8.5in}%
+}}
+
+% Use @smallbook to reset parameters for 7x9.25 trim size.
+\def\smallbook{{\globaldefs = 1
+  \parskip = 2pt plus 1pt
+  \textleading = 12pt
+  %
+  \internalpagesizes{7.5in}{5in}%
+                    {-.2in}{0in}%
+                    {\bindingoffset}{16pt}%
+                    {9.25in}{7in}%
+  %
+  \lispnarrowing = 0.3in
+  \tolerance = 700
+  \hfuzz = 1pt
+  \contentsrightmargin = 0pt
+  \defbodyindent = .5cm
+}}
+
+% Use @smallerbook to reset parameters for 6x9 trim size.
+% (Just testing, parameters still in flux.)
+\def\smallerbook{{\globaldefs = 1
+  \parskip = 1.5pt plus 1pt
+  \textleading = 12pt
+  %
+  \internalpagesizes{7.4in}{4.8in}%
+                    {-.2in}{-.4in}%
+                    {0pt}{14pt}%
+                    {9in}{6in}%
+  %
+  \lispnarrowing = 0.25in
+  \tolerance = 700
+  \hfuzz = 1pt
+  \contentsrightmargin = 0pt
+  \defbodyindent = .4cm
+}}
+
+% Use @afourpaper to print on European A4 paper.
+\def\afourpaper{{\globaldefs = 1
+  \parskip = 3pt plus 2pt minus 1pt
+  \textleading = 13.2pt
+  %
+  % Double-side printing via postscript on Laserjet 4050
+  % prints double-sided nicely when \bindingoffset=10mm and \hoffset=-6mm.
+  % To change the settings for a different printer or situation, adjust
+  % \normaloffset until the front-side and back-side texts align.  Then
+  % do the same for \bindingoffset.  You can set these for testing in
+  % your texinfo source file like this:
+  % @tex
+  % \global\normaloffset = -6mm
+  % \global\bindingoffset = 10mm
+  % @end tex
+  \internalpagesizes{673.2pt}{160mm}% that's 51 lines
+                    {\voffset}{\hoffset}%
+                    {\bindingoffset}{44pt}%
+                    {297mm}{210mm}%
+  %
+  \tolerance = 700
+  \hfuzz = 1pt
+  \contentsrightmargin = 0pt
+  \defbodyindent = 5mm
+}}
+
+% Use @afivepaper to print on European A5 paper.
+% From romildo@urano.iceb.ufop.br, 2 July 2000.
+% He also recommends making @example and @lisp be small.
+\def\afivepaper{{\globaldefs = 1
+  \parskip = 2pt plus 1pt minus 0.1pt
+  \textleading = 12.5pt
+  %
+  \internalpagesizes{160mm}{120mm}%
+                    {\voffset}{\hoffset}%
+                    {\bindingoffset}{8pt}%
+                    {210mm}{148mm}%
+  %
+  \lispnarrowing = 0.2in
+  \tolerance = 800
+  \hfuzz = 1.2pt
+  \contentsrightmargin = 0pt
+  \defbodyindent = 2mm
+  \tableindent = 12mm
+}}
+
+% A specific text layout, 24x15cm overall, intended for A4 paper.
+\def\afourlatex{{\globaldefs = 1
+  \afourpaper
+  \internalpagesizes{237mm}{150mm}%
+                    {\voffset}{4.6mm}%
+                    {\bindingoffset}{7mm}%
+                    {297mm}{210mm}%
+  %
+  % Must explicitly reset to 0 because we call \afourpaper.
+  \globaldefs = 0
+}}
+
+% Use @afourwide to print on A4 paper in landscape format.
+\def\afourwide{{\globaldefs = 1
+  \afourpaper
+  \internalpagesizes{241mm}{165mm}%
+                    {\voffset}{-2.95mm}%
+                    {\bindingoffset}{7mm}%
+                    {297mm}{210mm}%
+  \globaldefs = 0
+}}
+
+% @pagesizes TEXTHEIGHT[,TEXTWIDTH]
+% Perhaps we should allow setting the margins, \topskip, \parskip,
+% and/or leading, also. Or perhaps we should compute them somehow.
+%
+\parseargdef\pagesizes{\pagesizesyyy #1,,\finish}
+\def\pagesizesyyy#1,#2,#3\finish{{%
+  \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \hsize=#2\relax \fi
+  \globaldefs = 1
+  %
+  \parskip = 3pt plus 2pt minus 1pt
+  \setleading{\textleading}%
+  %
+  \dimen0 = #1\relax
+  \advance\dimen0 by \voffset
+  %
+  \dimen2 = \hsize
+  \advance\dimen2 by \normaloffset
+  %
+  \internalpagesizes{#1}{\hsize}%
+                    {\voffset}{\normaloffset}%
+                    {\bindingoffset}{44pt}%
+                    {\dimen0}{\dimen2}%
+}}
+
+% Set default to letter.
+%
+\letterpaper
+
+
+\message{and turning on texinfo input format.}
+
+% Define macros to output various characters with catcode for normal text.
+\catcode`\"=\other
+\catcode`\~=\other
+\catcode`\^=\other
+\catcode`\_=\other
+\catcode`\|=\other
+\catcode`\<=\other
+\catcode`\>=\other
+\catcode`\+=\other
+\catcode`\$=\other
+\def\normaldoublequote{"}
+\def\normaltilde{~}
+\def\normalcaret{^}
+\def\normalunderscore{_}
+\def\normalverticalbar{|}
+\def\normalless{<}
+\def\normalgreater{>}
+\def\normalplus{+}
+\def\normaldollar{$}%$ font-lock fix
+
+% This macro is used to make a character print one way in \tt
+% (where it can probably be output as-is), and another way in other fonts,
+% where something hairier probably needs to be done.
+%
+% #1 is what to print if we are indeed using \tt; #2 is what to print
+% otherwise.  Since all the Computer Modern typewriter fonts have zero
+% interword stretch (and shrink), and it is reasonable to expect all
+% typewriter fonts to have this, we can check that font parameter.
+%
+\def\ifusingtt#1#2{\ifdim \fontdimen3\font=0pt #1\else #2\fi}
+
+% Same as above, but check for italic font.  Actually this also catches
+% non-italic slanted fonts since it is impossible to distinguish them from
+% italic fonts.  But since this is only used by $ and it uses \sl anyway
+% this is not a problem.
+\def\ifusingit#1#2{\ifdim \fontdimen1\font>0pt #1\else #2\fi}
+
+% Turn off all special characters except @
+% (and those which the user can use as if they were ordinary).
+% Most of these we simply print from the \tt font, but for some, we can
+% use math or other variants that look better in normal text.
+
+\catcode`\"=\active
+\def\activedoublequote{{\tt\char34}}
+\let"=\activedoublequote
+\catcode`\~=\active
+\def~{{\tt\char126}}
+\chardef\hat=`\^
+\catcode`\^=\active
+\def^{{\tt \hat}}
+
+\catcode`\_=\active
+\def_{\ifusingtt\normalunderscore\_}
+\let\realunder=_
+% Subroutine for the previous macro.
+\def\_{\leavevmode \kern.07em \vbox{\hrule width.3em height.1ex}\kern .07em }
+
+\catcode`\|=\active
+\def|{{\tt\char124}}
+\chardef \less=`\<
+\catcode`\<=\active
+\def<{{\tt \less}}
+\chardef \gtr=`\>
+\catcode`\>=\active
+\def>{{\tt \gtr}}
+\catcode`\+=\active
+\def+{{\tt \char 43}}
+\catcode`\$=\active
+\def${\ifusingit{{\sl\$}}\normaldollar}%$ font-lock fix
+
+% If a .fmt file is being used, characters that might appear in a file
+% name cannot be active until we have parsed the command line.
+% So turn them off again, and have \everyjob (or @setfilename) turn them on.
+% \otherifyactive is called near the end of this file.
+\def\otherifyactive{\catcode`+=\other \catcode`\_=\other}
+
+% Used sometimes to turn off (effectively) the active characters even after
+% parsing them.
+\def\turnoffactive{%
+  \normalturnoffactive
+  \otherbackslash
+}
+
+\catcode`\@=0
+
+% \backslashcurfont outputs one backslash character in current font,
+% as in \char`\\.
+\global\chardef\backslashcurfont=`\\
+\global\let\rawbackslashxx=\backslashcurfont  % let existing .??s files work
+
+% \realbackslash is an actual character `\' with catcode other, and
+% \doublebackslash is two of them (for the pdf outlines).
+{\catcode`\\=\other @gdef@realbackslash{\} @gdef@doublebackslash{\\}}
+
+% In texinfo, backslash is an active character; it prints the backslash
+% in fixed width font.
+\catcode`\\=\active
+@def@normalbackslash{{@tt@backslashcurfont}}
+% On startup, @fixbackslash assigns:
+%  @let \ = @normalbackslash
+
+% \rawbackslash defines an active \ to do \backslashcurfont.
+% \otherbackslash defines an active \ to be a literal `\' character with
+% catcode other.
+@gdef@rawbackslash{@let\=@backslashcurfont}
+@gdef@otherbackslash{@let\=@realbackslash}
+
+% Same as @turnoffactive except outputs \ as {\tt\char`\\} instead of
+% the literal character `\'.
+%
+@def@normalturnoffactive{%
+  @let\=@normalbackslash
+  @let"=@normaldoublequote
+  @let~=@normaltilde
+  @let^=@normalcaret
+  @let_=@normalunderscore
+  @let|=@normalverticalbar
+  @let<=@normalless
+  @let>=@normalgreater
+  @let+=@normalplus
+  @let$=@normaldollar %$ font-lock fix
+  @unsepspaces
+}
+
+% Make _ and + \other characters, temporarily.
+% This is canceled by @fixbackslash.
+@otherifyactive
+
+% If a .fmt file is being used, we don't want the `\input texinfo' to show up.
+% That is what \eatinput is for; after that, the `\' should revert to printing
+% a backslash.
+%
+@gdef@eatinput input texinfo{@fixbackslash}
+@global@let\ = @eatinput
+
+% On the other hand, perhaps the file did not have a `\input texinfo'. Then
+% the first `\' in the file would cause an error. This macro tries to fix
+% that, assuming it is called before the first `\' could plausibly occur.
+% Also turn back on active characters that might appear in the input
+% file name, in case not using a pre-dumped format.
+%
+@gdef@fixbackslash{%
+  @ifx\@eatinput @let\ = @normalbackslash @fi
+  @catcode`+=@active
+  @catcode`@_=@active
+}
+
+% Say @foo, not \foo, in error messages.
+@escapechar = `@@
+
+% These look ok in all fonts, so just make them not special.
+@catcode`@& = @other
+@catcode`@# = @other
+@catcode`@% = @other
+
+
+@c Local variables:
+@c eval: (add-hook 'write-file-hooks 'time-stamp)
+@c page-delimiter: "^\\\\message"
+@c time-stamp-start: "def\\\\texinfoversion{"
+@c time-stamp-format: "%:y-%02m-%02d.%02H"
+@c time-stamp-end: "}"
+@c End:
+
+@c vim:sw=2:
+
+@ignore
+   arch-tag: e1b36e32-c96e-4135-a41a-0b2efa2ea115
+@end ignore
index dd42ad4..6784c9e 100644 (file)
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
-
 """Demonstrate the use of the Assuan protocol engine"""
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:
     # Invoke the pinentry to get a confirmation.
     err = c.assuan_transact(['GET_CONFIRMATION', 'Hello there'])
index 987dfd1..4d99330 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Copyright (C) 2016 g10 Code GmbH
+# Copyright (C) 2016, 2018 g10 Code GmbH
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -14,7 +14,6 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
-
 """A decryption filter
 
 This demonstrates decryption using gpg3 in three lines of code.  To
@@ -25,8 +24,10 @@ be used like this:
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
+
+del absolute_import, print_function, unicode_literals
+
 gpg.Context().decrypt(sys.stdin, sink=sys.stdout)
index 12510f3..30b3145 100755 (executable)
 # It deletes keys for joe@example.org generated by genkey.py script
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 with gpg.Context() as c:
     # Note: We must not modify the key store during iteration,
     # therefore, we explicitly make a list.
index d84a01c..36ced57 100755 (executable)
 # It uses keys for joe+gpg@example.org generated by genkey.py script
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import os
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 user = "joe+gpg@example.org"
 
 with gpg.Context(armor=True) as c, gpg.Data() as expkey:
index a043500..710a530 100755 (executable)
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 # This is the example from the GPGME manual.
 
 parms = """<GnupgKeyParms format="internal">
index b868979..0c7bb89 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
diff --git a/lang/python/examples/howto/advanced/cython/keycount.pyx b/lang/python/examples/howto/advanced/cython/keycount.pyx
new file mode 100755 (executable)
index 0000000..2aa636d
--- /dev/null
@@ -0,0 +1,26 @@
+from __future__ import absolute_import
+
+import cython
+import gpg
+
+c = gpg.Context()
+seckeys = c.keylist(pattern=None, secret=True)
+pubkeys = c.keylist(pattern=None, secret=False)
+
+seclist = list(seckeys)
+secnum = len(seclist)
+
+publist = list(pubkeys)
+pubnum = len(publist)
+
+if cython.compiled is True:
+    cc = "Powered by Cython compiled C code."
+else:
+    cc = "Powered by Python."
+
+print("""
+    Number of secret keys:  {0}
+    Number of public keys:  {1}
+
+  {2}
+""".format(secnum, pubnum, cc))
diff --git a/lang/python/examples/howto/advanced/cython/setup.py b/lang/python/examples/howto/advanced/cython/setup.py
new file mode 100644 (file)
index 0000000..f8dce03
--- /dev/null
@@ -0,0 +1,6 @@
+from distutils.core import setup
+from Cython.Build import cythonize
+
+setup(
+    ext_modules = cythonize("keycount.pyx", annotate=True)
+)
index 597bbc5..9d350e2 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
index 429ab1f..02d1cb3 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
index 60a050b..8eba1f2 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
@@ -32,13 +32,20 @@ if len(sys.argv) == 3:
     newfile = sys.argv[2]
 elif len(sys.argv) == 2:
     ciphertext = sys.argv[1]
-    newfile = input("Enter path and filename of file to save decrypted data to: ")
+    newfile = input("Enter path and filename to save decrypted data to: ")
 else:
     ciphertext = input("Enter path and filename of encrypted file: ")
-    newfile = input("Enter path and filename of file to save decrypted data to: ")
+    newfile = input("Enter path and filename to save decrypted data to: ")
 
 with open(ciphertext, "rb") as cfile:
-    plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+    try:
+        plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+    except gpg.errors.GPGMEError as e:
+        plaintext = None
+        print(e)
 
-with open(newfile, "wb") as nfile:
-    nfile.write(plaintext)
+if plaintext is not None:
+    with open(newfile, "wb") as nfile:
+        nfile.write(plaintext)
+else:
+    pass
index 99fbe65..a0c5a2a 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
index ad4e1ce..8951cb5 100755 (executable)
@@ -3,6 +3,9 @@
 
 from __future__ import absolute_import, division, unicode_literals
 
+import gpg
+import sys
+
 # Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
 #
 # This program is free software; you can redistribute it and/or modify it under
@@ -18,15 +21,12 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
 # <http://www.gnu.org/licenses/>.
 
-import gpg
-import sys
-
 """
 Encrypts a file to a specified key.  If entering both the key and the filename
 on the command line, the key must be entered first.
@@ -55,7 +55,7 @@ with open(filename, "rb") as f:
 with gpg.Context(armor=True) as ca:
     try:
         ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey,
-                                                    sign=False)
+                                                     sign=False)
         with open("{0}.asc".format(filename), "wb") as fa:
             fa.write(ciphertext)
     except gpg.errors.InvalidRecipients as e:
@@ -64,7 +64,7 @@ with gpg.Context(armor=True) as ca:
 with gpg.Context() as cg:
     try:
         ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey,
-                                                    sign=False)
+                                                     sign=False)
         with open("{0}.gpg".format(filename), "wb") as fg:
             fg.write(ciphertext)
     except gpg.errors.InvalidRecipients as e:
index 41aaac8..6610ee0 100755 (executable)
@@ -3,6 +3,9 @@
 
 from __future__ import absolute_import, division, unicode_literals
 
+import gpg
+import sys
+
 # Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
 #
 # This program is free software; you can redistribute it and/or modify it under
@@ -18,15 +21,12 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
 # <http://www.gnu.org/licenses/>.
 
-import gpg
-import sys
-
 """
 Signs and encrypts a file to a specified key.  If entering both the key and the
 filename on the command line, the key must be entered first.
@@ -58,13 +58,13 @@ with open(filename, "rb") as f:
 with gpg.Context(armor=True) as ca:
     ciphertext, result, sign_result = ca.encrypt(text, recipients=rkey,
                                                  always_trust=True,
-                                                     add_encrypt_to=True)
+                                                 add_encrypt_to=True)
     with open("{0}.asc".format(filename), "wb") as fa:
         fa.write(ciphertext)
 
 with gpg.Context() as cg:
     ciphertext, result, sign_result = cg.encrypt(text, recipients=rkey,
                                                  always_trust=True,
-                                                     add_encrypt_to=True)
+                                                 add_encrypt_to=True)
     with open("{0}.gpg".format(filename), "wb") as fg:
         fg.write(ciphertext)
diff --git a/lang/python/examples/howto/encrypt-to-group-gullible.py b/lang/python/examples/howto/encrypt-to-group-gullible.py
new file mode 100755 (executable)
index 0000000..5ba82bc
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import sys
+from groups import group_lists
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""
+Uses the groups module to encrypt to multiple recipients.
+
+"""
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) > 3:
+    group_id = sys.argv[1]
+    filepath = sys.argv[2:]
+elif len(sys.argv) == 3:
+    group_id = sys.argv[1]
+    filepath = sys.argv[2]
+elif len(sys.argv) == 2:
+    group_id = sys.argv[1]
+    filepath = input("Enter the filename to encrypt: ")
+else:
+    group_id = input("Enter the group name to encrypt to: ")
+    filepath = input("Enter the filename to encrypt: ")
+
+with open(filepath, "rb") as f:
+    text = f.read()
+
+for i in range(len(group_lists)):
+    if group_lists[i][0] == group_id:
+        klist = group_lists[i][1]
+    else:
+        klist = None
+
+logrus = []
+
+if klist is not None:
+    for i in range(len(klist)):
+        apattern = list(c.keylist(pattern=klist[i], secret=False))
+        if apattern[0].can_encrypt == 1:
+            logrus.append(apattern[0])
+        else:
+            pass
+    try:
+        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                    add_encrypt_to=True)
+    except:
+        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                    add_encrypt_to=True,
+                                                    always_trust=True)
+    with open("{0}.asc".format(filepath), "wb") as f:
+        f.write(ciphertext)
+else:
+    pass
+
+# EOF
diff --git a/lang/python/examples/howto/encrypt-to-group-trustno1.py b/lang/python/examples/howto/encrypt-to-group-trustno1.py
new file mode 100755 (executable)
index 0000000..680c9ea
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import sys
+from groups import group_lists
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""
+Uses the groups module to encrypt to multiple recipients.
+
+"""
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) > 3:
+    group_id = sys.argv[1]
+    filepath = sys.argv[2:]
+elif len(sys.argv) == 3:
+    group_id = sys.argv[1]
+    filepath = sys.argv[2]
+elif len(sys.argv) == 2:
+    group_id = sys.argv[1]
+    filepath = input("Enter the filename to encrypt: ")
+else:
+    group_id = input("Enter the group name to encrypt to: ")
+    filepath = input("Enter the filename to encrypt: ")
+
+with open(filepath, "rb") as f:
+    text = f.read()
+
+for i in range(len(group_lists)):
+    if group_lists[i][0] == group_id:
+        klist = group_lists[i][1]
+    else:
+        klist = None
+
+logrus = []
+
+if klist is not None:
+    for i in range(len(klist)):
+        apattern = list(c.keylist(pattern=klist[i], secret=False))
+        if apattern[0].can_encrypt == 1:
+            logrus.append(apattern[0])
+        else:
+            pass
+    try:
+        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                    add_encrypt_to=True)
+    except gpg.errors.InvalidRecipients as e:
+        for i in range(len(e.recipients)):
+            for n in range(len(logrus)):
+                if logrus[n].fpr == e.recipients[i].fpr:
+                    logrus.remove(logrus[n])
+                else:
+                    pass
+        try:
+            ciphertext, result, sign_result = c.encrypt(text,
+                                                        recipients=logrus,
+                                                        add_encrypt_to=True)
+        except:
+            pass
+    with open("{0}.asc".format(filepath), "wb") as f:
+        f.write(ciphertext)
+else:
+    pass
+
+# EOF
diff --git a/lang/python/examples/howto/encrypt-to-group.py b/lang/python/examples/howto/encrypt-to-group.py
new file mode 100755 (executable)
index 0000000..e4ef1b6
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import sys
+from groups import group_lists
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""
+Uses the groups module to encrypt to multiple recipients.
+
+"""
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) > 3:
+    group_id = sys.argv[1]
+    filepath = sys.argv[2:]
+elif len(sys.argv) == 3:
+    group_id = sys.argv[1]
+    filepath = sys.argv[2]
+elif len(sys.argv) == 2:
+    group_id = sys.argv[1]
+    filepath = input("Enter the filename to encrypt: ")
+else:
+    group_id = input("Enter the group name to encrypt to: ")
+    filepath = input("Enter the filename to encrypt: ")
+
+with open(filepath, "rb") as f:
+    text = f.read()
+
+for i in range(len(group_lists)):
+    if group_lists[i][0] == group_id:
+        klist = group_lists[i][1]
+    else:
+        klist = None
+
+logrus = []
+
+if klist is not None:
+    for i in range(len(klist)):
+        apattern = list(c.keylist(pattern=klist[i], secret=False))
+        if apattern[0].can_encrypt == 1:
+            logrus.append(apattern[0])
+        else:
+            pass
+    try:
+        ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+                                                    add_encrypt_to=True)
+    except gpg.errors.InvalidRecipients as e:
+        for i in range(len(e.recipients)):
+            for n in range(len(logrus)):
+                if logrus[n].fpr == e.recipients[i].fpr:
+                    logrus.remove(logrus[n])
+                else:
+                    pass
+        try:
+            ciphertext, result, sign_result = c.encrypt(text,
+                                                        recipients=logrus,
+                                                        add_encrypt_to=True,
+                                                        always_trust=True)
+        except:
+            pass
+    with open("{0}.asc".format(filepath), "wb") as f:
+        f.write(ciphertext)
+else:
+    pass
+
+# EOF
diff --git a/lang/python/examples/howto/export-key.py b/lang/python/examples/howto/export-key.py
new file mode 100755 (executable)
index 0000000..ff3345a
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os.path
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script exports one or more public keys.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the key(s) to: ")
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if homedir is not None and os.path.exists(homedir) is False:
+    homedir = None
+elif homedir is not None and os.path.exists(homedir) is True:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export(pattern=logrus)
+except:
+    result = c.key_export(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+else:
+    pass
diff --git a/lang/python/examples/howto/export-minimised-key.py b/lang/python/examples/howto/export-minimised-key.py
new file mode 100755 (executable)
index 0000000..ffd7524
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os.path
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script exports one or more public keys in minimised form.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the key(s) to: ")
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if homedir is not None and os.path.exists(homedir) is False:
+    homedir = None
+elif homedir is not None and os.path.exists(homedir) is True:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export_minimal(pattern=logrus)
+except:
+    result = c.key_export_minimal(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+else:
+    pass
diff --git a/lang/python/examples/howto/export-secret-key.py b/lang/python/examples/howto/export-secret-key.py
new file mode 100755 (executable)
index 0000000..af1aa70
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os
+import os.path
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script exports one or more secret keys.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if homedir is not None and os.path.exists(homedir) is False:
+    homedir = None
+elif homedir is not None and os.path.exists(homedir) is True:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+try:
+    result = c.key_export_secret(pattern=logrus)
+except:
+    result = c.key_export_secret(pattern=None)
+
+if result is not None:
+    with open(keyfile, "wb") as f:
+        f.write(result)
+    os.chmod(keyfile, 0o600)
+else:
+    pass
diff --git a/lang/python/examples/howto/export-secret-keys.py b/lang/python/examples/howto/export-secret-keys.py
new file mode 100755 (executable)
index 0000000..f4e5008
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os
+import os.path
+import subprocess
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script exports one or more secret keys as both ASCII armored and binary
+file formats, saved in files within the user's GPG home directory.
+
+The gpg-agent and pinentry are invoked to authorise the export.
+""")
+
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+else:
+    gpgconfcmd = "gpgconf --list-dirs homedir"
+
+a = gpg.Context(armor=True)
+b = gpg.Context()
+c = gpg.Context()
+
+if len(sys.argv) >= 4:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    keyfile = sys.argv[1]
+    logrus = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the filename to save the secret key to: ")
+    logrus = input("Enter the UID matching the secret key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if homedir is not None and os.path.exists(homedir) is False:
+    homedir = None
+elif homedir is not None and os.path.exists(homedir) is True:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+if c.home_dir is not None:
+    if c.home_dir.endswith("/"):
+        gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
+        ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
+    else:
+        gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
+        ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
+else:
+    if os.path.exists(os.environ["GNUPGHOME"]) is True:
+        hd = os.environ["GNUPGHOME"]
+    else:
+        try:
+            hd = subprocess.getoutput(gpgconfcmd)
+        except:
+            process = subprocess.Popen(gpgconfcmd.split(),
+                                       stdout=subprocess.PIPE)
+            procom = process.communicate()
+            if sys.version_info[0] == 2:
+                hd = procom[0].strip()
+            else:
+                hd = procom[0].decode().strip()
+    gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
+    ascfile = "{0}/{1}.asc".format(hd, keyfile)
+
+try:
+    a_result = a.key_export_secret(pattern=logrus)
+    b_result = b.key_export_secret(pattern=logrus)
+except:
+    a_result = a.key_export_secret(pattern=None)
+    b_result = b.key_export_secret(pattern=None)
+
+if a_result is not None:
+    with open(ascfile, "wb") as f:
+        f.write(a_result)
+    os.chmod(ascfile, 0o600)
+else:
+    pass
+
+if b_result is not None:
+    with open(gpgfile, "wb") as f:
+        f.write(b_result)
+    os.chmod(gpgfile, 0o600)
+else:
+    pass
index 5e7fdf6..3d51b25 100644 (file)
@@ -17,13 +17,14 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
 # <http://www.gnu.org/licenses/>.
 
 import subprocess
+import sys
 
 """
 Intended for use with other scripts.
@@ -31,7 +32,20 @@ Intended for use with other scripts.
 Usage: from groups import group_lists
 """
 
-lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines()
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-options gpg"
+else:
+    gpgconfcmd = "gpgconf --list-options gpg"
+
+try:
+    lines = subprocess.getoutput(gpgconfcmd).splitlines()
+except:
+    process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+    procom = process.communicate()
+    if sys.version_info[0] == 2:
+        lines = procom[0].splitlines()
+    else:
+        lines = procom[0].decode().splitlines()
 
 for i in range(len(lines)):
     if lines[i].startswith("group") is True:
@@ -41,10 +55,12 @@ for i in range(len(lines)):
 
 groups = line.split(":")[-1].replace('"', '').split(',')
 
-group_lines = groups
-for i in range(len(group_lines)):
-    group_lines[i] = group_lines[i].split("=")
+group_lines = []
+group_lists = []
+
+for i in range(len(groups)):
+    group_lines.append(groups[i].split("="))
+    group_lists.append(groups[i].split("="))
 
-group_lists = group_lines
 for i in range(len(group_lists)):
     group_lists[i][1] = group_lists[i][1].split()
diff --git a/lang/python/examples/howto/import-key.py b/lang/python/examples/howto/import-key.py
new file mode 100755 (executable)
index 0000000..ab52ba2
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os.path
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script imports one or more public keys from a single file.
+""")
+
+c = gpg.Context(armor=True)
+
+if len(sys.argv) >= 3:
+    keyfile = sys.argv[1]
+    homedir = sys.argv[2]
+elif len(sys.argv) == 2:
+    keyfile = sys.argv[1]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyfile = input("Enter the path and filename to import the key(s) from: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+    if os.path.exists(os.path.expanduser(homedir)) is True:
+        c.home_dir = os.path.expanduser(homedir)
+    else:
+        pass
+elif os.path.exists(homedir) is True:
+    c.home_dir = homedir
+else:
+    pass
+
+if os.path.isfile(keyfile) is True:
+    with open(keyfile, "rb") as f:
+        incoming = f.read()
+    result = c.key_import(incoming)
+else:
+    result = None
+
+if result is not None and hasattr(result, "considered") is False:
+    print(result)
+elif result is not None and hasattr(result, "considered") is True:
+    num_keys = len(result.imports)
+    new_revs = result.new_revocations
+    new_sigs = result.new_signatures
+    new_subs = result.new_sub_keys
+    new_uids = result.new_user_ids
+    new_scrt = result.secret_imported
+    nochange = result.unchanged
+    print("""
+The total number of keys considered for import was:  {0}
+
+   Number of keys revoked:  {1}
+ Number of new signatures:  {2}
+    Number of new subkeys:  {3}
+   Number of new user IDs:  {4}
+Number of new secret keys:  {5}
+ Number of unchanged keys:  {6}
+
+The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+    for i in range(num_keys):
+        print(result.imports[i].fpr)
+    print("")
+elif result is None:
+    print("You must specify a key file to import.")
diff --git a/lang/python/examples/howto/import-keys-hkp.py b/lang/python/examples/howto/import-keys-hkp.py
new file mode 100755 (executable)
index 0000000..d50e465
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script imports one or more public keys from the SKS keyservers.
+""")
+
+c = gpg.Context()
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+results = []
+
+if len(sys.argv) > 2:
+    pattern = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    pattern = sys.argv[1]
+else:
+    pattern = input("Enter the pattern to search for keys or user IDs: ")
+
+try:
+    keys = server.search(pattern)
+    print("Found {0} key(s).".format(len(keys)))
+except Exception as e:
+    keys = []
+    for logrus in pattern.split():
+        if logrus.startswith("0x") is True:
+            key = server.search(logrus)
+        else:
+            key = server.search("0x{0}".format(logrus))
+        keys.append(key[0])
+    print("Found {0} key(s).".format(len(keys)))
+
+for key in keys:
+    import_result = c.key_import(key.key_blob)
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print(result)
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+   Number of keys revoked:  {1}
+ Number of new signatures:  {2}
+    Number of new subkeys:  {3}
+   Number of new user IDs:  {4}
+Number of new secret keys:  {5}
+ Number of unchanged keys:  {6}
+
+The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    else:
+        pass
diff --git a/lang/python/examples/howto/import-keys.py b/lang/python/examples/howto/import-keys.py
new file mode 100755 (executable)
index 0000000..d1a268e
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os.path
+import requests
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script imports one or more public keys from the SKS keyservers.
+""")
+
+c = gpg.Context()
+url = "https://sks-keyservers.net/pks/lookup"
+pattern = input("Enter the pattern to search for key or user IDs: ")
+payload = {"op": "get", "search": pattern}
+
+r = requests.get(url, verify=True, params=payload)
+result = c.key_import(r.content)
+
+if result is not None and hasattr(result, "considered") is False:
+    print(result)
+elif result is not None and hasattr(result, "considered") is True:
+    num_keys = len(result.imports)
+    new_revs = result.new_revocations
+    new_sigs = result.new_signatures
+    new_subs = result.new_sub_keys
+    new_uids = result.new_user_ids
+    new_scrt = result.secret_imported
+    nochange = result.unchanged
+    print("""
+The total number of keys considered for import was:  {0}
+
+   Number of keys revoked:  {1}
+ Number of new signatures:  {2}
+    Number of new subkeys:  {3}
+   Number of new user IDs:  {4}
+Number of new secret keys:  {5}
+ Number of unchanged keys:  {6}
+
+The key IDs for all considered keys were:
+""".format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+    for i in range(num_keys):
+        print(result.imports[i].fpr)
+    print("")
+else:
+    pass
index 8e25454..af47272 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
diff --git a/lang/python/examples/howto/local-sign-group.py b/lang/python/examples/howto/local-sign-group.py
new file mode 100755 (executable)
index 0000000..0353646
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os.path
+import subprocess
+import sys
+
+from groups import group_lists
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script applies a local signature or certification to every key in a group.
+
+Usage: local-sign-group.py <group name> [signing keyid] [gnupg homedir]
+""")
+
+c = gpg.Context(armor=True)
+mkfpr = None
+defkey_fpr = None
+enckey_fpr = None
+to_certify = []
+
+if len(sys.argv) >= 4:
+    clique = sys.argv[1]
+    sigkey = sys.argv[2]
+    homedir = sys.argv[3]
+elif len(sys.argv) == 3:
+    clique = sys.argv[1]
+    sigkey = sys.argv[2]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+elif len(sys.argv) == 2:
+    clique = sys.argv[1]
+    sigkey = input("Enter the key ID to sign with (conditionally optional): ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    clique = input("Enter the group matching the key(s) to locally sign: ")
+    sigkey = input("Enter the key ID to sign with (conditionally optional): ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if homedir is not None and os.path.exists(homedir) is False:
+    homedir = None
+elif homedir is not None and os.path.exists(homedir) is True:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+if len(sigkey) == 0:
+    sigkey = None
+else:
+    pass
+
+if sys.platform == "win32":
+    gpgconfcmd = "gpgconf.exe --list-options gpg"
+else:
+    gpgconfcmd = "gpgconf --list-options gpg"
+
+try:
+    lines = subprocess.getoutput(gpgconfcmd).splitlines()
+except:
+    process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+    procom = process.communicate()
+    if sys.version_info[0] == 2:
+        lines = procom[0].splitlines()
+    else:
+        lines = procom[0].decode().splitlines()
+
+for i in range(len(lines)):
+    if lines[i].startswith("default-key") is True:
+        dline = lines[i]
+    elif lines[i].startswith("encrypt-to") is True:
+        eline = lines[i]
+    else:
+        pass
+
+defkey_fpr = dline.split(":")[-1].replace('"', '').split(',')[0].upper()
+enckey_fpr = eline.split(":")[-1].replace('"', '').split(',')[0].upper()
+
+try:
+    dkey = c.keylist(pattern=defkey_fpr, secret=True)
+    dk = list(dkey)
+except Exception as de:
+    print(de)
+    dk = None
+    print("No valid default key.")
+
+try:
+    ekey = c.keylist(pattern=defkey_fpr, secret=True)
+    ek = list(ekey)
+except Exception as ee:
+    print(ee)
+    ek = None
+    print("No valid always encrypt to key.")
+
+if sigkey is not None:
+    mykey = c.keylist(pattern=sigkey, secret=True)
+    mk = list(mykey)
+    mkfpr = mk[0].fpr.upper()
+    c.signers = mk
+else:
+    if dk is None and ek is not None:
+        c.signers = ek
+    else:
+        pass
+
+for group in group_lists:
+    if group[0] == clique:
+        for logrus in group[1]:
+            khole = c.keylist(pattern=logrus)
+            k = list(khole)
+            to_certify.append(k[0].fpr.upper())
+    else:
+        pass
+
+if mkfpr is not None:
+    if to_certify.count(mkfpr) > 0:
+        for n in range(to_certify.count(mkfpr)):
+            to_certify.remove(mkfpr)
+    else:
+        pass
+else:
+    pass
+
+if defkey_fpr is not None:
+    if to_certify.count(defkey_fpr) > 0:
+        for n in range(to_certify.count(defkey_fpr)):
+            to_certify.remove(defkey_fpr)
+    else:
+        pass
+else:
+    pass
+
+if enckey_fpr is not None:
+    if to_certify.count(enckey_fpr) > 0:
+        for n in range(to_certify.count(enckey_fpr)):
+            to_certify.remove(enckey_fpr)
+    else:
+        pass
+else:
+    pass
+
+for fpr in to_certify:
+    key = c.get_key(fpr)
+    c.key_sign(key, uids=None, expires_in=False, local=True)
diff --git a/lang/python/examples/howto/mutt-groups.py b/lang/python/examples/howto/mutt-groups.py
new file mode 100755 (executable)
index 0000000..6572a21
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import sys
+from groups import group_lists
+
+"""
+Uses the groups module to generate Mutt crypt-hooks from gpg.conf.
+
+"""
+
+if len(sys.argv) >= 2:
+    hook_file = sys.argv[1]
+else:
+    hook_file = input("Enter the filename to save the crypt-hooks in: ")
+
+with open(hook_file, "w") as f:
+    f.write("""# Change settings based upon message recipient
+#
+#      send-hook [!]<pattern> <command>
+#
+# <command> is executed when sending mail to an address matching <pattern>
+#
+# crypt-hook regexp key-id
+#     The crypt-hook command provides a method by which you can
+#     specify the ID of the public key to be used when encrypting
+#     messages to a certain recipient.  The meaning of "key ID" is to
+#     be taken broadly: This can be a different e-mail address, a
+#     numerical key ID, or even just an arbitrary search string.  You
+#     may use multiple crypt-hooks with the same regexp; multiple
+#     matching crypt-hooks result in the use of multiple key-ids for a
+#     recipient.
+""")
+
+for n in range(len(group_lists)):
+    rule = group_lists[n][0].replace(".", "\\\\.")
+    with open(hook_file, "a") as f:
+        f.write("\n")
+        f.write("# {0}\n".format(group_lists[n][0]))
+        for i in range(len(group_lists[n][1])):
+            f.write("crypt-hook {0} {1}\n".format(rule, group_lists[n][1][i]))
diff --git a/lang/python/examples/howto/pmkey-import-alt.py b/lang/python/examples/howto/pmkey-import-alt.py
new file mode 100755 (executable)
index 0000000..c7822f3
--- /dev/null
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import os.path
+import requests
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.  Optionally enables specifying a different GnuPG home directory.
+
+Usage:  pmkey-import-alt.py [search string] [homedir]
+""")
+
+c = gpg.Context(armor=True)
+url = "https://api.protonmail.ch/pks/lookup"
+ksearch = []
+
+if len(sys.argv) >= 3:
+    keyterm = sys.argv[1]
+    homedir = sys.argv[2]
+elif len(sys.argv) == 2:
+    keyterm = sys.argv[1]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if homedir.startswith("~"):
+    if os.path.exists(os.path.expanduser(homedir)) is True:
+        c.home_dir = os.path.expanduser(homedir)
+    else:
+        pass
+elif os.path.exists(homedir) is True:
+    c.home_dir = homedir
+else:
+    pass
+
+if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+    ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+    ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+    ksearch.append("{0}@pm.me".format(keyterm[1:]))
+elif keyterm.count("@") == 0:
+    ksearch.append("{0}@protonmail.com".format(keyterm))
+    ksearch.append("{0}@protonmail.ch".format(keyterm))
+    ksearch.append("{0}@pm.me".format(keyterm))
+elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+    uidlist = keyterm.split("@")
+    for uid in uidlist:
+        ksearch.append("{0}@protonmail.com".format(uid))
+        ksearch.append("{0}@protonmail.ch".format(uid))
+        ksearch.append("{0}@pm.me".format(uid))
+elif keyterm.count("@") > 2:
+    uidlist = keyterm.split("@")
+    for uid in uidlist:
+        ksearch.append("{0}@protonmail.com".format(uid))
+        ksearch.append("{0}@protonmail.ch".format(uid))
+        ksearch.append("{0}@pm.me".format(uid))
+else:
+    ksearch.append(keyterm)
+
+for k in ksearch:
+    payload = {"op": "get", "search": k}
+    try:
+        r = requests.get(url, verify=True, params=payload)
+        if r.ok is True:
+            result = c.key_import(r.content)
+        elif r.ok is False:
+            result = r.content
+    except Exception as e:
+        result = None
+
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        print(e)
diff --git a/lang/python/examples/howto/pmkey-import-hkp-alt.py b/lang/python/examples/howto/pmkey-import-hkp-alt.py
new file mode 100755 (executable)
index 0000000..61fcd8d
--- /dev/null
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import os.path
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.  Optionally enables specifying a different GnuPG home directory.
+
+Usage:  pmkey-import-hkp.py [homedir] [search string]
+   or:  pmkey-import-hkp.py [search string]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 3:
+    homedir = sys.argv[1]
+    keyterms = sys.argv[2:]
+elif len(sys.argv) == 3:
+    homedir = sys.argv[1]
+    keyterm = sys.argv[2]
+    keyterms.append(keyterm)
+elif len(sys.argv) == 2:
+    homedir = ""
+    keyterm = sys.argv[1]
+    keyterms.append(keyterm)
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+    keyterms.append(keyterm)
+
+if len(homedir) == 0:
+    homedir = None
+    homeless = False
+
+if homedir is not None:
+    if homedir.startswith("~"):
+        if os.path.exists(os.path.expanduser(homedir)) is True:
+            if os.path.isdir(os.path.expanduser(homedir)) is True:
+                c.home_dir = os.path.realpath(os.path.expanduser(homedir))
+            else:
+                homeless = True
+        else:
+            homeless = True
+    elif os.path.exists(os.path.realpath(homedir)) is True:
+        if os.path.isdir(os.path.realpath(homedir)) is True:
+            c.home_dir = os.path.realpath(homedir)
+        else:
+            homeless = True
+    else:
+        homeless = True
+
+# First check to see if the homedir really is a homedir and if not, treat it as
+# a search string.
+if homeless is True:
+    keyterms.append(homedir)
+    c.home_dir = None
+else:
+    pass
+
+for keyterm in keyterms:
+    if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+    elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+        ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+        ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+        ksearch.append("{0}@pm.me".format(keyterm[1:]))
+    elif keyterm.count("@") == 0:
+        ksearch.append("{0}@protonmail.com".format(keyterm))
+        ksearch.append("{0}@protonmail.ch".format(keyterm))
+        ksearch.append("{0}@pm.me".format(keyterm))
+    elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    elif keyterm.count("@") > 2:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    else:
+        ksearch.append(keyterm)
+
+for k in ksearch:
+    print("Checking for key for: {0}".format(k))
+    try:
+        keys = server.search(k)
+        if isinstance(keys, list) is True:
+            for key in keys:
+                allkeys.append(key)
+                try:
+                    import_result = c.key_import(key.key_blob)
+                except Exception as e:
+                    import_result = c.key_import(key.key)
+        else:
+            paradox.append(keys)
+            import_result = None
+    except Exception as e:
+        import_result = None
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        pass
diff --git a/lang/python/examples/howto/pmkey-import-hkp.py b/lang/python/examples/howto/pmkey-import-hkp.py
new file mode 100755 (executable)
index 0000000..66223a9
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import os.path
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+
+Usage:  pmkey-import-hkp.py [search strings]
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://api.protonmail.ch")
+keyterms = []
+ksearch = []
+allkeys = []
+results = []
+paradox = []
+homeless = None
+
+if len(sys.argv) > 2:
+    keyterms = sys.argv[1:]
+elif len(sys.argv) == 2:
+    keyterm = sys.argv[1]
+    keyterms.append(keyterm)
+else:
+    key_term = input("Enter the key ID, UID or search string: ")
+    keyterms = key_term.split()
+
+for keyterm in keyterms:
+    if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+        ksearch.append(keyterm[1:])
+    elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+        ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+        ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+        ksearch.append("{0}@pm.me".format(keyterm[1:]))
+    elif keyterm.count("@") == 0:
+        ksearch.append("{0}@protonmail.com".format(keyterm))
+        ksearch.append("{0}@protonmail.ch".format(keyterm))
+        ksearch.append("{0}@pm.me".format(keyterm))
+    elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    elif keyterm.count("@") > 2:
+        uidlist = keyterm.split("@")
+        for uid in uidlist:
+            ksearch.append("{0}@protonmail.com".format(uid))
+            ksearch.append("{0}@protonmail.ch".format(uid))
+            ksearch.append("{0}@pm.me".format(uid))
+    else:
+        ksearch.append(keyterm)
+
+for k in ksearch:
+    print("Checking for key for: {0}".format(k))
+    try:
+        keys = server.search(k)
+        if isinstance(keys, list) is True:
+            for key in keys:
+                allkeys.append(key)
+                try:
+                    import_result = c.key_import(key.key_blob)
+                except Exception as e:
+                    import_result = c.key_import(key.key)
+        else:
+            paradox.append(keys)
+            import_result = None
+    except Exception as e:
+        import_result = None
+    results.append(import_result)
+
+for result in results:
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        pass
diff --git a/lang/python/examples/howto/pmkey-import.py b/lang/python/examples/howto/pmkey-import.py
new file mode 100755 (executable)
index 0000000..58a01c8
--- /dev/null
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import requests
+import sys
+
+del absolute_import, division, unicode_literals
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script searches the ProtonMail key server for the specified key and
+imports it.
+
+Usage:  pmkey-import.py [search string]
+""")
+
+c = gpg.Context(armor=True)
+url = "https://api.protonmail.ch/pks/lookup"
+ksearch = []
+
+if len(sys.argv) >= 2:
+    keyterm = sys.argv[1]
+else:
+    keyterm = input("Enter the key ID, UID or search string: ")
+
+if keyterm.count("@") == 2 and keyterm.startswith("@") is True:
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+    ksearch.append(keyterm[1:])
+elif keyterm.count("@") == 1 and keyterm.startswith("@") is True:
+    ksearch.append("{0}@protonmail.com".format(keyterm[1:]))
+    ksearch.append("{0}@protonmail.ch".format(keyterm[1:]))
+    ksearch.append("{0}@pm.me".format(keyterm[1:]))
+elif keyterm.count("@") == 0:
+    ksearch.append("{0}@protonmail.com".format(keyterm))
+    ksearch.append("{0}@protonmail.ch".format(keyterm))
+    ksearch.append("{0}@pm.me".format(keyterm))
+elif keyterm.count("@") == 2 and keyterm.startswith("@") is False:
+    uidlist = keyterm.split("@")
+    for uid in uidlist:
+        ksearch.append("{0}@protonmail.com".format(uid))
+        ksearch.append("{0}@protonmail.ch".format(uid))
+        ksearch.append("{0}@pm.me".format(uid))
+elif keyterm.count("@") > 2:
+    uidlist = keyterm.split("@")
+    for uid in uidlist:
+        ksearch.append("{0}@protonmail.com".format(uid))
+        ksearch.append("{0}@protonmail.ch".format(uid))
+        ksearch.append("{0}@pm.me".format(uid))
+else:
+    ksearch.append(keyterm)
+
+for k in ksearch:
+    payload = {"op": "get", "search": k}
+    try:
+        r = requests.get(url, verify=True, params=payload)
+        if r.ok is True:
+            result = c.key_import(r.content)
+        elif r.ok is False:
+            result = r.content
+    except Exception as e:
+        result = None
+
+    if result is not None and hasattr(result, "considered") is False:
+        print("{0} for {1}".format(result.decode(), k))
+    elif result is not None and hasattr(result, "considered") is True:
+        num_keys = len(result.imports)
+        new_revs = result.new_revocations
+        new_sigs = result.new_signatures
+        new_subs = result.new_sub_keys
+        new_uids = result.new_user_ids
+        new_scrt = result.secret_imported
+        nochange = result.unchanged
+        print("""
+The total number of keys considered for import was:  {0}
+
+With UIDs wholely or partially matching the following string:
+
+        {1}
+
+   Number of keys revoked:  {2}
+ Number of new signatures:  {3}
+    Number of new subkeys:  {4}
+   Number of new user IDs:  {5}
+Number of new secret keys:  {6}
+ Number of unchanged keys:  {7}
+
+The key IDs for all considered keys were:
+""".format(num_keys, k, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+           nochange))
+        for i in range(num_keys):
+            print(result.imports[i].fpr)
+        print("")
+    elif result is None:
+        print(e)
index 7a3d190..be34748 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
diff --git a/lang/python/examples/howto/send-key-to-keyserver.py b/lang/python/examples/howto/send-key-to-keyserver.py
new file mode 100755 (executable)
index 0000000..261c8b6
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import hkp4py
+import os.path
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+print("""
+This script sends one or more public keys to the SKS keyservers and is
+essentially a slight variation on the export-key.py script.
+
+The default is to send all keys if there is no pattern or search term.
+""")
+
+c = gpg.Context(armor=True)
+server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
+
+if len(sys.argv) >= 3:
+    logrus = sys.argv[1]
+    homedir = sys.argv[2]
+elif len(sys.argv) == 2:
+    logrus = sys.argv[1]
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+else:
+    logrus = input("Enter the UID matching the key(s) to export: ")
+    homedir = input("Enter the GPG configuration directory path (optional): ")
+
+if len(homedir) == 0:
+    homedir = None
+elif homedir.startswith("~"):
+    userdir = os.path.expanduser(homedir)
+    if os.path.exists(userdir) is True:
+        homedir = os.path.realpath(userdir)
+    else:
+        homedir = None
+else:
+    homedir = os.path.realpath(homedir)
+
+if homedir is not None and os.path.exists(homedir) is False:
+    homedir = None
+elif homedir is not None and os.path.exists(homedir) is True:
+    if os.path.isdir(homedir) is False:
+        homedir = None
+    else:
+        pass
+
+if homedir is not None:
+    c.home_dir = homedir
+else:
+    pass
+
+if len(logrus) > 0:
+    try:
+        export_result = c.key_export(pattern=logrus)
+    except Exception as e:
+        print(e)
+        export_result = None
+else:
+    export_result = c.key_export(pattern=None)
+
+if export_result is not None:
+    try:
+        try:
+            send_result = server.add(export_result)
+        except:
+            send_result = server.add(export_result.decode())
+        if send_result is not None:
+            print(send_result)
+        else:
+            pass
+    except Exception as e:
+        print(e)
+else:
+    pass
index 01006df..f680965 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
index b1afe13..6d32c57 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
diff --git a/lang/python/examples/howto/symcrypt-file.py b/lang/python/examples/howto/symcrypt-file.py
new file mode 100755 (executable)
index 0000000..bce9286
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, unicode_literals
+
+import gpg
+import sys
+
+# Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
+#
+# This program 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.
+#
+# This program 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 program 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 General Public License and the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License and the GNU
+# Lesser General Public along with this program; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""
+Symmetrically encrypts a file.  Passphrase will be prompted for via Pinentry.
+
+Will produce both an ASCII armoured and GPG binary format copy of the encrypted
+file.
+"""
+
+if len(sys.argv) > 2:
+    filename = " ".join(sys.argv[1:])
+elif len(sys.argv) == 2:
+    filename = sys.argv[1]
+else:
+    filename = input("Enter the path and filename to encrypt: ")
+
+with open(filename, "rb") as f:
+    text = f.read()
+
+with gpg.Context(armor=True) as ca:
+    try:
+        ciphertext, result, sign_result = ca.encrypt(text, passphrase=None,
+                                                     sign=False)
+        with open("{0}.asc".format(filename), "wb") as fa:
+            fa.write(ciphertext)
+    except gpg.errors.GPGMEError as e:
+        print(e)
+
+with gpg.Context() as cg:
+    try:
+        ciphertext, result, sign_result = cg.encrypt(text, passphrase=None,
+                                                     sign=False)
+        with open("{0}.gpg".format(filename), "wb") as fg:
+            fg.write(ciphertext)
+    except gpg.errors.GPGMEError as e:
+        print(e)
index ddd7932..98bde36 100755 (executable)
@@ -3,6 +3,10 @@
 
 from __future__ import absolute_import, division, unicode_literals
 
+import os
+import os.path
+import sys
+
 # Copyright (C) 2018 Ben McGinnes <ben@gnupg.org>
 #
 # This program is free software; you can redistribute it and/or modify it under
@@ -18,16 +22,12 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
 # <http://www.gnu.org/licenses/>.
 
-import os
-import os.path
-import sys
-
 intro = """
 This script creates a temporary directory to use as a homedir for
 testing key generation tasks with the correct permissions, along
@@ -54,6 +54,13 @@ message telling you to specify a new directory name.  There is no
 default directory name.
 """
 
+ciphers256 = "TWOFISH CAMELLIA256 AES256"
+ciphers192 = "CAMELLIA192 AES192"
+ciphers128 = "CAMELLIA128 AES"
+ciphersBad = "BLOWFISH IDEA CAST5 3DES"
+digests = "SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1"
+compress = "ZLIB BZIP2 ZIP Uncompressed"
+
 gpgconf = """# gpg.conf settings for key generation:
 expert
 allow-freeform-uid
@@ -63,11 +70,11 @@ tofu-default-policy unknown
 enable-large-rsa
 enable-dsa2
 cert-digest-algo SHA512
-default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
-personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
-personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
-personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
-"""
+default-preference-list {0} {1} {2} {3} {4} {5}
+personal-cipher-preferences {0} {1} {2} {3}
+personal-digest-preferences {4}
+personal-compress-preferences {5}
+""".format(ciphers256, ciphers192, ciphers128, ciphersBad, digests, compress)
 
 agentconf = """# gpg-agent.conf settings for key generation:
 default-cache-ttl 300
@@ -84,17 +91,17 @@ else:
 userdir = os.path.expanduser("~")
 
 if new_homedir.startswith("~"):
-    new_homdir.replace("~", "")
+    new_homedir.replace("~", "")
 else:
     pass
 
 if new_homedir.startswith("/"):
-    new_homdir.replace("/", "")
+    new_homedir.replace("/", "")
 else:
     pass
 
 if new_homedir.startswith("."):
-    new_homdir.replace(".", "_")
+    new_homedir.replace(".", "_")
 else:
     pass
 
index 8aafc3b..aa4331c 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
index 9f8702f..c561fe8 100755 (executable)
@@ -18,7 +18,7 @@ from __future__ import absolute_import, division, unicode_literals
 # This program 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 General Public License and the GNU
-# Lesser General Public Licensefor more details.
+# Lesser General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License and the GNU
 # Lesser General Public along with this program; if not, see
index ed0d8c4..5b58c97 100644 (file)
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
-
 """Simple interactive editor to test editor scripts"""
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 if len(sys.argv) != 2:
     sys.exit("Usage: %s <Gpg key pattern>\n" % sys.argv[0])
 
@@ -40,10 +40,12 @@ with gpg.Context() as c:
     print("Editing key {} ({}):".format(key.uids[0].uid, key.subkeys[0].fpr))
 
     def edit_fnc(keyword, args):
-        print("Status: {}, args: {} > ".format(
-            keyword, args), end='', flush=True)
+        print(
+            "Status: {}, args: {} > ".format(keyword, args),
+            end='',
+            flush=True)
 
-        if not 'GET' in keyword:
+        if 'GET' not in keyword:
             # no prompt
             print()
             return None
index bad4220..5c10d3d 100755 (executable)
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
-
 """
 This program will try to encrypt a simple message to each key on your
 keyring.  If your keyring has any invalid keys on it, those keys will
 be skipped and it will re-try the encryption."""
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 with gpg.Context(armor=True) as c:
     recipients = list()
     for key in c.keylist():
@@ -40,14 +40,15 @@ with gpg.Context(armor=True) as c:
     while not ciphertext:
         print("Encrypting to %d recipients" % len(recipients))
         try:
-            ciphertext, _, _ = c.encrypt(b'This is my message.',
-                                         recipients=recipients)
+            ciphertext, _, _ = c.encrypt(
+                b'This is my message.', recipients=recipients)
         except gpg.errors.InvalidRecipients as e:
             print("Encryption failed for these keys:\n{0!s}".format(e))
 
             # filter out the bad keys
             bad_keys = {bad.fpr for bad in e.recipients}
-            recipients = [r for r in recipients
-                          if not r.subkeys[0].fpr in bad_keys]
+            recipients = [
+                r for r in recipients if not r.subkeys[0].fpr in bad_keys
+            ]
 
     sys.stdout.buffer.write(ciphertext)
index 16c2256..5b90b4b 100755 (executable)
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 from gpg.constants.sig import mode
 
+del absolute_import, print_function, unicode_literals
+
 with gpg.Context() as c:
     signed, _ = c.sign(b"Test message", mode=mode.CLEAR)
     sys.stdout.buffer.write(signed)
index 5870ca9..2df7275 100755 (executable)
 # It uses keys for joe+gpg@example.org generated by genkey.py script
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 from gpg.constants.sig import mode
 
+del absolute_import, print_function, unicode_literals
+
 user = "joe+gpg"
 
 with gpg.Context(pinentry_mode=gpg.constants.PINENTRY_MODE_LOOPBACK) as c:
index 8f451d7..17c3eba 100755 (executable)
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 with gpg.Context(armor=True) as c:
     recipients = []
     print("Enter name of your recipient(s), end with a blank line.")
@@ -40,8 +41,8 @@ with gpg.Context(armor=True) as c:
     if not recipients:
         sys.exit("No recipients.")
 
-    print("Encrypting for {}.".format(", ".join(k.uids[0].name
-                                                for k in recipients)))
+    print("Encrypting for {}.".format(", ".join(
+        k.uids[0].name for k in recipients)))
 
     ciphertext, _, _ = c.encrypt(b"This is my message,", recipients)
     sys.stdout.buffer.write(ciphertext)
index d4c0884..f1cdb2c 100644 (file)
 #
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
-
 """A test applicaton for the CMS protocol."""
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
 if len(sys.argv) != 2:
     sys.exit("fingerprint or unique key ID for gpgme_get_key()")
 
index b3ca133..dc0e7d3 100755 (executable)
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
+
 def print_engine_infos():
     print("gpgme version:", gpg.core.check_version(None))
     print("engines:")
@@ -31,8 +33,9 @@ def print_engine_infos():
         print(engine.file_name, engine.version)
 
     for proto in [gpg.constants.protocol.OpenPGP, gpg.constants.protocol.CMS]:
-        print("Have {}? {}".format(gpg.core.get_protocol_name(proto),
-                                   gpg.core.engine_check_version(proto)))
+        print("Have {}? {}".format(
+            gpg.core.get_protocol_name(proto),
+            gpg.core.engine_check_version(proto)))
 
 
 def verifyprintdetails(filename, detached_sig_filename=None):
@@ -40,9 +43,9 @@ def verifyprintdetails(filename, detached_sig_filename=None):
     with gpg.Context() as c:
 
         # Verify.
-        data, result = c.verify(open(filename),
-                                open(detached_sig_filename)
-                                if detached_sig_filename else None)
+        data, result = c.verify(
+            open(filename),
+            open(detached_sig_filename) if detached_sig_filename else None)
 
         # List results for all signatures. Status equal 0 means "Ok".
         for index, sign in enumerate(result.signatures):
@@ -57,15 +60,15 @@ def verifyprintdetails(filename, detached_sig_filename=None):
     if data:
         sys.stdout.buffer.write(data)
 
+
 def main():
     print_engine_infos()
     print()
 
     argc = len(sys.argv)
     if argc < 2 or argc > 3:
-        sys.exit(
-            "Usage: {} <filename>[ <detached_signature_filename>]".format(
-                sys.argv[0]))
+        sys.exit("Usage: {} <filename>[ <detached_signature_filename>]".format(
+            sys.argv[0]))
 
     if argc == 2:
         print("trying to verify file {}.".format(sys.argv[1]))
@@ -74,5 +77,6 @@ def main():
         print("trying to verify signature {1} for file {0}.".format(*sys.argv))
         verifyprintdetails(sys.argv[1], sys.argv[2])
 
+
 if __name__ == "__main__":
     main()
index 492326b..f3d14a7 100644 (file)
 %include "cpointer.i"
 %include "cstring.i"
 
+%{
+/* We use public symbols (eg. "_obsolete_class") which are marked as
+ * deprecated but we need to keep them.  Silence the warning.  */
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+%}
+
 /* Generate doc strings for all methods.
 
    This will generate docstrings of the form
@@ -575,7 +581,8 @@ off_t gpgme_data_seek (gpgme_data_t dh, off_t offset, int whence=SEEK_SET);
 %}
 
 /* This is for notations, where we want to hide the length fields, and
-   the unused bit field block.  */
+ * the unused bit field block.  We silence the warning.  */
+%warnfilter(302) _gpgme_sig_notation;
 struct _gpgme_sig_notation
 {
   struct _gpgme_sig_notation *next;
index 2595073..65a4be0 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2016-2017 g10 Code GmbH
+# Copyright (C) 2016-2018 g10 Code GmbH
 # Copyright (C) 2004,2008 Igor Belyi <belyi@users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
 #
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from distutils.core import setup, Extension
-import os, os.path, sys
+from distutils.command.build import build
+
 import glob
+import os
+import os.path
 import re
 import shutil
 import subprocess
+import sys
 
 # Out-of-tree build of the gpg bindings.
 gpg_error_config = ["gpg-error-config"]
@@ -40,9 +44,11 @@ top_builddir = os.environ.get("top_builddir")
 if top_builddir:
     # In-tree build.
     in_tree = True
-    gpgme_config = [os.path.join(top_builddir, "src/gpgme-config")] + gpgme_config_flags
+    gpgme_config = [os.path.join(top_builddir, "src/gpgme-config")
+                    ] + gpgme_config_flags
     gpgme_h = os.path.join(top_builddir, "src/gpgme.h")
-    library_dirs = [os.path.join(top_builddir, "src/.libs")] # XXX uses libtool internals
+    library_dirs = [os.path.join(top_builddir,
+                                 "src/.libs")]  # XXX uses libtool internals
     extra_macros.update(
         HAVE_CONFIG_H=1,
         HAVE_DATA_H=1,
@@ -55,17 +61,18 @@ else:
     devnull = open(os.devnull, "w")
 
 try:
-    subprocess.check_call(gpgme_config + ['--version'],
-                          stdout=devnull)
+    subprocess.check_call(gpgme_config + ['--version'], stdout=devnull)
 except:
     sys.exit("Could not find gpgme-config.  " +
              "Please install the libgpgme development package.")
 
+
 def getconfig(what, config=gpgme_config):
-    confdata = subprocess.Popen(config + ["--%s" % what],
-                                stdout=subprocess.PIPE).communicate()[0]
+    confdata = subprocess.Popen(
+        config + ["--%s" % what], stdout=subprocess.PIPE).communicate()[0]
     return [x for x in confdata.decode('utf-8').split() if x != '']
 
+
 version = version_raw = getconfig("version")[0]
 if '-' in version:
     version = version.split('-')[0]
@@ -90,7 +97,7 @@ for item in getconfig('cflags'):
         include_dirs.append(item[2:])
     elif item.startswith("-D"):
         defitem = item[2:].split("=", 1)
-        if len(defitem)==2:
+        if len(defitem) == 2:
             define_macros.append((defitem[0], defitem[1]))
         else:
             define_macros.append((defitem[0], None))
@@ -98,49 +105,67 @@ for item in getconfig('cflags'):
 # Adjust include and library locations in case of win32
 uname_s = os.popen("uname -s").read()
 if uname_s.startswith("MINGW32"):
-   mnts = [x.split()[0:3:2] for x in os.popen("mount").read().split("\n") if x]
-   tmplist = sorted([(len(x[1]), x[1], x[0]) for x in mnts])
-   tmplist.reverse()
-   extra_dirs = []
-   for item in include_dirs:
-       for ln, mnt, tgt in tmplist:
-           if item.startswith(mnt):
-               item = os.path.normpath(item[ln:])
-               while item[0] == os.path.sep:
-                   item = item[1:]
-               extra_dirs.append(os.path.join(tgt, item))
-               break
-   include_dirs += extra_dirs
-   for item in [x[2:] for x in libs if x.startswith("-L")]:
-       for ln, mnt, tgt in tmplist:
-           if item.startswith(mnt):
-               item = os.path.normpath(item[ln:])
-               while item[0] == os.path.sep:
-                   item = item[1:]
-               library_dirs.append(os.path.join(tgt, item))
-               break
+    mnts = [
+        x.split()[0:3:2] for x in os.popen("mount").read().split("\n") if x
+    ]
+    tmplist = sorted([(len(x[1]), x[1], x[0]) for x in mnts])
+    tmplist.reverse()
+    extra_dirs = []
+    for item in include_dirs:
+        for ln, mnt, tgt in tmplist:
+            if item.startswith(mnt):
+                item = os.path.normpath(item[ln:])
+                while item[0] == os.path.sep:
+                    item = item[1:]
+                extra_dirs.append(os.path.join(tgt, item))
+                break
+    include_dirs += extra_dirs
+    for item in [x[2:] for x in libs if x.startswith("-L")]:
+        for ln, mnt, tgt in tmplist:
+            if item.startswith(mnt):
+                item = os.path.normpath(item[ln:])
+                while item[0] == os.path.sep:
+                    item = item[1:]
+                library_dirs.append(os.path.join(tgt, item))
+                break
+
 
 def in_srcdir(name):
     return os.path.join(os.environ.get("srcdir", ""), name)
+
+
 def up_to_date(source, target):
-    return (os.path.exists(target)
-            and os.path.getmtime(source) <= os.path.getmtime(target))
+    return (os.path.exists(target) and
+            os.path.getmtime(source) <= os.path.getmtime(target))
+
 
 # We build an Extension using SWIG, which generates a Python module.
 # By default, the 'build_py' step is run before 'build_ext', and
 # therefore the generated Python module is not copied into the build
 # directory.
-# Bug: http://bugs.python.org/issue1016626
+# Bugs: https://bugs.python.org/issue1016626
+#       https://bugs.python.org/issue2624
 # Workaround:
-# http://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module
-from distutils.command.build import build
-class BuildExtFirstHack(build):
+# https://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module
+#
+# To install to multiple Python installations or to alternate ones run the
+# following three commands (yes, run the build one twice):
+#
+# /path/to/pythonX.Y setup.py build
+# /path/to/pythonX.Y setup.py build
+# /path/to/pythonX.Y setup.py install
+#
+# It is highly likely that this will need to be run as root or with sudo (or
+# sudo -H).  It may or may not work with venv. and outside a virtualenv
 
+class BuildExtFirstHack(build):
     def _read_header(self, header, cflags):
         tmp_include = self._in_build_base("include1.h")
         with open(tmp_include, 'w') as f:
             f.write("#include <%s>" % header)
-        return subprocess.check_output(os.environ.get('CPP', 'cc -E').split() + cflags + [tmp_include]).decode('utf-8')
+        return subprocess.check_output(
+            os.environ.get('CPP', 'cc -E').split() + cflags +
+            [tmp_include]).decode('utf-8')
 
     def _write_if_unchanged(self, target, content):
         if os.path.exists(target):
@@ -158,13 +183,14 @@ class BuildExtFirstHack(build):
     def _generate_errors_i(self):
 
         try:
-            subprocess.check_call(gpg_error_config + ['--version'],
-                                  stdout=devnull)
+            subprocess.check_call(
+                gpg_error_config + ['--version'], stdout=devnull)
         except:
             sys.exit("Could not find gpg-error-config.  " +
                      "Please install the libgpg-error development package.")
 
-        gpg_error_content = self._read_header("gpg-error.h", getconfig("cflags", config=gpg_error_config))
+        gpg_error_content = self._read_header(
+            "gpg-error.h", getconfig("cflags", config=gpg_error_config))
 
         filter_re = re.compile(r'GPG_ERR_[^ ]* =')
         rewrite_re = re.compile(r' *(.*) = .*')
@@ -173,9 +199,11 @@ class BuildExtFirstHack(build):
         for line in gpg_error_content.splitlines():
             if not filter_re.search(line):
                 continue
-            errors_i_content += rewrite_re.sub(r'%constant long \1 = \1;'+'\n', line.strip())
+            errors_i_content += rewrite_re.sub(
+                r'%constant long \1 = \1;' + '\n', line.strip())
 
-        self._write_if_unchanged(self._in_build_base("errors.i"), errors_i_content)
+        self._write_if_unchanged(
+            self._in_build_base("errors.i"), errors_i_content)
 
     def _in_build_base(self, name):
         return os.path.join(self.build_base, name)
@@ -191,7 +219,8 @@ class BuildExtFirstHack(build):
         # Copy due to http://bugs.python.org/issue2624
         # Avoid creating in srcdir
         for source, target in ((in_srcdir(n), self._in_build_base(n))
-                               for n in ('gpgme.i', 'helpers.c', 'private.h', 'helpers.h')):
+                               for n in ('gpgme.i', 'helpers.c', 'private.h',
+                                         'helpers.h')):
             if not up_to_date(source, target):
                 shutil.copy2(source, target)
 
@@ -203,52 +232,60 @@ class BuildExtFirstHack(build):
     def run(self):
         self._generate()
 
-        swig_sources.extend((self._in_build_base('gpgme.i'), self._in_build_base('helpers.c')))
-        swig_opts.extend(['-I' + self.build_base,
-                          '-outdir', os.path.join(self.build_lib, 'gpg')])
+        swig_sources.extend((self._in_build_base('gpgme.i'),
+                             self._in_build_base('helpers.c')))
+        swig_opts.extend([
+            '-I' + self.build_base, '-outdir',
+            os.path.join(self.build_lib, 'gpg')
+        ])
         include_dirs.insert(0, self.build_base)
 
         self.run_command('build_ext')
         build.run(self)
 
+
 py3 = [] if sys.version_info.major < 3 else ['-py3']
 swig_sources = []
 swig_opts = ['-threads'] + py3 + extra_swig_opts
-swige = Extension("gpg._gpgme",
-                  sources = swig_sources,
-                  swig_opts = swig_opts,
-                  include_dirs = include_dirs,
-                  define_macros = define_macros,
-                  library_dirs = library_dirs,
-                  extra_link_args = libs)
-
-setup(name="gpg",
-      cmdclass={'build': BuildExtFirstHack},
-      version="@VERSION@",
-      description='Python bindings for GPGME GnuPG cryptography library',
-      # XXX add a long description
-      #long_description=long_description,
-      author='The GnuPG hackers',
-      author_email='gnupg-devel@gnupg.org',
-      url='https://www.gnupg.org',
-      ext_modules=[swige],
-      packages = ['gpg', 'gpg.constants', 'gpg.constants.data',
-                  'gpg.constants.keylist', 'gpg.constants.sig',
-                  'gpg.constants.tofu'],
-      license="LGPL2.1+ (the library), GPL2+ (tests and examples)",
-      classifiers=[
-          'Development Status :: 4 - Beta',
-          'Intended Audience :: Developers',
-          'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
-          'Programming Language :: Python :: 2',
-          'Programming Language :: Python :: 2.7',
-          'Programming Language :: Python :: 3',
-          'Programming Language :: Python :: 3.4',
-          'Programming Language :: Python :: 3.5',
-          'Programming Language :: Python :: 3.6',
-          'Operating System :: POSIX',
-          'Operating System :: Microsoft :: Windows',
-          'Topic :: Communications :: Email',
-          'Topic :: Security :: Cryptography',
-      ],
+swige = Extension(
+    "gpg._gpgme",
+    sources=swig_sources,
+    swig_opts=swig_opts,
+    include_dirs=include_dirs,
+    define_macros=define_macros,
+    library_dirs=library_dirs,
+    extra_link_args=libs)
+
+setup(
+    name="gpg",
+    cmdclass={'build': BuildExtFirstHack},
+    version="@VERSION@",
+    description='Python bindings for GPGME GnuPG cryptography library',
+    # TODO: add a long description
+    # long_description=long_description,
+    author='The GnuPG hackers',
+    author_email='gnupg-devel@gnupg.org',
+    url='https://www.gnupg.org',
+    ext_modules=[swige],
+    packages=[
+        'gpg', 'gpg.constants', 'gpg.constants.data', 'gpg.constants.keylist',
+        'gpg.constants.sig', 'gpg.constants.tofu'
+    ],
+    license="LGPL2.1+ (the library), GPL2+ (tests and examples)",
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Operating System :: POSIX',
+        'Operating System :: Microsoft :: Windows',
+        'Topic :: Communications :: Email',
+        'Topic :: Security :: Cryptography',
+    ],
 )
index 385b17e..30e638c 100644 (file)
@@ -15,7 +15,6 @@
 # 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
-
 """gpg: GnuPG Interface for Python (GPGME bindings)
 
 Welcome to gpg, the GnuPG Interface for Python.
@@ -96,7 +95,6 @@ GPGME documentation: https://www.gnupg.org/documentation/manuals/gpgme/
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from . import core
 from . import errors
@@ -107,6 +105,8 @@ from . import version
 from .core import Context
 from .core import Data
 
+del absolute_import, print_function, unicode_literals
+
 # Interface hygiene.
 
 # Drop the low-level gpgme that creeps in for some reason.
@@ -117,5 +117,7 @@ del gpgme
 _ = [Context, Data, core, errors, constants, util, callbacks, version]
 del _
 
-__all__ = ["Context", "Data",
-           "core", "errors", "constants", "util", "callbacks", "version"]
+__all__ = [
+    "Context", "Data", "core", "errors", "constants", "util", "callbacks",
+    "version"
+]
index b25a9a7..9aacf56 100644 (file)
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from getpass import getpass
 
+del absolute_import, print_function, unicode_literals
+
+
 def passphrase_stdin(hint, desc, prev_bad, hook=None):
     """This is a sample callback that will read a passphrase from
     the terminal.  The hook here, if present, will be used to describe
     why the passphrase is needed."""
     why = ''
-    if hook != None:
+    if hook is not None:
         why = ' ' + hook
     if prev_bad:
         why += ' (again)'
     print("Please supply %s' password%s:" % (hint, why))
     return getpass()
 
+
 def progress_stdout(what, type, current, total, hook=None):
-    print("PROGRESS UPDATE: what = %s, type = %d, current = %d, total = %d" %\
+    print("PROGRESS UPDATE: what = %s, type = %d, current = %d, total = %d" %
           (what, type, current, total))
 
+
 def readcb_fh(count, hook):
     """A callback for data.  hook should be a Python file-like object."""
     if count:
index 484ffd2..7a953aa 100644 (file)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
+# Globals may need to be set prior to module import, if so this prevents PEP8
+# compliance, but better that than code breakage.
 util.process_constants('GPGME_', globals())
-del util
 
 # For convenience, we import the modules here.
-from . import data, keylist, sig, tofu # The subdirs.
-from . import create, event, keysign, md, pk, protocol, sigsum, status, validity
+from . import data, keylist, sig, tofu  # The subdirs.
+# The remaining modules can no longer fit on one line.
+from . import create, event, keysign, md, pk, protocol, sigsum, status
+from . import validity
+
+del absolute_import, print_function, unicode_literals, util
 
 # A complication arises because 'import' is a reserved keyword.
 # Import it as 'Import' instead.
-globals()['Import'] = getattr(__import__('', globals(), locals(),
-                                         [str('import')], 1), "import")
+globals()['Import'] = getattr(
+    __import__('', globals(), locals(), [str('import')], 1), "import")
 
-__all__ = ['data', 'event', 'import', 'keysign', 'keylist', 'md', 'pk',
-           'protocol', 'sig', 'sigsum', 'status', 'tofu', 'validity', 'create']
+__all__ = [
+    'data', 'event', 'import', 'keysign', 'keylist', 'md', 'pk', 'protocol',
+    'sig', 'sigsum', 'status', 'tofu', 'validity', 'create'
+]
 
 # GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact.  We
 # implement gpg.Context.op_edit using gpgme_op_interact, so the
index 132e96d..382dad9 100644 (file)
@@ -18,8 +18,7 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_CREATE_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 8274ab9..c085667 100644 (file)
@@ -1,6 +1,6 @@
-
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from . import encoding
 __all__ = ['encoding']
+
+del absolute_import, print_function, unicode_literals
index e76a22e..9afa732 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_DATA_ENCODING_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 1b14d1d..9f9273d 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_EVENT_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 47c296c..e477eb2 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_IMPORT_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 2ce0edf..fa8f7f0 100644 (file)
@@ -1,6 +1,6 @@
-
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from . import mode
 __all__ = ['mode']
+
+del absolute_import, print_function, unicode_literals
index 39e1819..bda7710 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_KEYLIST_MODE_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index fccdbc4..328dfb9 100644 (file)
@@ -18,8 +18,7 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_KEYSIGN_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index f3e8bbd..068b31d 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_MD_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 6bf2a21..3a826d1 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_PK_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index d086bbd..cc9ca07 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_PROTOCOL_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 39d4e6e..f45af00 100644 (file)
@@ -1,6 +1,6 @@
-
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from . import mode, notation
 __all__ = ['mode', 'notation']
+
+del absolute_import, print_function, unicode_literals
index 0f4f0ef..3a2d17a 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_SIG_MODE_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 9a79e01..9e56be3 100644 (file)
@@ -18,8 +18,7 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_SIG_NOTATION_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 09ef9d7..0fe0e77 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_SIGSUM_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index 819a58b..5e58a6a 100644 (file)
@@ -18,7 +18,8 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from . import policy
 __all__ = ['policy']
+
+del absolute_import, print_function, unicode_literals
index 5a61f06..53d853d 100644 (file)
@@ -18,8 +18,7 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_TOFU_POLICY_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index d3c5345..4ecf4ec 100644 (file)
@@ -16,8 +16,7 @@
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from gpg import util
 util.process_constants('GPGME_VALIDITY_', globals())
-del util
+del absolute_import, print_function, unicode_literals, util
index bd95d23..6e92592 100644 (file)
@@ -1,5 +1,22 @@
-# Copyright (C) 2016-2017 g10 Code GmbH
-# Copyright (C) 2004,2008 Igor Belyi <belyi@users.sourceforge.net>
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import re
+import os
+import warnings
+import weakref
+
+from . import gpgme
+from .errors import errorcheck, GPGMEError
+from . import constants
+from . import errors
+from . import util
+
+del absolute_import, print_function, unicode_literals
+
+# Copyright (C) 2016-2018 g10 Code GmbH
+# Copyright (C) 2004, 2008 Igor Belyi <belyi@users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
 #
 #    This library is free software; you can redistribute it and/or
@@ -15,7 +32,6 @@
 #    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
-
 """Core functionality
 
 Core functionality of GPGME wrapped in a object-oriented fashion.
@@ -24,18 +40,6 @@ and the 'Data' class describing buffers of data.
 
 """
 
-from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
-
-import re
-import os
-import warnings
-import weakref
-from . import gpgme
-from .errors import errorcheck, GPGMEError
-from . import constants
-from . import errors
-from . import util
 
 class GpgmeWrapper(object):
     """Base wrapper class
@@ -49,8 +53,8 @@ class GpgmeWrapper(object):
         self.wrapped = wrapped
 
     def __repr__(self):
-        return '<{}/{!r}>'.format(super(GpgmeWrapper, self).__repr__(),
-                                  self.wrapped)
+        return '<{}/{!r}>'.format(
+            super(GpgmeWrapper, self).__repr__(), self.wrapped)
 
     def __str__(self):
         acc = ['{}.{}'.format(__name__, self.__class__.__name__)]
@@ -64,7 +68,7 @@ class GpgmeWrapper(object):
         return hash(repr(self.wrapped))
 
     def __eq__(self, other):
-        if other == None:
+        if other is None:
             return False
         else:
             return repr(self.wrapped) == repr(other.wrapped)
@@ -98,12 +102,12 @@ class GpgmeWrapper(object):
     _boolean_properties = set()
 
     def __wrap_boolean_property(self, key, do_set=False, value=None):
-        get_func = getattr(gpgme,
-                           "{}get_{}".format(self._cprefix, key))
-        set_func = getattr(gpgme,
-                           "{}set_{}".format(self._cprefix, key))
+        get_func = getattr(gpgme, "{}get_{}".format(self._cprefix, key))
+        set_func = getattr(gpgme, "{}set_{}".format(self._cprefix, key))
+
         def get(slf):
             return bool(get_func(slf.wrapped))
+
         def set_(slf, value):
             set_func(slf.wrapped, bool(value))
 
@@ -116,9 +120,10 @@ class GpgmeWrapper(object):
             return get(self)
 
     _munge_docstring = re.compile(r'gpgme_([^(]*)\(([^,]*), (.*\) -> .*)')
+
     def __getattr__(self, key):
         """On-the-fly generation of wrapper methods and properties"""
-        if key[0] == '_' or self._cprefix == None:
+        if key[0] == '_' or self._cprefix is None:
             return None
 
         if key in self._boolean_properties:
@@ -128,12 +133,14 @@ class GpgmeWrapper(object):
         func = getattr(gpgme, name)
 
         if self._errorcheck(name):
+
             def _funcwrap(slf, *args):
                 result = func(slf.wrapped, *args)
                 if slf._callback_excinfo:
                     gpgme.gpg_raise_callback_exception(slf)
                 return errorcheck(result, name)
         else:
+
             def _funcwrap(slf, *args):
                 result = func(slf.wrapped, *args)
                 if slf._callback_excinfo:
@@ -149,6 +156,7 @@ class GpgmeWrapper(object):
         # Bind the method to 'self'.
         def wrapper(*args):
             return _funcwrap(self, *args)
+
         wrapper.__doc__ = doc
 
         return wrapper
@@ -160,6 +168,7 @@ class GpgmeWrapper(object):
         else:
             super(GpgmeWrapper, self).__setattr__(key, value)
 
+
 class Context(GpgmeWrapper):
     """Context for cryptographic operations
 
@@ -173,10 +182,15 @@ class Context(GpgmeWrapper):
 
     """
 
-    def __init__(self, armor=False, textmode=False, offline=False,
-                 signers=[], pinentry_mode=constants.PINENTRY_MODE_DEFAULT,
+    def __init__(self,
+                 armor=False,
+                 textmode=False,
+                 offline=False,
+                 signers=[],
+                 pinentry_mode=constants.PINENTRY_MODE_DEFAULT,
                  protocol=constants.PROTOCOL_OpenPGP,
-                 wrapped=None, home_dir=None):
+                 wrapped=None,
+                 home_dir=None):
         """Construct a context object
 
         Keyword arguments:
@@ -212,22 +226,29 @@ class Context(GpgmeWrapper):
         Helper function to retrieve the results of an operation, or
         None if SINK is given.
         """
-        if sink or data == None:
+        if sink or data is None:
             return None
         data.seek(0, os.SEEK_SET)
         return data.read()
 
     def __repr__(self):
-        return (
-            "Context(armor={0.armor}, "
-            "textmode={0.textmode}, offline={0.offline}, "
-            "signers={0.signers}, pinentry_mode={0.pinentry_mode}, "
-            "protocol={0.protocol}, home_dir={0.home_dir}"
-            ")").format(self)
-
-    def encrypt(self, plaintext, recipients=[], sign=True, sink=None,
-                passphrase=None, always_trust=False, add_encrypt_to=False,
-                prepare=False, expect_sign=False, compress=True):
+        return ("Context(armor={0.armor}, "
+                "textmode={0.textmode}, offline={0.offline}, "
+                "signers={0.signers}, pinentry_mode={0.pinentry_mode}, "
+                "protocol={0.protocol}, home_dir={0.home_dir}"
+                ")").format(self)
+
+    def encrypt(self,
+                plaintext,
+                recipients=[],
+                sign=True,
+                sink=None,
+                passphrase=None,
+                always_trust=False,
+                add_encrypt_to=False,
+                prepare=False,
+                expect_sign=False,
+                compress=True):
         """Encrypt data
 
         Encrypt the given plaintext for the given recipients.  If the
@@ -267,12 +288,14 @@ class Context(GpgmeWrapper):
         flags |= expect_sign * constants.ENCRYPT_EXPECT_SIGN
         flags |= (not compress) * constants.ENCRYPT_NO_COMPRESS
 
-        if passphrase != None:
+        if passphrase is not None:
             old_pinentry_mode = self.pinentry_mode
             old_passphrase_cb = getattr(self, '_passphrase_cb', None)
             self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+
             def passphrase_cb(hint, desc, prev_bad, hook=None):
                 return passphrase
+
             self.set_passphrase_cb(passphrase_cb)
 
         try:
@@ -283,25 +306,26 @@ class Context(GpgmeWrapper):
         except errors.GPGMEError as e:
             result = self.op_encrypt_result()
             sig_result = self.op_sign_result() if sign else None
-            results = (self.__read__(sink, ciphertext),
-                       result, sig_result)
+            results = (self.__read__(sink, ciphertext), result, sig_result)
             if e.getcode() == errors.UNUSABLE_PUBKEY:
                 if result.invalid_recipients:
-                    raise errors.InvalidRecipients(result.invalid_recipients,
-                                                   error=e.error,
-                                                   results=results)
+                    raise errors.InvalidRecipients(
+                        result.invalid_recipients,
+                        error=e.error,
+                        results=results)
             if e.getcode() == errors.UNUSABLE_SECKEY:
                 sig_result = self.op_sign_result()
                 if sig_result.invalid_signers:
-                    raise errors.InvalidSigners(sig_result.invalid_signers,
-                                                error=e.error,
-                                                results=results)
+                    raise errors.InvalidSigners(
+                        sig_result.invalid_signers,
+                        error=e.error,
+                        results=results)
             # Otherwise, just raise the error, but attach the results
             # first.
             e.results = results
             raise e
         finally:
-            if passphrase != None:
+            if passphrase is not None:
                 self.pinentry_mode = old_pinentry_mode
                 if old_passphrase_cb:
                     self.set_passphrase_cb(*old_passphrase_cb[1:])
@@ -342,37 +366,62 @@ class Context(GpgmeWrapper):
         GPGMEError     -- as signaled by the underlying library
 
         """
+        sink_result = None
+        verify_sigs = None
         plaintext = sink if sink else Data()
 
-        if passphrase != None:
+        if passphrase is not None:
             old_pinentry_mode = self.pinentry_mode
             old_passphrase_cb = getattr(self, '_passphrase_cb', None)
             self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+
             def passphrase_cb(hint, desc, prev_bad, hook=None):
                 return passphrase
+
             self.set_passphrase_cb(passphrase_cb)
 
         try:
-            if verify:
+            if verify is not None:
+                if isinstance(verify, bool) is True:
+                    if verify is False:
+                        verify = True
+                        sink_result = True
+                    else:
+                        pass
+                elif isinstance(verify, list) is True:
+                    if len(verify) > 0:
+                        verify_sigs = True
+                    else:
+                        pass
+                else:
+                    verify = True
                 self.op_decrypt_verify(ciphertext, plaintext)
             else:
                 self.op_decrypt(ciphertext, plaintext)
         except errors.GPGMEError as e:
             result = self.op_decrypt_result()
-            verify_result = self.op_verify_result() if verify else None
+            if verify is not None and sink_result is None:
+                verify_result = self.op_verify_result()
+            else:
+                verify_result = None
             # Just raise the error, but attach the results first.
-            e.results = (self.__read__(sink, plaintext),
-                         result, verify_result)
+            e.results = (self.__read__(sink, plaintext), result, verify_result)
             raise e
         finally:
-            if passphrase != None:
+            if passphrase is not None:
                 self.pinentry_mode = old_pinentry_mode
                 if old_passphrase_cb:
                     self.set_passphrase_cb(*old_passphrase_cb[1:])
 
         result = self.op_decrypt_result()
-        verify_result = self.op_verify_result() if verify else None
+
+        if verify is not None and sink_result is None:
+            verify_result = self.op_verify_result()
+        else:
+            verify_result = None
+
         results = (self.__read__(sink, plaintext), result, verify_result)
+
         if result.unsupported_algorithm:
             raise errors.UnsupportedAlgorithm(result.unsupported_algorithm,
                                               results=results)
@@ -382,8 +431,8 @@ class Context(GpgmeWrapper):
                    for s in verify_result.signatures):
                 raise errors.BadSignatures(verify_result, results=results)
 
-        if verify and verify != True:
-            missing = list()
+        if verify_sigs is not None:
+            missing = []
             for key in verify:
                 ok = False
                 for subkey in key.subkeys:
@@ -398,8 +447,29 @@ class Context(GpgmeWrapper):
                 if not ok:
                     missing.append(key)
             if missing:
-                raise errors.MissingSignatures(verify_result, missing,
-                                               results=results)
+                try:
+                    raise errors.MissingSignatures(verify_result, missing,
+                                                   results=results)
+                except errors.MissingSignatures as e:
+                    raise e
+                    # mse = e
+                    # mserr = "gpg.errors.MissingSignatures:"
+                    # print(mserr, miss_e, "\n")
+                    # # The full details can then be found in mse.results,
+                    # # mse.result, mse.missing if necessary.
+                    # mse_list = []
+                    # msp = "Missing signatures from: \n".format()
+                    # print(msp)
+                    # for key in mse.missing:
+                    #     mse_list.append(key.fpr)
+                    #     msl = []
+                    #     msl.append(key.fpr)
+                    #     for user in key.uids:
+                    #         msl.append(user.name)
+                    #         msl.append(user.email)
+                    #         # msl.append(user.uid)
+                    #     print(" ".join(msl))
+                    # raise mse
 
         return results
 
@@ -431,13 +501,13 @@ class Context(GpgmeWrapper):
         try:
             self.op_sign(data, signeddata, mode)
         except errors.GPGMEError as e:
-            results = (self.__read__(sink, signeddata),
-                       self.op_sign_result())
+            results = (self.__read__(sink, signeddata), self.op_sign_result())
             if e.getcode() == errors.UNUSABLE_SECKEY:
                 if results[1].invalid_signers:
-                    raise errors.InvalidSigners(results[1].invalid_signers,
-                                                error=e.error,
-                                                results=results)
+                    raise errors.InvalidSigners(
+                        results[1].invalid_signers,
+                        error=e.error,
+                        results=results)
             e.results = results
             raise e
 
@@ -481,8 +551,7 @@ class Context(GpgmeWrapper):
                 self.op_verify(signed_data, None, data)
         except errors.GPGMEError as e:
             # Just raise the error, but attach the results first.
-            e.results = (self.__read__(sink, data),
-                         self.op_verify_result())
+            e.results = (self.__read__(sink, data), self.op_verify_result())
             raise e
 
         results = (self.__read__(sink, data), self.op_verify_result())
@@ -504,12 +573,171 @@ class Context(GpgmeWrapper):
             if not ok:
                 missing.append(key)
         if missing:
-            raise errors.MissingSignatures(results[1], missing,
-                                           results=results)
+            raise errors.MissingSignatures(
+                results[1], missing, results=results)
 
         return results
 
-    def keylist(self, pattern=None, secret=False,
+    def key_import(self, data):
+        """Import data
+
+        Imports the given data into the Context.
+
+        Returns:
+                -- an object describing the results of imported or updated
+                   keys
+
+        Raises:
+        TypeError      -- Very rarely.
+        GPGMEError     -- as signaled by the underlying library:
+
+                          Import status errors, when they occur, will usually
+                          be of NODATA.  NO_PUBKEY indicates something
+                          managed to run the function without any
+                          arguments, while an argument of None triggers
+                          the first NODATA of errors.GPGME in the
+                          exception.
+        """
+        try:
+            self.op_import(data)
+            result = self.op_import_result()
+            if result.considered == 0:
+                status = constants.STATUS_IMPORT_PROBLEM
+            else:
+                status = constants.STATUS_KEY_CONSIDERED
+        except Exception as e:
+            if e == errors.GPGMEError:
+                if e.code_str == "No data":
+                    status = constants.STATUS_NODATA
+                else:
+                    status = constants.STATUS_FILE_ERROR
+            elif e == TypeError and hasattr(data, "decode") is True:
+                status = constants.STATUS_NO_PUBKEY
+            elif e == TypeError and hasattr(data, "encode") is True:
+                status = constants.STATUS_FILE_ERROR
+            else:
+                status = constants.STATUS_ERROR
+
+        if status == constants.STATUS_KEY_CONSIDERED:
+            import_result = result
+        else:
+            import_result = status
+
+        return import_result
+
+    def key_export(self, pattern=None):
+        """Export keys.
+
+        Exports public keys matching the pattern specified.  If no
+        pattern is specified then exports all available keys.
+
+        Keyword arguments:
+        pattern        -- return keys matching pattern (default: all keys)
+
+        Returns:
+                -- A key block containing one or more OpenPGP keys in
+                   either ASCII armoured or binary format as determined
+                   by the Context().  If there are no matching keys it
+                   returns None.
+
+        Raises:
+        GPGMEError     -- as signaled by the underlying library.
+        """
+        data = Data()
+        mode = 0
+        try:
+            self.op_export(pattern, mode, data)
+            data.seek(0, os.SEEK_SET)
+            pk_result = data.read()
+        except GPGMEError as e:
+            pk_result = e
+
+        if len(pk_result) > 0:
+            result = pk_result
+        else:
+            result = None
+
+        return result
+
+    def key_export_minimal(self, pattern=None):
+        """Export keys.
+
+        Exports public keys matching the pattern specified in a
+        minimised format.  If no pattern is specified then exports all
+        available keys.
+
+        Keyword arguments:
+        pattern        -- return keys matching pattern (default: all keys)
+
+        Returns:
+                -- A key block containing one or more minimised OpenPGP
+                   keys in either ASCII armoured or binary format as
+                   determined by the Context().  If there are no matching
+                   keys it returns None.
+
+        Raises:
+        GPGMEError     -- as signaled by the underlying library.
+        """
+        data = Data()
+        mode = gpgme.GPGME_EXPORT_MODE_MINIMAL
+        try:
+            self.op_export(pattern, mode, data)
+            data.seek(0, os.SEEK_SET)
+            pk_result = data.read()
+        except GPGMEError as e:
+            pk_result = e
+
+        if len(pk_result) > 0:
+            result = pk_result
+        else:
+            result = None
+
+        return result
+
+    def key_export_secret(self, pattern=None):
+        """Export secret keys.
+
+        Exports secret keys matching the pattern specified.  If no
+        pattern is specified then exports or attempts to export all
+        available secret keys.
+
+        IMPORTANT: Each secret key to be exported will prompt for its
+        passphrase via an invocation of pinentry and gpg-agent.  If the
+        passphrase is not entered or does not match then no data will be
+        exported.  This is the same result as when specifying a pattern
+        that is not matched by the available keys.
+
+        Keyword arguments:
+        pattern        -- return keys matching pattern (default: all keys)
+
+        Returns:
+                -- On success a key block containing one or more OpenPGP
+                   secret keys in either ASCII armoured or binary format
+                   as determined by the Context().
+                -- On failure while not raising an exception, returns None.
+
+        Raises:
+        GPGMEError     -- as signaled by the underlying library.
+        """
+        data = Data()
+        mode = gpgme.GPGME_EXPORT_MODE_SECRET
+        try:
+            self.op_export(pattern, mode, data)
+            data.seek(0, os.SEEK_SET)
+            sk_result = data.read()
+        except GPGMEError as e:
+            sk_result = e
+
+        if len(sk_result) > 0:
+            result = sk_result
+        else:
+            result = None
+
+        return result
+
+    def keylist(self,
+                pattern=None,
+                secret=False,
                 mode=constants.keylist.mode.LOCAL,
                 source=None):
         """List keys
@@ -544,9 +772,17 @@ class Context(GpgmeWrapper):
             key = self.op_keylist_next()
         self.op_keylist_end()
 
-    def create_key(self, userid, algorithm=None, expires_in=0, expires=True,
-                   sign=False, encrypt=False, certify=False, authenticate=False,
-                   passphrase=None, force=False):
+    def create_key(self,
+                   userid,
+                   algorithm=None,
+                   expires_in=0,
+                   expires=True,
+                   sign=False,
+                   encrypt=False,
+                   certify=False,
+                   authenticate=False,
+                   passphrase=None,
+                   force=False):
         """Create a primary key
 
         Create a primary key for the user id USERID.
@@ -583,9 +819,10 @@ class Context(GpgmeWrapper):
         encrypt      -- request the encryption capability (see above)
         certify      -- request the certification capability (see above)
         authenticate -- request the authentication capability (see above)
-        passphrase   -- protect the key with a passphrase (default: no passphrase)
-        force        -- force key creation even if a key with the same userid exists
-                                                          (default: False)
+        passphrase   -- protect the key with a passphrase (default: no
+                        passphrase)
+        force        -- force key creation even if a key with the same userid
+                        exists (default: False)
 
         Returns:
                      -- an object describing the result of the key creation
@@ -598,22 +835,26 @@ class Context(GpgmeWrapper):
             old_pinentry_mode = self.pinentry_mode
             old_passphrase_cb = getattr(self, '_passphrase_cb', None)
             self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+
             def passphrase_cb(hint, desc, prev_bad, hook=None):
                 return passphrase
+
             self.set_passphrase_cb(passphrase_cb)
 
         try:
-            self.op_createkey(userid, algorithm,
-                              0, # reserved
-                              expires_in,
-                              None, # extrakey
-                              ((constants.create.SIGN if sign else 0)
-                               | (constants.create.ENCR if encrypt else 0)
-                               | (constants.create.CERT if certify else 0)
-                               | (constants.create.AUTH if authenticate else 0)
-                               | (constants.create.NOPASSWD if passphrase == None else 0)
-                               | (0 if expires else constants.create.NOEXPIRE)
-                               | (constants.create.FORCE if force else 0)))
+            self.op_createkey(
+                userid,
+                algorithm,
+                0,  # reserved
+                expires_in,
+                None,  # extrakey
+                ((constants.create.SIGN if sign else 0) |
+                 (constants.create.ENCR if encrypt else 0) |
+                 (constants.create.CERT if certify else 0) |
+                 (constants.create.AUTH if authenticate else 0) |
+                 (constants.create.NOPASSWD if passphrase is None else 0) |
+                 (0 if expires else constants.create.NOEXPIRE) |
+                 (constants.create.FORCE if force else 0)))
         finally:
             if util.is_a_string(passphrase):
                 self.pinentry_mode = old_pinentry_mode
@@ -622,8 +863,15 @@ class Context(GpgmeWrapper):
 
         return self.op_genkey_result()
 
-    def create_subkey(self, key, algorithm=None, expires_in=0, expires=True,
-                      sign=False, encrypt=False, authenticate=False, passphrase=None):
+    def create_subkey(self,
+                      key,
+                      algorithm=None,
+                      expires_in=0,
+                      expires=True,
+                      sign=False,
+                      encrypt=False,
+                      authenticate=False,
+                      passphrase=None):
         """Create a subkey
 
         Create a subkey for the given KEY.  As subkeys are a concept
@@ -659,7 +907,8 @@ class Context(GpgmeWrapper):
         sign         -- request the signing capability (see above)
         encrypt      -- request the encryption capability (see above)
         authenticate -- request the authentication capability (see above)
-        passphrase   -- protect the subkey with a passphrase (default: no passphrase)
+        passphrase   -- protect the subkey with a passphrase (default: no
+                        passphrase)
 
         Returns:
                      -- an object describing the result of the subkey creation
@@ -672,20 +921,23 @@ class Context(GpgmeWrapper):
             old_pinentry_mode = self.pinentry_mode
             old_passphrase_cb = getattr(self, '_passphrase_cb', None)
             self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+
             def passphrase_cb(hint, desc, prev_bad, hook=None):
                 return passphrase
+
             self.set_passphrase_cb(passphrase_cb)
 
         try:
-            self.op_createsubkey(key, algorithm,
-                                 0, # reserved
-                                 expires_in,
-                                 ((constants.create.SIGN if sign else 0)
-                                  | (constants.create.ENCR if encrypt else 0)
-                                  | (constants.create.AUTH if authenticate else 0)
-                                  | (constants.create.NOPASSWD
-                                     if passphrase == None else 0)
-                                  | (0 if expires else constants.create.NOEXPIRE)))
+            self.op_createsubkey(
+                key,
+                algorithm,
+                0,  # reserved
+                expires_in,
+                ((constants.create.SIGN if sign else 0) |
+                 (constants.create.ENCR if encrypt else 0) |
+                 (constants.create.AUTH if authenticate else 0) |
+                 (constants.create.NOPASSWD if passphrase is None else 0) |
+                 (0 if expires else constants.create.NOEXPIRE)))
         finally:
             if util.is_a_string(passphrase):
                 self.pinentry_mode = old_pinentry_mode
@@ -745,8 +997,8 @@ class Context(GpgmeWrapper):
 
         """
         flags = 0
-        if uids == None or util.is_a_string(uids):
-            pass#through unchanged
+        if uids is None or util.is_a_string(uids):
+            pass  # through unchanged
         else:
             flags |= constants.keysign.LFSEP
             uids = "\n".join(uids)
@@ -771,8 +1023,11 @@ class Context(GpgmeWrapper):
         """
         self.op_tofu_policy(key, policy)
 
-    def assuan_transact(self, command,
-                        data_cb=None, inquire_cb=None, status_cb=None):
+    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
@@ -803,12 +1058,10 @@ class Context(GpgmeWrapper):
         errptr = gpgme.new_gpgme_error_t_p()
 
         err = gpgme.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)
+            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:
             gpgme.gpg_raise_callback_exception(self)
@@ -836,10 +1089,10 @@ class Context(GpgmeWrapper):
         GPGMEError     -- as signaled by the underlying library
 
         """
-        if key == None:
+        if key is None:
             raise ValueError("First argument cannot be None")
 
-        if sink == None:
+        if sink is None:
             sink = Data()
 
         if fnc_value:
@@ -847,8 +1100,8 @@ class Context(GpgmeWrapper):
         else:
             opaquedata = (weakref.ref(self), func)
 
-        result = gpgme.gpgme_op_interact(self.wrapped, key, flags,
-                                         opaquedata, sink)
+        result = gpgme.gpgme_op_interact(self.wrapped, key, flags, opaquedata,
+                                         sink)
         if self._callback_excinfo:
             gpgme.gpg_raise_callback_exception(self)
         errorcheck(result)
@@ -857,6 +1110,7 @@ class Context(GpgmeWrapper):
     def signers(self):
         """Keys used for signing"""
         return [self.signers_enum(i) for i in range(self.signers_count())]
+
     @signers.setter
     def signers(self, signers):
         old = self.signers
@@ -872,6 +1126,7 @@ class Context(GpgmeWrapper):
     def pinentry_mode(self):
         """Pinentry mode"""
         return self.get_pinentry_mode()
+
     @pinentry_mode.setter
     def pinentry_mode(self, value):
         self.set_pinentry_mode(value)
@@ -880,6 +1135,7 @@ class Context(GpgmeWrapper):
     def protocol(self):
         """Protocol to use"""
         return self.get_protocol()
+
     @protocol.setter
     def protocol(self, value):
         errorcheck(gpgme.gpgme_engine_check_version(value))
@@ -889,6 +1145,7 @@ class Context(GpgmeWrapper):
     def home_dir(self):
         """Engine's home directory"""
         return self.engine_info.home_dir
+
     @home_dir.setter
     def home_dir(self, value):
         self.set_engine_info(self.protocol, home_dir=value)
@@ -901,24 +1158,16 @@ class Context(GpgmeWrapper):
         # The list of functions is created using:
         #
         # $ grep '^gpgme_error_t ' obj/lang/python/python3.5-gpg/gpgme.h \
-        #   | grep -v _op_ | awk "/\(gpgme_ctx/ { printf (\"'%s',\\n\", \$2) } "
-        return ((name.startswith('gpgme_op_')
-                 and not name.endswith('_result'))
-                or name in {
-                    'gpgme_new',
-                    'gpgme_set_ctx_flag',
-                    'gpgme_set_protocol',
-                    'gpgme_set_sub_protocol',
-                    'gpgme_set_keylist_mode',
-                    'gpgme_set_pinentry_mode',
-                    'gpgme_set_locale',
-                    'gpgme_ctx_set_engine_info',
-                    'gpgme_signers_add',
-                    'gpgme_sig_notation_add',
-                    'gpgme_set_sender',
-                    'gpgme_cancel',
-                    'gpgme_cancel_async',
-                    'gpgme_get_key',
+        # | grep -v _op_ | awk "/\(gpgme_ctx/ { printf (\"'%s',\\n\", \$2) } "
+        return ((name.startswith('gpgme_op_') and not
+                 name.endswith('_result')) or name in {
+                     'gpgme_new', 'gpgme_set_ctx_flag', 'gpgme_set_protocol',
+                     'gpgme_set_sub_protocol', 'gpgme_set_keylist_mode',
+                     'gpgme_set_pinentry_mode', 'gpgme_set_locale',
+                     'gpgme_ctx_set_engine_info', 'gpgme_signers_add',
+                     'gpgme_sig_notation_add', 'gpgme_set_sender',
+                     'gpgme_cancel', 'gpgme_cancel_async', 'gpgme_get_key',
+                     'gpgme_get_sig_key',
                 })
 
     _boolean_properties = {'armor', 'textmode', 'offline'}
@@ -938,6 +1187,7 @@ class Context(GpgmeWrapper):
     # Implement the context manager protocol.
     def __enter__(self):
         return self
+
     def __exit__(self, type, value, tb):
         self.__del__()
 
@@ -1032,10 +1282,10 @@ class Context(GpgmeWrapper):
 
         Please see the GPGME manual for more information.
         """
-        if func == None:
+        if func is None:
             hookdata = None
         else:
-            if hook == None:
+            if hook is None:
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
@@ -1057,10 +1307,10 @@ class Context(GpgmeWrapper):
         Please see the GPGME manual for more information.
 
         """
-        if func == None:
+        if func is None:
             hookdata = None
         else:
-            if hook == None:
+            if hook is None:
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
@@ -1081,10 +1331,10 @@ class Context(GpgmeWrapper):
         Please see the GPGME manual for more information.
 
         """
-        if func == None:
+        if func is None:
             hookdata = None
         else:
-            if hook == None:
+            if hook is None:
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
@@ -1152,8 +1402,8 @@ class Context(GpgmeWrapper):
         magic numbers will break as a result.
 
         """
-        warnings.warn("Call to deprecated method op_edit.",
-                      category=DeprecationWarning)
+        warnings.warn(
+            "Call to deprecated method op_edit.", category=DeprecationWarning)
         return self.interact(key, func, sink=out, fnc_value=fnc_value)
 
 
@@ -1182,7 +1432,8 @@ class Data(GpgmeWrapper):
         # This list is compiled using
         #
         # $ grep -v '^gpgme_error_t ' obj/lang/python/python3.5-gpg/gpgme.h \
-        #   | awk "/\(gpgme_data_t/ { printf (\"'%s',\\n\", \$2) } " | sed "s/'\\*/'/"
+        #   | awk "/\(gpgme_data_t/ { printf (\"'%s',\\n\", \$2) } " \
+        #   | sed "s/'\\*/'/"
         return name not in {
             'gpgme_data_read',
             'gpgme_data_write',
@@ -1191,11 +1442,17 @@ class Data(GpgmeWrapper):
             'gpgme_data_release_and_get_mem',
             'gpgme_data_get_encoding',
             'gpgme_data_get_file_name',
+            'gpgme_data_set_flag',
             'gpgme_data_identify',
         }
 
-    def __init__(self, string=None, file=None, offset=None,
-                 length=None, cbs=None, copy=True):
+    def __init__(self,
+                 string=None,
+                 file=None,
+                 offset=None,
+                 length=None,
+                 cbs=None,
+                 copy=True):
         """Initialize a new gpgme_data_t object.
 
         If no args are specified, make it an empty object.
@@ -1239,13 +1496,13 @@ class Data(GpgmeWrapper):
         super(Data, self).__init__(None)
         self.data_cbs = None
 
-        if cbs != None:
+        if cbs is not None:
             self.new_from_cbs(*cbs)
-        elif string != None:
+        elif string is not None:
             self.new_from_mem(string, copy)
-        elif file != None and offset != None and length != None:
+        elif file is not None and offset is not None and length is not None:
             self.new_from_filepart(file, offset, length)
-        elif file != None:
+        elif file is not None:
             if util.is_a_string(file):
                 self.new_from_file(file, copy)
             else:
@@ -1258,7 +1515,7 @@ class Data(GpgmeWrapper):
             # At interpreter shutdown, gpgme is set to NONE.
             return
 
-        if self.wrapped != None and gpgme.gpgme_data_release:
+        if self.wrapped is not None and gpgme.gpgme_data_release:
             gpgme.gpgme_data_release(self.wrapped)
             if self._callback_excinfo:
                 gpgme.gpg_raise_callback_exception(self)
@@ -1268,6 +1525,7 @@ class Data(GpgmeWrapper):
     # Implement the context manager protocol.
     def __enter__(self):
         return self
+
     def __exit__(self, type, value, tb):
         self.__del__()
 
@@ -1282,7 +1540,8 @@ class Data(GpgmeWrapper):
 
     def new_from_mem(self, string, copy=True):
         tmp = gpgme.new_gpgme_data_t_p()
-        errorcheck(gpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
+        errorcheck(
+            gpgme.gpgme_data_new_from_mem(tmp, string, len(string), copy))
         self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
         gpgme.delete_gpgme_data_t_p(tmp)
 
@@ -1300,12 +1559,12 @@ class Data(GpgmeWrapper):
 
     def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
         tmp = gpgme.new_gpgme_data_t_p()
-        if hook != None:
-            hookdata = (weakref.ref(self),
-                        read_cb, write_cb, seek_cb, release_cb, hook)
+        if hook is not 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)
+            hookdata = (weakref.ref(self), read_cb, write_cb, seek_cb,
+                        release_cb)
         gpgme.gpg_data_new_from_cbs(self, hookdata, tmp)
         self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
         gpgme.delete_gpgme_data_t_p(tmp)
@@ -1327,12 +1586,13 @@ class Data(GpgmeWrapper):
             filename = file
         else:
             fp = gpgme.fdopen(file.fileno(), file.mode)
-            if fp == None:
-                raise ValueError("Failed to open file from %s arg %s" % \
-                      (str(type(file)), str(file)))
+            if fp is None:
+                raise ValueError("Failed to open file from %s arg %s" % (str(
+                    type(file)), str(file)))
 
-        errorcheck(gpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
-                                                      offset, length))
+        errorcheck(
+            gpgme.gpgme_data_new_from_filepart(tmp, filename, fp, offset,
+                                               length))
         self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
         gpgme.delete_gpgme_data_t_p(tmp)
 
@@ -1349,10 +1609,16 @@ class Data(GpgmeWrapper):
 
     def new_from_stream(self, file):
         """This wrap around gpgme_data_new_from_stream is an alias for
-        new_from_fd() method since in python there's not difference
-        between file stream and file descriptor"""
+        new_from_fd() method since in python there's no difference
+        between file stream and file descriptor."""
         self.new_from_fd(file)
 
+    def new_from_estream(self, file):
+        """This wrap around gpgme_data_new_from_estream is an alias for
+        new_from_fd() method since in python there's no difference
+        between file stream and file descriptor, but using fd broke."""
+        self.new_from_stream(file)
+
     def write(self, buffer):
         """Write buffer given as string or bytes.
 
@@ -1365,7 +1631,7 @@ class Data(GpgmeWrapper):
                 raise GPGMEError.fromSyserror()
         return written
 
-    def read(self, size = -1):
+    def read(self, size=-1):
         """Read at most size bytes, returned as bytes.
 
         If the size argument is negative or omitted, read until EOF is reached.
@@ -1400,6 +1666,7 @@ class Data(GpgmeWrapper):
                 chunks.append(result)
             return b''.join(chunks)
 
+
 def pubkey_algo_string(subkey):
     """Return short algorithm string
 
@@ -1412,6 +1679,7 @@ def pubkey_algo_string(subkey):
     """
     return gpgme.gpgme_pubkey_algo_string(subkey)
 
+
 def pubkey_algo_name(algo):
     """Return name of public key algorithm
 
@@ -1424,6 +1692,7 @@ def pubkey_algo_name(algo):
     """
     return gpgme.gpgme_pubkey_algo_name(algo)
 
+
 def hash_algo_name(algo):
     """Return name of hash algorithm
 
@@ -1436,6 +1705,7 @@ def hash_algo_name(algo):
     """
     return gpgme.gpgme_hash_algo_name(algo)
 
+
 def get_protocol_name(proto):
     """Get protocol description
 
@@ -1447,6 +1717,7 @@ def get_protocol_name(proto):
     """
     return gpgme.gpgme_get_protocol_name(proto)
 
+
 def addrspec_from_uid(uid):
     """Return the address spec
 
@@ -1458,22 +1729,26 @@ def addrspec_from_uid(uid):
     """
     return gpgme.gpgme_addrspec_from_uid(uid)
 
+
 def check_version(version=None):
     return gpgme.gpgme_check_version(version)
 
+
 # check_version also makes sure that several subsystems are properly
 # initialized, and it must be run at least once before invoking any
 # other function.  We do it here so that the user does not have to do
 # it unless she really wants to check for a certain version.
 check_version()
 
-def engine_check_version (proto):
+
+def engine_check_version(proto):
     try:
         errorcheck(gpgme.gpgme_engine_check_version(proto))
         return True
     except errors.GPGMEError:
         return False
 
+
 def get_engine_info():
     ptr = gpgme.new_gpgme_engine_info_t_p()
     try:
@@ -1484,6 +1759,7 @@ def get_engine_info():
     gpgme.delete_gpgme_engine_info_t_p(ptr)
     return info
 
+
 def set_engine_info(proto, file_name, home_dir=None):
     """Changes the default configuration of the crypto engine implementing
     the protocol 'proto'. 'file_name' is the file name of
@@ -1492,10 +1768,12 @@ def set_engine_info(proto, file_name, home_dir=None):
     used if omitted)."""
     errorcheck(gpgme.gpgme_set_engine_info(proto, file_name, home_dir))
 
+
 def set_locale(category, value):
     """Sets the default locale used by contexts"""
     errorcheck(gpgme.gpgme_set_locale(None, category, value))
 
+
 def wait(hang):
     """Wait for asynchronous call on any Context  to finish.
     Wait forever if hang is True.
@@ -1509,7 +1787,7 @@ def wait(hang):
     context = gpgme.gpgme_wait(None, ptr, hang)
     status = gpgme.gpgme_error_t_p_value(ptr)
     gpgme.delete_gpgme_error_t_p(ptr)
-    if context == None:
+    if context is None:
         errorcheck(status)
     else:
         context = Context(context)
index c41ac69..9c7f037 100644 (file)
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 from . import gpgme
 from . import util
 
+del absolute_import, print_function, unicode_literals
+
 # To appease static analysis tools, we define some constants here.
 # They are overwritten with the proper values by process_constants.
 NO_ERROR = None
@@ -30,6 +31,7 @@ EOF = None
 util.process_constants('GPG_ERR_', globals())
 del util
 
+
 class GpgError(Exception):
     """A GPG Error
 
@@ -55,6 +57,7 @@ class GpgError(Exception):
     exception objects.
 
     """
+
     def __init__(self, error=None, context=None, results=None):
         self.error = error
         self.context = context
@@ -62,37 +65,38 @@ class GpgError(Exception):
 
     @property
     def code(self):
-        if self.error == None:
+        if self.error is None:
             return None
         return gpgme.gpgme_err_code(self.error)
 
     @property
     def code_str(self):
-        if self.error == None:
+        if self.error is None:
             return None
         return gpgme.gpgme_strerror(self.error)
 
     @property
     def source(self):
-        if self.error == None:
+        if self.error is None:
             return None
         return gpgme.gpgme_err_source(self.error)
 
     @property
     def source_str(self):
-        if self.error == None:
+        if self.error is None:
             return None
         return gpgme.gpgme_strsource(self.error)
 
     def __str__(self):
         msgs = []
-        if self.context != None:
+        if self.context is not None:
             msgs.append(self.context)
-        if self.error != None:
+        if self.error is not None:
             msgs.append(self.source_str)
             msgs.append(self.code_str)
         return ': '.join(msgs)
 
+
 class GPGMEError(GpgError):
     '''Generic error
 
@@ -101,24 +105,30 @@ class GPGMEError(GpgError):
     returns an error.  This is the error that was used in PyME.
 
     '''
+
     @classmethod
     def fromSyserror(cls):
         return cls(gpgme.gpgme_err_code_from_syserror())
+
     @property
     def message(self):
         return self.context
+
     def getstring(self):
         return str(self)
+
     def getcode(self):
         return self.code
+
     def getsource(self):
         return self.source
 
 
-def errorcheck(retval, extradata = None):
+def errorcheck(retval, extradata=None):
     if retval:
         raise GPGMEError(retval, extradata)
 
+
 class KeyNotFound(GPGMEError, KeyError):
     """Raised if a key was not found
 
@@ -127,63 +137,76 @@ class KeyNotFound(GPGMEError, KeyError):
     indicating EOF, and a KeyError.
 
     """
+
     def __init__(self, keystr):
         self.keystr = keystr
         GPGMEError.__init__(self, EOF)
+
     def __str__(self):
         return self.keystr
 
+
 # These errors are raised in the idiomatic interface code.
 
+
 class EncryptionError(GpgError):
     pass
 
+
 class InvalidRecipients(EncryptionError):
     def __init__(self, recipients, **kwargs):
         EncryptionError.__init__(self, **kwargs)
         self.recipients = recipients
+
     def __str__(self):
-        return ", ".join("{}: {}".format(r.fpr,
-                                         gpgme.gpgme_strerror(r.reason))
+        return ", ".join("{}: {}".format(r.fpr, gpgme.gpgme_strerror(r.reason))
                          for r in self.recipients)
 
+
 class DeryptionError(GpgError):
     pass
 
+
 class UnsupportedAlgorithm(DeryptionError):
     def __init__(self, algorithm, **kwargs):
         DeryptionError.__init__(self, **kwargs)
         self.algorithm = algorithm
+
     def __str__(self):
         return self.algorithm
 
+
 class SigningError(GpgError):
     pass
 
+
 class InvalidSigners(SigningError):
     def __init__(self, signers, **kwargs):
         SigningError.__init__(self, **kwargs)
         self.signers = signers
+
     def __str__(self):
-        return ", ".join("{}: {}".format(s.fpr,
-                                         gpgme.gpgme_strerror(s.reason))
+        return ", ".join("{}: {}".format(s.fpr, gpgme.gpgme_strerror(s.reason))
                          for s in self.signers)
 
+
 class VerificationError(GpgError):
     def __init__(self, result, **kwargs):
         GpgError.__init__(self, **kwargs)
         self.result = result
 
+
 class BadSignatures(VerificationError):
     def __str__(self):
-        return ", ".join("{}: {}".format(s.fpr,
-                                         gpgme.gpgme_strerror(s.status))
+        return ", ".join("{}: {}".format(s.fpr, gpgme.gpgme_strerror(s.status))
                          for s in self.result.signatures
                          if s.status != NO_ERROR)
 
+
 class MissingSignatures(VerificationError):
     def __init__(self, result, missing, **kwargs):
         VerificationError.__init__(self, result, **kwargs)
         self.missing = missing
+
     def __str__(self):
         return ", ".join(k.subkeys[0].fpr for k in self.missing)
index bfd0f68..6b5f63c 100644 (file)
@@ -19,7 +19,6 @@
 
 from __future__ import absolute_import, print_function, unicode_literals
 del absolute_import, print_function, unicode_literals
-
 """Robust result objects
 
 Results returned by the underlying library are fragile, i.e. they are
@@ -30,23 +29,28 @@ therefore create deep copies of the results.
 
 """
 
+
 class Result(object):
     """Result object
 
     Describes the result of an operation.
 
     """
-
     """Convert to types"""
     _type = {}
-
     """Map functions over list attributes"""
     _map = {}
-
     """Automatically copy unless blacklisted"""
     _blacklist = {
-        'acquire', 'append', 'disown', 'next', 'own', 'this', 'thisown',
+        'acquire',
+        'append',
+        'disown',
+        'next',
+        'own',
+        'this',
+        'thisown',
     }
+
     def __init__(self, fragile):
         for key, func in self._type.items():
             if hasattr(fragile, key):
@@ -67,52 +71,67 @@ class Result(object):
     def __repr__(self):
         return '{}({})'.format(
             self.__class__.__name__,
-            ', '.join('{}={!r}'.format(k, getattr(self, k))
-                      for k in dir(self) if not k.startswith('_')))
+            ', '.join('{}={!r}'.format(k, getattr(self, k)) for k in dir(self)
+                      if not k.startswith('_')))
+
 
 class InvalidKey(Result):
     pass
 
+
 class EncryptResult(Result):
     _map = dict(invalid_recipients=InvalidKey)
 
+
 class Recipient(Result):
     pass
 
+
 class DecryptResult(Result):
     _type = dict(wrong_key_usage=bool, is_de_vs=bool)
     _map = dict(recipients=Recipient)
 
+
 class NewSignature(Result):
     pass
 
+
 class SignResult(Result):
     _map = dict(invalid_signers=InvalidKey, signatures=NewSignature)
 
+
 class Notation(Result):
     pass
 
+
 class Signature(Result):
     _type = dict(wrong_key_usage=bool, chain_model=bool, is_de_vs=bool)
     _map = dict(notations=Notation)
 
+
 class VerifyResult(Result):
     _map = dict(signatures=Signature)
 
+
 class ImportStatus(Result):
     pass
 
+
 class ImportResult(Result):
     _map = dict(imports=ImportStatus)
 
+
 class GenkeyResult(Result):
     _type = dict(primary=bool, sub=bool)
 
+
 class KeylistResult(Result):
     _type = dict(truncated=bool)
 
+
 class VFSMountResult(Result):
     pass
 
+
 class EngineInfo(Result):
     pass
index e4fca4c..320a823 100644 (file)
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 
+del absolute_import, print_function, unicode_literals
+
+
 def process_constants(prefix, scope):
     """Called by the constant modules to load up the constants from the C
     library starting with PREFIX.  Matching constants will be inserted
@@ -30,17 +32,19 @@ def process_constants(prefix, scope):
     """
     from . import gpgme
     index = len(prefix)
-    constants = {identifier[index:]: getattr(gpgme, identifier)
-                 for identifier in dir(gpgme)
-                 if identifier.startswith(prefix)}
+    constants = {
+        identifier[index:]: getattr(gpgme, identifier)
+        for identifier in dir(gpgme) 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)
+    return ''.join('%{0:2x}'.format(ord(c))
+                   if c == '+' or c == '"' or c == '%' or ord(c) <= 0x20 else c
+                   for c in s)
+
 
 # Python2/3 compatibility
 if sys.version_info[0] == 3:
index 3864f8b..d26d33d 100644 (file)
@@ -21,7 +21,8 @@ GPG_AGENT = gpg-agent
 
 test_srcdir = $(top_srcdir)/tests/gpg
 
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) \
+GNUPGHOME=$(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) \
        LC_ALL=C GPG_AGENT_INFO= \
        top_srcdir=$(top_srcdir) \
        srcdir=$(srcdir) \
@@ -118,3 +119,7 @@ gpg.conf:
 gpg-agent.conf:
 # This is required for gpg2, which does not support command fd.
        echo pinentry-program $(abs_top_srcdir)/tests/gpg/pinentry >$@
+       echo disable-scdaemon >> $@
+
+
+# end-of-file
index c7512dd..9efd7ee 100644 (file)
@@ -113,7 +113,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
@@ -334,7 +334,8 @@ top_srcdir = @top_srcdir@
 GPG = gpg
 GPG_AGENT = gpg-agent
 test_srcdir = $(top_srcdir)/tests/gpg
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) \
+GNUPGHOME = $(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) \
        LC_ALL=C GPG_AGENT_INFO= \
        top_srcdir=$(top_srcdir) \
        srcdir=$(srcdir) \
@@ -626,6 +627,9 @@ gpg.conf:
 gpg-agent.conf:
 # This is required for gpg2, which does not support command fd.
        echo pinentry-program $(abs_top_srcdir)/tests/gpg/pinentry >$@
+       echo disable-scdaemon >> $@
+
+# end-of-file
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
index 65375cb..d0d52dc 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import subprocess
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
-subprocess.check_call([os.path.join(os.getenv('top_srcdir'),
-                                    "tests", "start-stop-agent"), "--stop"])
+subprocess.check_call([
+    os.path.join(os.getenv('top_srcdir'), "tests", "start-stop-agent"),
+    "--stop"
+])
index 49e4f82..30a8de7 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import subprocess
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 print("Using gpg module from {0!r}.".format(os.path.dirname(gpg.__file__)))
 
-subprocess.check_call([os.path.join(os.getenv('top_srcdir'),
-                                    "tests", "start-stop-agent"), "--start"])
+subprocess.check_call([
+    os.path.join(os.getenv('top_srcdir'), "tests", "start-stop-agent"),
+    "--start"
+])
 
 with gpg.Context() as c:
     alpha = c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False)
index 95df197..cec13b5 100644 (file)
 # 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/>.
 
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
+from __future__ import absolute_import, division
+from __future__ import print_function, unicode_literals
 
 import argparse
 import glob
@@ -28,34 +26,50 @@ import os
 import subprocess
 import sys
 
+del absolute_import, division, print_function, unicode_literals
+
+
 class SplitAndAccumulate(argparse.Action):
     def __call__(self, parser, namespace, values, option_string=None):
         current = getattr(namespace, self.dest, list())
         current.extend(values.split())
         setattr(namespace, self.dest, current)
 
+
 parser = argparse.ArgumentParser(description='Run tests.')
-parser.add_argument('tests', metavar='TEST', type=str, nargs='+',
-                    help='A test to run')
-parser.add_argument('-v', '--verbose', action="store_true", default=False,
-                    help='Be verbose.')
-parser.add_argument('-q', '--quiet', action="store_true", default=False,
-                    help='Be quiet.')
-parser.add_argument('--interpreters', metavar='PYTHON', type=str,
-                    default=[], action=SplitAndAccumulate,
-                    help='Use these interpreters to run the tests, ' +
-                    'separated by spaces.')
-parser.add_argument('--srcdir', type=str,
-                    default=os.environ.get("srcdir", ""),
-                    help='Location of the tests.')
-parser.add_argument('--builddir', type=str,
-                    default=os.environ.get("abs_builddir", ""),
-                    help='Location of the tests.')
-parser.add_argument('--python-libdir', type=str,
-                    default=None,
-                    help='Optional location of the in-tree module lib directory.')
-parser.add_argument('--parallel', action="store_true", default=False,
-                    help='Ignored.  For compatibility with run-tests.scm.')
+parser.add_argument(
+    'tests', metavar='TEST', type=str, nargs='+', help='A test to run')
+parser.add_argument(
+    '-v', '--verbose', action="store_true", default=False, help='Be verbose.')
+parser.add_argument(
+    '-q', '--quiet', action="store_true", default=False, help='Be quiet.')
+parser.add_argument(
+    '--interpreters',
+    metavar='PYTHON',
+    type=str,
+    default=[],
+    action=SplitAndAccumulate,
+    help='Use these interpreters to run the tests, ' + 'separated by spaces.')
+parser.add_argument(
+    '--srcdir',
+    type=str,
+    default=os.environ.get("srcdir", ""),
+    help='Location of the tests.')
+parser.add_argument(
+    '--builddir',
+    type=str,
+    default=os.environ.get("abs_builddir", ""),
+    help='Location of the tests.')
+parser.add_argument(
+    '--python-libdir',
+    type=str,
+    default=None,
+    help='Optional location of the in-tree module lib directory.')
+parser.add_argument(
+    '--parallel',
+    action="store_true",
+    default=False,
+    help='Ignored.  For compatibility with run-tests.scm.')
 
 args = parser.parse_args()
 if not args.interpreters:
@@ -64,26 +78,31 @@ if not args.interpreters:
 out = sys.stdout if args.verbose else None
 err = sys.stderr if args.verbose else None
 
+
 def status_to_str(code):
     return {0: "PASS", 77: "SKIP", 99: "ERROR"}.get(code, "FAIL")
 
+
 results = list()
 for interpreter in args.interpreters:
-    version = subprocess.check_output(
-        [interpreter, "-c", "import sys; print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"]).strip().decode()
+    version = subprocess.check_output([
+        interpreter, "-c",
+        "import sys; print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"
+    ]).strip().decode()
 
     if args.python_libdir:
         python_libdir = args.python_libdir
     else:
-        pattern = os.path.join(args.builddir, "..",
-                               "{0}-gpg".format(os.path.basename(interpreter)),
-                               "lib*")
+        pattern = os.path.join(args.builddir, "..", "{0}-gpg".format(
+            os.path.basename(interpreter)), "lib*")
         libdirs = glob.glob(pattern)
         if len(libdirs) == 0:
-            sys.exit("Build directory matching {0!r} not found.".format(pattern))
+            sys.exit(
+                "Build directory matching {0!r} not found.".format(pattern))
         elif len(libdirs) > 1:
-            sys.exit("Multiple build directories matching {0!r} found: {1}".format(
-                pattern, libdirs))
+            sys.exit(
+                "Multiple build directories matching {0!r} found: {1}".format(
+                    pattern, libdirs))
         python_libdir = libdirs[0]
 
     env = dict(os.environ)
@@ -95,16 +114,22 @@ for interpreter in args.interpreters:
     for test in args.tests:
         status = subprocess.call(
             [interpreter, os.path.join(args.srcdir, test)],
-            env=env, stdout=out, stderr=err)
+            env=env,
+            stdout=out,
+            stderr=err)
         if not args.quiet:
             print("{0}: {1}".format(status_to_str(status), test))
         results.append(status)
 
+
 def count(status):
     return len(list(filter(lambda x: x == status, results)))
+
+
 def failed():
     return len(list(filter(lambda x: x not in (0, 77, 99), results)))
 
+
 if not args.quiet:
     print("{0} tests run, {1} succeeded, {2} failed, {3} skipped.".format(
         len(results), count(0), failed(), count(77)))
index efccf31..e6b3d8b 100644 (file)
@@ -16,7 +16,6 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import contextlib
 import shutil
@@ -27,20 +26,28 @@ import tempfile
 import time
 import gpg
 
+del absolute_import, print_function, unicode_literals
+
+
 def assert_gpg_version(version=(2, 1, 0)):
     with gpg.Context() as c:
-        clean_version = re.match(r'\d+\.\d+\.\d+', c.engine_info.version).group(0)
+        clean_version = re.match(r'\d+\.\d+\.\d+',
+                                 c.engine_info.version).group(0)
         if tuple(map(int, clean_version.split('.'))) < version:
             print("GnuPG too old: have {0}, need {1}.".format(
                 c.engine_info.version, '.'.join(map(str, version))))
             sys.exit(77)
 
+
 def have_tofu_support(ctx, some_uid):
-    keys = list(ctx.keylist(some_uid,
-                            mode=(gpg.constants.keylist.mode.LOCAL
-                                  |gpg.constants.keylist.mode.WITH_TOFU)))
+    keys = list(
+        ctx.keylist(
+            some_uid,
+            mode=(gpg.constants.keylist.mode.LOCAL |
+                  gpg.constants.keylist.mode.WITH_TOFU)))
     return len(keys) > 0
 
+
 # Skip the Python tests for GnuPG < 2.1.12.  Prior versions do not
 # understand the command line flags that we assume exist.  C.f. issue
 # 3008.
@@ -53,13 +60,18 @@ encrypt_only = "F52770D5C4DB41408D918C9F920572769B9FE19C"
 sign_only = "7CCA20CCDE5394CEE71C9F0BFED153F12F18F45D"
 no_such_key = "A" * 40
 
+
 def make_filename(name):
     return os.path.join(os.environ['top_srcdir'], 'tests', 'gpg', name)
 
+
 def in_srcdir(name):
     return os.path.join(os.environ['srcdir'], name)
 
+
 verbose = int(os.environ.get('verbose', 0)) > 1
+
+
 def print_data(data):
     if verbose:
         try:
@@ -75,10 +87,12 @@ def print_data(data):
         else:
             sys.stdout.write(data)
 
+
 def mark_key_trusted(ctx, key):
     class Editor(object):
         def __init__(self):
             self.steps = ["trust", "save"]
+
         def edit(self, status, args, out):
             if args == "keyedit.prompt":
                 result = self.steps.pop(0)
@@ -91,6 +105,7 @@ def mark_key_trusted(ctx, key):
             else:
                 result = None
             return result
+
     with gpg.Data() as sink:
         ctx.op_edit(key, Editor().edit, sink, sink)
 
@@ -103,9 +118,11 @@ class TemporaryDirectory(object):
     def __enter__(self):
         self.path = tempfile.mkdtemp()
         return self.path
+
     def __exit__(self, *args):
         shutil.rmtree(self.path, ignore_errors=True)
 
+
 @contextlib.contextmanager
 def EphemeralContext():
     with TemporaryDirectory() as tmp:
@@ -124,7 +141,7 @@ def EphemeralContext():
                 ctx.assuan_transact(["KILLAGENT"])
             except gpg.errors.GPGMEError as e:
                 if e.getcode() == gpg.errors.ASS_CONNECT_FAILED:
-                    pass # the agent was not running
+                    pass  # the agent was not running
                 else:
                     raise
 
index 9a70cda..25a1c23 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 c = gpg.Context()
 c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
@@ -33,6 +34,7 @@ sink = gpg.Data()
 
 # Valid passphrases, both as string and bytes.
 for passphrase in ('foo', b'foo'):
+
     def passphrase_cb(hint, desc, prev_bad, hook=None):
         assert hook == passphrase
         return hook
@@ -40,10 +42,12 @@ for passphrase in ('foo', b'foo'):
     c.set_passphrase_cb(passphrase_cb, passphrase)
     c.op_encrypt([], 0, source, sink)
 
+
 # Returning an invalid type.
 def passphrase_cb(hint, desc, prev_bad, hook=None):
     return 0
 
+
 c.set_passphrase_cb(passphrase_cb, None)
 try:
     c.op_encrypt([], 0, source, sink)
@@ -55,9 +59,12 @@ else:
 
 # Raising an exception inside callback.
 myException = Exception()
+
+
 def passphrase_cb(hint, desc, prev_bad, hook=None):
     raise myException
 
+
 c.set_passphrase_cb(passphrase_cb, None)
 try:
     c.op_encrypt([], 0, source, sink)
@@ -66,10 +73,12 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
+
 # Wrong kind of callback function.
 def bad_passphrase_cb():
     pass
 
+
 c.set_passphrase_cb(bad_passphrase_cb, None)
 try:
     c.op_encrypt([], 0, source, sink)
@@ -78,8 +87,6 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
-
-
 # Test the progress callback.
 parms = """<GnupgKeyParms format="internal">
 Key-Type: RSA
@@ -88,26 +95,31 @@ Name-Real: Joe Tester
 Name-Comment: with stupid passphrase
 Name-Email: joe+gpg@example.org
 Passphrase: Crypt0R0cks
-Expire-Date: 2020-12-31
+Expire-Date: 2099-12-31
 </GnupgKeyParms>
 """
 
 messages = []
+
+
 def progress_cb(what, typ, current, total, hook=None):
     assert hook == messages
     messages.append(
         "PROGRESS UPDATE: what = {}, type = {}, current = {}, total = {}"
         .format(what, typ, current, total))
 
+
 c = gpg.Context()
 c.set_progress_cb(progress_cb, messages)
 c.op_genkey(parms, None, None)
 assert len(messages) > 0
 
+
 # Test exception handling.
 def progress_cb(what, typ, current, total, hook=None):
     raise myException
 
+
 c = gpg.Context()
 c.set_progress_cb(progress_cb, None)
 try:
@@ -117,7 +129,6 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
-
 # Test the edit callback.
 c = gpg.Context()
 c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
@@ -127,11 +138,15 @@ alpha = c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False)
 
 cookie = object()
 edit_cb_called = False
+
+
 def edit_cb(status, args, hook):
     global edit_cb_called
     edit_cb_called = True
     assert hook == cookie
     return "quit" if args == "keyedit.prompt" else None
+
+
 c.op_edit(alpha, edit_cb, cookie, sink)
 assert edit_cb_called
 
@@ -141,8 +156,11 @@ c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
 c.set_passphrase_cb(lambda *args: "abc")
 sink = gpg.Data()
 
+
 def edit_cb(status, args):
     raise myException
+
+
 try:
     c.op_edit(alpha, edit_cb, None, sink)
 except Exception as e:
@@ -150,18 +168,19 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
-
-
 # Test the status callback.
 source = gpg.Data("Hallo Leute\n")
 sink = gpg.Data()
 
 status_cb_called = False
+
+
 def status_cb(keyword, args, hook=None):
     global status_cb_called
     status_cb_called = True
     assert hook == cookie
 
+
 c = gpg.Context()
 c.set_status_cb(status_cb, cookie)
 c.set_ctx_flag("full-status", "1")
@@ -172,9 +191,11 @@ assert status_cb_called
 source = gpg.Data("Hallo Leute\n")
 sink = gpg.Data()
 
+
 def status_cb(keyword, args):
     raise myException
 
+
 c = gpg.Context()
 c.set_status_cb(status_cb, None)
 c.set_ctx_flag("full-status", "1")
@@ -186,13 +207,16 @@ 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 = gpg.Data(cbs=(read_cb, None, None, release_cb, cookie))
 try:
     data.read()
@@ -201,8 +225,11 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
+
 def read_cb(amount):
     raise myException
+
+
 data = gpg.Data(cbs=(read_cb, None, None, lambda: None))
 try:
     data.read()
@@ -215,6 +242,8 @@ else:
 def write_cb(what, hook=None):
     assert hook == cookie
     return "wrong type"
+
+
 data = gpg.Data(cbs=(None, write_cb, None, release_cb, cookie))
 try:
     data.write(b'stuff')
@@ -223,8 +252,11 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
+
 def write_cb(what):
     raise myException
+
+
 data = gpg.Data(cbs=(None, write_cb, None, lambda: None))
 try:
     data.write(b'stuff')
@@ -237,6 +269,8 @@ else:
 def seek_cb(offset, whence, hook=None):
     assert hook == cookie
     return "wrong type"
+
+
 data = gpg.Data(cbs=(None, None, seek_cb, release_cb, cookie))
 try:
     data.seek(0, os.SEEK_SET)
@@ -245,8 +279,11 @@ except Exception as e:
 else:
     assert False, "Expected an error, got none"
 
+
 def seek_cb(offset, whence):
     raise myException
+
+
 data = gpg.Data(cbs=(None, None, seek_cb, lambda: None))
 try:
     data.seek(0, os.SEEK_SET)
index 5cf074c..006c11f 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 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.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 data = gpg.Data('Hello world!')
 assert data.read() == b'Hello world!'
@@ -94,7 +95,8 @@ with tempfile.NamedTemporaryFile() as tmp:
 
     # Open using name, offset, and length.
     data = gpg.Data(file=tmp.name, offset=23, length=42)
-    assert data.read() == binjunk[23:23+42]
+    assert data.read() == binjunk[23:23 + 42]
+
 
 # Test callbacks.
 class DataObject(object):
@@ -118,6 +120,7 @@ class DataObject(object):
         assert not self.released
         self.released = True
 
+
 do = DataObject()
 cookie = object()
 data = gpg.Data(cbs=(do.read, do.write, do.seek, do.release, cookie))
index 03bbc4b..991d18c 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
+
 def check_verify_result(result, summary, fpr, status):
     assert len(result.signatures) == 1, "Unexpected number of signatures"
     sig = result.signatures[0]
@@ -32,7 +34,9 @@ def check_verify_result(result, summary, fpr, status):
     assert len(sig.notations) == 0
     assert not sig.wrong_key_usage
     assert sig.validity == gpg.constants.validity.FULL
-    assert gpg.errors.GPGMEError(sig.validity_reason).getcode() == gpg.errors.NO_ERROR
+    assert gpg.errors.GPGMEError(
+        sig.validity_reason).getcode() == gpg.errors.NO_ERROR
+
 
 c = gpg.Context()
 
@@ -47,10 +51,9 @@ assert not result.unsupported_algorithm, \
 support.print_data(sink)
 
 verify_result = c.op_verify_result()
-check_verify_result(verify_result,
-                    gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
-                    "A0FF4590BB6122EDEF6E3C542D727CC768697734",
-                    gpg.errors.NO_ERROR)
+check_verify_result(
+    verify_result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
+    "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR)
 
 # Idiomatic interface.
 with gpg.Context() as c:
@@ -60,15 +63,14 @@ with gpg.Context() as c:
         c.decrypt(open(support.make_filename("cipher-2.asc")), verify=[alpha])
     assert plaintext.find(b'Wenn Sie dies lesen k') >= 0, \
         'Plaintext not found'
-    check_verify_result(verify_result,
-                        gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
-                        "A0FF4590BB6122EDEF6E3C542D727CC768697734",
-                        gpg.errors.NO_ERROR)
+    check_verify_result(
+        verify_result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
+        "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR)
 
     try:
-        c.decrypt(open(support.make_filename("cipher-2.asc")),
-                  verify=[alpha, bob])
-    except gpg.errors.MissingSignatures as e:
+        c.decrypt(
+            open(support.make_filename("cipher-2.asc")), verify=[alpha, bob])
+    except Exception as e:
         assert len(e.missing) == 1
         assert e.missing[0] == bob
     else:
index 05b6d8b..f2417c9 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 c = gpg.Context()
 
 source = gpg.Data(file=support.make_filename("cipher-1.asc"))
index b1075a9..cbc17d9 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import os
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
+
 
 class KeyEditor(object):
     def __init__(self):
@@ -47,11 +49,12 @@ class KeyEditor(object):
             result = None
 
         if self.verbose:
-            sys.stderr.write("Code: {}, args: {!r}, Returning: {!r}\n"
-                             .format(status, args, result))
+            sys.stderr.write("Code: {}, args: {!r}, Returning: {!r}\n".format(
+                status, args, result))
 
         return result
 
+
 c = gpg.Context()
 c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
 c.set_passphrase_cb(lambda *args: "abc")
@@ -59,13 +62,15 @@ c.set_armor(True)
 
 # The deprecated interface.
 editor = KeyEditor()
-c.interact(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
-           editor.edit_fnc)
+c.interact(
+    c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
+    editor.edit_fnc)
 assert editor.done
 
 # The deprecated interface.
 sink = gpg.Data()
 editor = KeyEditor()
-c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
-          editor.edit_fnc, sink, sink)
+c.op_edit(
+    c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
+    editor.edit_fnc, sink, sink)
 assert editor.done
index 5646085..18576ac 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import random
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 if len(sys.argv) == 2:
     nbytes = int(sys.argv[1])
 else:
@@ -33,6 +34,8 @@ else:
 c = gpg.Context()
 
 ntoread = nbytes
+
+
 def read_cb(amount):
     global ntoread
     chunk = ntoread if ntoread < amount else amount
@@ -41,12 +44,16 @@ def read_cb(amount):
     assert chunk >= 0
     return bytes(bytearray(random.randrange(256) for i in range(chunk)))
 
+
 nwritten = 0
+
+
 def write_cb(data):
     global nwritten
     nwritten += len(data)
     return len(data)
 
+
 source = gpg.Data(cbs=(read_cb, None, None, lambda: None))
 sink = gpg.Data(cbs=(None, write_cb, None, lambda: None))
 
@@ -61,5 +68,5 @@ assert not result.invalid_recipients, \
 assert ntoread == 0
 
 if support.verbose:
-    sys.stderr.write(
-        "plaintext={} bytes, ciphertext={} bytes\n".format(nbytes, nwritten))
+    sys.stderr.write("plaintext={} bytes, ciphertext={} bytes\n".format(
+        nbytes, nwritten))
index f04783f..84d1abb 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 c = gpg.Context()
 c.set_armor(True)
 
+
 def check_result(r, typ):
     if r.invalid_signers:
         sys.exit("Invalid signer found: {}".format(r.invalid_signers.fpr))
@@ -42,7 +44,8 @@ def check_result(r, typ):
         sys.exit("Wrong pubkey algorithm reported: {}".format(
             signature.pubkey_algo))
 
-    if signature.hash_algo not in (gpg.constants.md.SHA1, gpg.constants.md.RMD160):
+    if signature.hash_algo not in (gpg.constants.md.SHA1,
+                                   gpg.constants.md.RMD160):
         sys.exit("Wrong hash algorithm reported: {}".format(
             signature.hash_algo))
 
@@ -53,6 +56,7 @@ def check_result(r, typ):
     if signature.fpr != "A0FF4590BB6122EDEF6E3C542D727CC768697734":
         sys.exit("Wrong fingerprint reported: {}".format(signature.fpr))
 
+
 keys = []
 keys.append(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False))
 keys.append(c.get_key("D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", False))
@@ -61,7 +65,8 @@ for recipients in (keys, []):
     source = gpg.Data("Hallo Leute\n")
     sink = gpg.Data()
 
-    c.op_encrypt_sign(recipients, gpg.constants.ENCRYPT_ALWAYS_TRUST, source, sink)
+    c.op_encrypt_sign(recipients, gpg.constants.ENCRYPT_ALWAYS_TRUST, source,
+                      sink)
     result = c.op_encrypt_result()
     assert not result.invalid_recipients, \
         "Invalid recipient encountered: {}".format(
@@ -72,13 +77,11 @@ for recipients in (keys, []):
 
     support.print_data(sink)
 
-
 # Idiomatic interface.
 with gpg.Context(armor=True) as c:
     message = "Hallo Leute\n".encode()
-    ciphertext, _, sig_result = c.encrypt(message,
-                                          recipients=keys,
-                                          always_trust=True)
+    ciphertext, _, sig_result = c.encrypt(
+        message, recipients=keys, always_trust=True)
     assert len(ciphertext) > 0
     assert ciphertext.find(b'BEGIN PGP MESSAGE') > 0, 'Marker not found'
     check_result(sig_result, gpg.constants.sig.mode.NORMAL)
index 8299293..9b099fe 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 for passphrase in ("abc", b"abc"):
     c = gpg.Context()
@@ -34,6 +35,7 @@ for passphrase in ("abc", b"abc"):
     cipher = gpg.Data()
 
     passphrase_cb_called = 0
+
     def passphrase_cb(hint, desc, prev_bad, hook=None):
         global passphrase_cb_called
         passphrase_cb_called += 1
@@ -55,7 +57,7 @@ for passphrase in ("abc", b"abc"):
 
     c.op_decrypt(cipher, plain)
     # Seems like the passphrase is cached.
-    #assert passphrase_cb_called == 2, \
+    # assert passphrase_cb_called == 2, \
     #    "Callback called {} times".format(passphrase_cb_called)
     support.print_data(plain)
 
@@ -70,12 +72,12 @@ for passphrase in ("abc", b"abc"):
         # Check that the passphrase callback is not altered.
         def f(*args):
             assert False
+
         c.set_passphrase_cb(f)
 
         message = "Hallo Leute\n".encode()
-        ciphertext, _, _ = c.encrypt(message,
-                                     passphrase=passphrase,
-                                     sign=False)
+        ciphertext, _, _ = c.encrypt(
+            message, passphrase=passphrase, sign=False)
         assert ciphertext.find(b'BEGIN PGP MESSAGE') > 0, 'Marker not found'
 
         plaintext, _, _ = c.decrypt(ciphertext, passphrase=passphrase)
index 921502a..e702daa 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 c = gpg.Context()
 c.set_armor(True)
 
@@ -41,36 +42,37 @@ support.print_data(sink)
 
 # Idiomatic interface.
 with gpg.Context(armor=True) as c:
-    ciphertext, _, _ = c.encrypt("Hallo Leute\n".encode(),
-                                 recipients=keys,
-                                 sign=False,
-                                 always_trust=True)
+    ciphertext, _, _ = c.encrypt(
+        "Hallo Leute\n".encode(),
+        recipients=keys,
+        sign=False,
+        always_trust=True)
     assert len(ciphertext) > 0
     assert ciphertext.find(b'BEGIN PGP MESSAGE') > 0, 'Marker not found'
 
-    c.encrypt("Hallo Leute\n".encode(),
-              recipients=[c.get_key(support.encrypt_only, False)],
-              sign=False, always_trust=True)
+    c.encrypt(
+        "Hallo Leute\n".encode(),
+        recipients=[c.get_key(support.encrypt_only, False)],
+        sign=False,
+        always_trust=True)
 
     try:
-        c.encrypt("Hallo Leute\n".encode(),
-                  recipients=[c.get_key(support.sign_only, False)],
-                  sign=False, always_trust=True)
+        c.encrypt(
+            "Hallo Leute\n".encode(),
+            recipients=[c.get_key(support.sign_only, False)],
+            sign=False,
+            always_trust=True)
     except gpg.errors.InvalidRecipients as e:
         assert len(e.recipients) == 1
         assert support.sign_only.endswith(e.recipients[0].fpr)
     else:
         assert False, "Expected an InvalidRecipients error, got none"
 
-
-
     try:
         # People might be tempted to provide strings.
         # We should raise something useful.
-        ciphertext, _, _ = c.encrypt("Hallo Leute\n",
-                                     recipients=keys,
-                                     sign=False,
-                                     always_trust=True)
+        ciphertext, _, _ = c.encrypt(
+            "Hallo Leute\n", recipients=keys, sign=False, always_trust=True)
     except TypeError as e:
         # This test is a bit fragile, because the message
         # may very well change. So if the behaviour will change
index b9d5204..6d771dd 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 c = gpg.Context()
 c.set_armor(True)
 
@@ -32,8 +33,8 @@ support.print_data(sink)
 
 # Again. Now using a key array.
 keys = []
-keys.append(c.get_key("0x68697734", False)) # Alpha
-keys.append(c.get_key("0xA9E3B0B2", False)) # Bob
+keys.append(c.get_key("0x68697734", False))  # Alpha
+keys.append(c.get_key("0xA9E3B0B2", False))  # Bob
 sink = gpg.Data()
 c.op_export_keys(keys, 0, sink)
 support.print_data(sink)
index 32fe84a..d9c226f 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 testname = "abcde12345"
 
index b7ae4eb..238bbf3 100755 (executable)
@@ -18,7 +18,6 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import io
@@ -26,7 +25,9 @@ import os
 import tempfile
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 # Both Context and Data can be used as context manager:
 with gpg.Context() as c, gpg.Data() as d:
@@ -34,8 +35,9 @@ with gpg.Context() as c, gpg.Data() as d:
     d.write(b"Halloechen")
     leak_c = c
     leak_d = d
-assert leak_c.wrapped == None
-assert leak_d.wrapped == None
+assert leak_c.wrapped is None
+assert leak_d.wrapped is None
+
 
 def sign_and_verify(source, signed, sink):
     with gpg.Context() as c:
@@ -53,6 +55,7 @@ def sign_and_verify(source, signed, sink):
     sink.seek(0, os.SEEK_SET)
     assert sink.read() == b"Hallo Leute\n"
 
+
 # Demonstrate automatic wrapping of file-like objects with 'fileno'
 # method.
 with tempfile.TemporaryFile() as source, \
@@ -73,7 +76,7 @@ if sys.version_info[0] == 3:
     bio.truncate(1)
     if len(bio.getvalue()) != 1:
         # This version of Python is affected, preallocate buffer.
-        preallocate = 128*b'\x00'
+        preallocate = 128 * b'\x00'
     else:
         preallocate = b''
 
index e2edf5a..82d3a4e 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2016 g10 Code GmbH
+# Copyright (C) 2016 Tobias Mueller <muelli at cryptobitch.de>
 #
 # This file is part of GPGME.
 #
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
+
 def check_result(result, fpr, secret):
     assert result.considered == 1 or (secret and result.considered == 3)
     assert result.no_user_id == 0
-    assert not ((secret and result.imported != 0)
-                or (not secret and (result.imported != 0
-                                    and result.imported != 1)))
+    assert not ((secret and result.imported != 0) or
+                (not secret and
+                 (result.imported != 0 and result.imported != 1)))
     assert result.imported_rsa == 0
-    assert not ((secret and (result.unchanged != 0 and result.unchanged != 1))
-                or (not secret and ((result.imported == 0
-                                     and result.unchanged != 1)
-                                 or (result.imported == 1
-                                     and result.unchanged != 0))))
+    assert not ((secret and
+                 (result.unchanged != 0 and result.unchanged != 1)) or
+                (not secret and
+                 ((result.imported == 0 and result.unchanged != 1) or
+                  (result.imported == 1 and result.unchanged != 0))))
     assert result.new_user_ids == 0
     assert result.new_sub_keys == 0
-    assert not ((secret
-                 and ((result.secret_imported == 0
-                       and result.new_signatures != 0)
-                      or (result.secret_imported == 1
-                          and result.new_signatures > 1)))
-                or (not secret and result.new_signatures != 0))
+    assert not ((secret and (
+        (result.secret_imported == 0 and result.new_signatures != 0) or
+        (result.secret_imported == 1 and result.new_signatures > 1))) or
+                (not secret and result.new_signatures != 0))
     assert result.new_revocations == 0
-    assert not ((secret and result.secret_read != 1 and result.secret_read != 3)
-                or (not secret and result.secret_read != 0))
-    assert not ((secret and result.secret_imported != 0
-                 and result.secret_imported != 1
-                 and result.secret_imported != 2)
-                or (not secret and result.secret_imported != 0))
-    assert not ((secret
-                 and ((result.secret_imported == 0
-                       and result.secret_unchanged != 1
-                       and result.secret_unchanged != 2)
-                      or (result.secret_imported == 1
-                          and result.secret_unchanged != 0)))
-                or (not secret and result.secret_unchanged != 0))
+    assert not (
+        (secret and result.secret_read != 1 and result.secret_read != 3) or
+        (not secret and result.secret_read != 0))
+    assert not (
+        (secret and result.secret_imported != 0 and result.
+            secret_imported != 1 and result.
+            secret_imported != 2) or (not secret and result.
+                                      secret_imported != 0))
+    assert not ((secret and
+                 ((result.secret_imported == 0 and result.
+                   secret_unchanged != 1 and result.
+                   secret_unchanged != 2) or (result.
+                                              secret_imported == 1 and result.
+                                              secret_unchanged != 0))) or
+                (not secret and result.secret_unchanged != 0))
     assert result.not_imported == 0
     if secret:
         assert not (len(result.imports) in (0, 3))
@@ -67,12 +69,17 @@ def check_result(result, fpr, secret):
     assert len(result.imports) == 1 or fpr == result.imports[1].fpr
     assert result.imports[0].result == 0
 
+
 c = gpg.Context()
 
-c.op_import(gpg.Data(file=support.make_filename("pubkey-1.asc")))
-result = c.op_import_result()
+result = c.key_import(open(support.make_filename("pubkey-1.asc"), 'rb').read())
 check_result(result, "ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", False)
 
-c.op_import(gpg.Data(file=support.make_filename("seckey-1.asc")))
-result = c.op_import_result()
+result = c.key_import(open(support.make_filename("seckey-1.asc"), 'rb').read())
 check_result(result, "ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55", True)
+
+try:
+    result = c.key_import(b"thisisnotakey")
+except ValueError:
+    pass
+assert result == "IMPORT_PROBLEM"
index 6503eb7..4fd9ba0 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 support.assert_gpg_version((2, 1, 14))
 
+
 # Check expration of keys.  This test assumes three subkeys of which
 # 2 are expired; it is used with the "Whisky" test key.  It has
 # already been checked that these 3 subkeys are available.
 def check_whisky(name, key):
-  sub1 = key.subkeys[2]
-  sub2 = key.subkeys[3]
+    sub1 = key.subkeys[2]
+    sub2 = key.subkeys[3]
+
+    assert sub1.expired and sub2.expired, \
+        "Subkey of `{}' not flagged as expired".format(name)
+    assert sub1.expires == 1129636886 and sub2.expires == 1129636939, \
+        "Subkey of `{}' has wrong expiration date".format(name)
 
-  assert sub1.expired and sub2.expired, \
-      "Subkey of `{}' not flagged as expired".format(name)
-  assert sub1.expires == 1129636886 and sub2.expires == 1129636939, \
-      "Subkey of `{}' has wrong expiration date".format(name)
 
 keys = [
-    [ "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8",
-      [ [ "Alfa Test", "demo key", "alfa@example.net" ],
-        [ "Alpha Test", "demo key", "alpha@example.net" ],
-       [ "Alice", "demo key", "" ] ], 1 ],
-    [ "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F",
-      [ [ "Bob", "demo key", "" ],
-       [ "Bravo Test", "demo key", "bravo@example.net" ] ], 1 ],
-    [ "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60",
-      [ [ "Charlie Test", "demo key", "charlie@example.net" ] ], 1 ],
-    [ "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424",
-      [ [ "Delta Test", "demo key", "delta@example.net" ] ], 1 ],
-    [ "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D",
-      [ [ "Echelon", "demo key", "" ],
-       [ "Echo Test", "demo key", "echo@example.net" ],
-       [ "Eve", "demo key", "" ] ], 1 ],
-    [ "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E",
-      [ [ "Foxtrot Test", "demo key", "foxtrot@example.net" ] ], 1 ],
-    [ "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354",
-      [ [ "Golf Test", "demo key", "golf@example.net" ] ], 1 ],
-    [ "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A",
-      [ [ "Hotel Test", "demo key", "hotel@example.net" ] ], 1 ],
-    [ "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73",
-      [ [ "India Test", "demo key", "india@example.net" ] ], 1 ],
-    [ "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136",
-      [ [ "Juliet Test", "demo key", "juliet@example.net" ] ], 1 ],
-    [ "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02",
-      [ [ "Kilo Test", "demo key", "kilo@example.net" ] ], 1 ],
-    [ "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C",
-      [ [ "Lima Test", "demo key", "lima@example.net" ] ], 1 ],
-    [ "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8",
-      [ [ "Mallory", "demo key", "" ],
-       [ "Mike Test", "demo key", "mike@example.net" ] ], 1 ],
-    [ "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472",
-      [ [ "November Test", "demo key", "november@example.net" ] ], 1 ],
-    [ "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F",
-      [ [ "Oscar Test", "demo key", "oscar@example.net" ] ], 1 ],
-    [ "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C",
-      [ [ "Papa test", "demo key", "papa@example.net" ] ], 1 ],
-    [ "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4",
-      [ [ "Quebec Test", "demo key", "quebec@example.net" ] ], 1 ],
-    [ "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA",
-      [ [ "Romeo Test", "demo key", "romeo@example.net" ] ], 1 ],
-    [ "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4",
-      [ [ "Sierra Test", "demo key", "sierra@example.net" ] ], 1 ],
-    [ "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402",
-      [ [ "Tango Test", "demo key", "tango@example.net" ] ], 1 ],
-    [ "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9",
-      [ [ "Uniform Test", "demo key", "uniform@example.net" ] ], 1 ],
-    [ "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134",
-      [ [ "Victor Test", "demo key", "victor@example.org" ] ], 1 ],
-    [ "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6",
-      [ [ "Whisky Test", "demo key", "whisky@example.net" ] ], 3,
-      check_whisky ],
-    [ "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE",
-      [ [ "XRay Test", "demo key", "xray@example.net" ] ], 1 ],
-    [ "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD",
-      [ [ "Yankee Test", "demo key", "yankee@example.net" ] ], 1 ],
-    [ "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881",
-      [ [ "Zulu Test", "demo key", "zulu@example.net" ] ], 1 ],
+    [
+        "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8",
+        [["Alfa Test", "demo key", "alfa@example.net"],
+         ["Alpha Test", "demo key", "alpha@example.net"],
+         ["Alice", "demo key", ""]], 1
+    ],
+    [
+        "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F",
+        [["Bob", "demo key", ""],
+         ["Bravo Test", "demo key", "bravo@example.net"]], 1
+    ],
+    [
+        "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60",
+        [["Charlie Test", "demo key", "charlie@example.net"]], 1
+    ],
+    [
+        "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424",
+        [["Delta Test", "demo key", "delta@example.net"]], 1
+    ],
+    [
+        "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D",
+        [["Echelon", "demo key",
+          ""], ["Echo Test", "demo key", "echo@example.net"],
+         ["Eve", "demo key", ""]], 1
+    ],
+    [
+        "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E",
+        [["Foxtrot Test", "demo key", "foxtrot@example.net"]], 1
+    ],
+    [
+        "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354",
+        [["Golf Test", "demo key", "golf@example.net"]], 1
+    ],
+    [
+        "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A",
+        [["Hotel Test", "demo key", "hotel@example.net"]], 1
+    ],
+    [
+        "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73",
+        [["India Test", "demo key", "india@example.net"]], 1
+    ],
+    [
+        "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136",
+        [["Juliet Test", "demo key", "juliet@example.net"]], 1
+    ],
+    [
+        "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02",
+        [["Kilo Test", "demo key", "kilo@example.net"]], 1
+    ],
+    [
+        "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C",
+        [["Lima Test", "demo key", "lima@example.net"]], 1
+    ],
+    [
+        "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8",
+        [["Mallory", "demo key", ""],
+         ["Mike Test", "demo key", "mike@example.net"]], 1
+    ],
+    [
+        "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472",
+        [["November Test", "demo key", "november@example.net"]], 1
+    ],
+    [
+        "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F",
+        [["Oscar Test", "demo key", "oscar@example.net"]], 1
+    ],
+    [
+        "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C",
+        [["Papa test", "demo key", "papa@example.net"]], 1
+    ],
+    [
+        "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4",
+        [["Quebec Test", "demo key", "quebec@example.net"]], 1
+    ],
+    [
+        "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA",
+        [["Romeo Test", "demo key", "romeo@example.net"]], 1
+    ],
+    [
+        "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4",
+        [["Sierra Test", "demo key", "sierra@example.net"]], 1
+    ],
+    [
+        "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402",
+        [["Tango Test", "demo key", "tango@example.net"]], 1
+    ],
+    [
+        "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9",
+        [["Uniform Test", "demo key", "uniform@example.net"]], 1
+    ],
+    [
+        "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134",
+        [["Victor Test", "demo key", "victor@example.org"]], 1
+    ],
+    [
+        "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6",
+        [["Whisky Test", "demo key", "whisky@example.net"]], 3, check_whisky
+    ],
+    [
+        "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE",
+        [["XRay Test", "demo key", "xray@example.net"]], 1
+    ],
+    [
+        "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD",
+        [["Yankee Test", "demo key", "yankee@example.net"]], 1
+    ],
+    [
+        "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881",
+        [["Zulu Test", "demo key", "zulu@example.net"]], 1
+    ],
 ]
 
+
 def check_global(key, uids, n_subkeys):
     assert not key.revoked, "Key unexpectedly revoked"
     assert not key.expired, "Key unexpectedly expired"
@@ -145,10 +200,11 @@ def check_subkey(fpr, which, subkey):
     assert not subkey.secret, which + " key unexpectedly secret"
     assert not subkey.is_cardkey, "Public key marked as card key"
     assert not subkey.card_number, "Public key with card number set"
-    assert not subkey.pubkey_algo != (gpg.constants.pk.DSA if which == "Primary"
-                                      else gpg.constants.pk.ELG_E), \
-        which + " key has unexpected public key algo: {}".\
-            format(subkey.pubkey_algo)
+    assert not subkey.pubkey_algo != \
+        (gpg.constants.pk.DSA if which == "Primary"
+         else gpg.constants.pk.ELG_E), \
+         which + " key has unexpected public key algo: {}".format(subkey.
+                                                                  pubkey_algo)
     assert subkey.length == 1024, \
         which + " key has unexpected length: {}".format(subkey.length)
     assert fpr.endswith(subkey.keyid), \
@@ -158,24 +214,26 @@ def check_subkey(fpr, which, subkey):
     assert not subkey.expires, \
         which + " key unexpectedly expires: {}".format(subkey.expires)
 
+
 def check_uid(which, ref, uid):
     assert not uid.revoked, which + " user ID unexpectedly revoked"
     assert not uid.invalid, which + " user ID unexpectedly invalid"
     assert uid.validity == gpg.constants.validity.UNKNOWN, \
-      which + " user ID has unexpected validity: {}".format(uid.validity)
+        which + " user ID has unexpected validity: {}".format(uid.validity)
     assert not uid.signatures, which + " user ID unexpectedly signed"
     assert uid.name == ref[0], \
-      "Unexpected name in {} user ID: {!r}".format(which.lower(), uid.name)
+        "Unexpected name in {} user ID: {!r}".format(which.lower(), uid.name)
     assert uid.comment == ref[1], \
-      "Unexpected comment in {} user ID: {!r}".format(which.lower(),
-                                                      uid.comment)
+        "Unexpected comment in {} user ID: {!r}".format(which.lower(),
+                                                        uid.comment)
     assert uid.email == ref[2], \
-      "Unexpected email in {} user ID: {!r}".format(which.lower(), uid.email)
+        "Unexpected email in {} user ID: {!r}".format(which.lower(), uid.email)
+
 
 # Export all the data from our keyring...
 key_data = gpg.Data()
 with gpg.Context() as c:
-  c.op_export_keys([c.get_key(k[0]) for k in keys], 0, key_data)
+    c.op_export_keys([c.get_key(k[0]) for k in keys], 0, key_data)
 
 # ... rewind the tape...
 key_data.rewind()
@@ -201,11 +259,11 @@ with support.EphemeralContext() as c:
         assert len(key.uids) == len(uids)
         check_uid("First", uids[0], key.uids[0])
         if len(key.uids) > 1:
-          check_uid("Second", uids[1], key.uids[1])
+            check_uid("Second", uids[1], key.uids[1])
         if len(key.uids) > 2:
-          check_uid("Third", uids[2], key.uids[2])
+            check_uid("Third", uids[2], key.uids[2])
 
         if misc_check:
-            misc_check (uids[0][0], key)
+            misc_check(uids[0][0], key)
 
     assert len(list(c.keylist())) == 0, "Keys were imported"
index 4505d3c..9cbada5 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
 c = gpg.Context()
 
+
 # Check expration of keys.  This test assumes three subkeys of which
 # 2 are expired; it is used with the "Whisky" test key.  It has
 # already been checked that these 3 subkeys are available.
 def check_whisky(name, key):
-  sub1 = key.subkeys[2]
-  sub2 = key.subkeys[3]
+    sub1 = key.subkeys[2]
+    sub2 = key.subkeys[3]
+
+    assert sub1.expired and sub2.expired, \
+        "Subkey of `{}' not flagged as expired".format(name)
+    assert sub1.expires == 1129636886 and sub2.expires == 1129636939, \
+        "Subkey of `{}' has wrong expiration date".format(name)
 
-  assert sub1.expired and sub2.expired, \
-      "Subkey of `{}' not flagged as expired".format(name)
-  assert sub1.expires == 1129636886 and sub2.expires == 1129636939, \
-      "Subkey of `{}' has wrong expiration date".format(name)
 
 keys = [
-    [ "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8",
-      [ [ "Alfa Test", "demo key", "alfa@example.net" ],
-        [ "Alpha Test", "demo key", "alpha@example.net" ],
-       [ "Alice", "demo key", "" ] ], 1 ],
-    [ "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F",
-      [ [ "Bob", "demo key", "" ],
-       [ "Bravo Test", "demo key", "bravo@example.net" ] ], 1 ],
-    [ "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60",
-      [ [ "Charlie Test", "demo key", "charlie@example.net" ] ], 1 ],
-    [ "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424",
-      [ [ "Delta Test", "demo key", "delta@example.net" ] ], 1 ],
-    [ "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D",
-      [ [ "Echelon", "demo key", "" ],
-       [ "Echo Test", "demo key", "echo@example.net" ],
-       [ "Eve", "demo key", "" ] ], 1 ],
-    [ "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E",
-      [ [ "Foxtrot Test", "demo key", "foxtrot@example.net" ] ], 1 ],
-    [ "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354",
-      [ [ "Golf Test", "demo key", "golf@example.net" ] ], 1 ],
-    [ "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A",
-      [ [ "Hotel Test", "demo key", "hotel@example.net" ] ], 1 ],
-    [ "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73",
-      [ [ "India Test", "demo key", "india@example.net" ] ], 1 ],
-    [ "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136",
-      [ [ "Juliet Test", "demo key", "juliet@example.net" ] ], 1 ],
-    [ "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02",
-      [ [ "Kilo Test", "demo key", "kilo@example.net" ] ], 1 ],
-    [ "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C",
-      [ [ "Lima Test", "demo key", "lima@example.net" ] ], 1 ],
-    [ "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8",
-      [ [ "Mallory", "demo key", "" ],
-       [ "Mike Test", "demo key", "mike@example.net" ] ], 1 ],
-    [ "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472",
-      [ [ "November Test", "demo key", "november@example.net" ] ], 1 ],
-    [ "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F",
-      [ [ "Oscar Test", "demo key", "oscar@example.net" ] ], 1 ],
-    [ "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C",
-      [ [ "Papa test", "demo key", "papa@example.net" ] ], 1 ],
-    [ "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4",
-      [ [ "Quebec Test", "demo key", "quebec@example.net" ] ], 1 ],
-    [ "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA",
-      [ [ "Romeo Test", "demo key", "romeo@example.net" ] ], 1 ],
-    [ "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4",
-      [ [ "Sierra Test", "demo key", "sierra@example.net" ] ], 1 ],
-    [ "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402",
-      [ [ "Tango Test", "demo key", "tango@example.net" ] ], 1 ],
-    [ "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9",
-      [ [ "Uniform Test", "demo key", "uniform@example.net" ] ], 1 ],
-    [ "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134",
-      [ [ "Victor Test", "demo key", "victor@example.org" ] ], 1 ],
-    [ "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6",
-      [ [ "Whisky Test", "demo key", "whisky@example.net" ] ], 3,
-      check_whisky ],
-    [ "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE",
-      [ [ "XRay Test", "demo key", "xray@example.net" ] ], 1 ],
-    [ "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD",
-      [ [ "Yankee Test", "demo key", "yankee@example.net" ] ], 1 ],
-    [ "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881",
-      [ [ "Zulu Test", "demo key", "zulu@example.net" ] ], 1 ],
+    [
+        "A0FF4590BB6122EDEF6E3C542D727CC768697734", "6AE6D7EE46A871F8",
+        [["Alfa Test", "demo key",
+          "alfa@example.net"], ["Alpha Test", "demo key", "alpha@example.net"],
+         ["Alice", "demo key", ""]], 1
+    ],
+    [
+        "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", "5381EA4EE29BA37F",
+        [["Bob", "demo key", ""],
+         ["Bravo Test", "demo key", "bravo@example.net"]], 1
+    ],
+    [
+        "61EE841A2A27EB983B3B3C26413F4AF31AFDAB6C", "E71E72ACBC43DA60",
+        [["Charlie Test", "demo key", "charlie@example.net"]], 1
+    ],
+    [
+        "6560C59C43D031C54D7C588EEBA9F240EB9DC9E6", "06F22880B0C45424",
+        [["Delta Test", "demo key", "delta@example.net"]], 1
+    ],
+    [
+        "3531152DE293E26A07F504BC318C1FAEFAEF6D1B", "B5C79E1A7272144D",
+        [["Echelon", "demo key",
+          ""], ["Echo Test", "demo key", "echo@example.net"],
+         ["Eve", "demo key", ""]], 1
+    ],
+    [
+        "56D33268F7FE693FBB594762D4BF57F37372E243", "0A32EE79EE45198E",
+        [["Foxtrot Test", "demo key", "foxtrot@example.net"]], 1
+    ],
+    [
+        "C9C07DCC6621B9FB8D071B1D168410A48FC282E6", "247491CC9DCAD354",
+        [["Golf Test", "demo key", "golf@example.net"]], 1
+    ],
+    [
+        "9E91CBB11E4D4135583EF90513DB965534C6E3F1", "76E26537D622AD0A",
+        [["Hotel Test", "demo key", "hotel@example.net"]], 1
+    ],
+    [
+        "CD538D6CC9FB3D745ECDA5201FE8FC6F04259677", "C1C8EFDE61F76C73",
+        [["India Test", "demo key", "india@example.net"]], 1
+    ],
+    [
+        "F8F1EDC73995AB739AD54B380C820C71D2699313", "BD0B108735F8F136",
+        [["Juliet Test", "demo key", "juliet@example.net"]], 1
+    ],
+    [
+        "3FD11083779196C2ECDD9594AD1B0FAD43C2D0C7", "86CBB34A9AF64D02",
+        [["Kilo Test", "demo key", "kilo@example.net"]], 1
+    ],
+    [
+        "1DDD28CEF714F5B03B8C246937CAB51FB79103F8", "0363B449FE56350C",
+        [["Lima Test", "demo key", "lima@example.net"]], 1
+    ],
+    [
+        "2686AA191A278013992C72EBBE794852BE5CF886", "5F600A834F31EAE8",
+        [["Mallory", "demo key", ""],
+         ["Mike Test", "demo key", "mike@example.net"]], 1
+    ],
+    [
+        "5AB9D6D7BAA1C95B3BAA3D9425B00FD430CEC684", "4C1D63308B70E472",
+        [["November Test", "demo key", "november@example.net"]], 1
+    ],
+    [
+        "43929E89F8F79381678CAE515F6356BA6D9732AC", "FF0785712681619F",
+        [["Oscar Test", "demo key", "oscar@example.net"]], 1
+    ],
+    [
+        "6FAA9C201E5E26DCBAEC39FD5D15E01D3FF13206", "2764E18263330D9C",
+        [["Papa test", "demo key", "papa@example.net"]], 1
+    ],
+    [
+        "A7969DA1C3297AA96D49843F1C67EC133C661C84", "6CDCFC44A029ACF4",
+        [["Quebec Test", "demo key", "quebec@example.net"]], 1
+    ],
+    [
+        "38FBE1E4BF6A5E1242C8F6A13BDBEDB1777FBED3", "9FAB805A11D102EA",
+        [["Romeo Test", "demo key", "romeo@example.net"]], 1
+    ],
+    [
+        "045B2334ADD69FC221076841A5E67F7FA3AE3EA1", "93B88B0F0F1B50B4",
+        [["Sierra Test", "demo key", "sierra@example.net"]], 1
+    ],
+    [
+        "ECAC774F4EEEB0620767044A58CB9A4C85A81F38", "97B60E01101C0402",
+        [["Tango Test", "demo key", "tango@example.net"]], 1
+    ],
+    [
+        "0DBCAD3F08843B9557C6C4D4A94C0F75653244D6", "93079B915522BDB9",
+        [["Uniform Test", "demo key", "uniform@example.net"]], 1
+    ],
+    [
+        "E8143C489C8D41124DC40D0B47AF4B6961F04784", "04071FB807287134",
+        [["Victor Test", "demo key", "victor@example.org"]], 1
+    ],
+    [
+        "E8D6C90B683B0982BD557A99DEF0F7B8EC67DBDE", "D7FBB421FD6E27F6",
+        [["Whisky Test", "demo key", "whisky@example.net"]], 3, check_whisky
+    ],
+    [
+        "04C1DF62EFA0EBB00519B06A8979A6C5567FB34A", "5CC6F87F41E408BE",
+        [["XRay Test", "demo key", "xray@example.net"]], 1
+    ],
+    [
+        "ED9B316F78644A58D042655A9EEF34CD4B11B25F", "5ADFD255F7B080AD",
+        [["Yankee Test", "demo key", "yankee@example.net"]], 1
+    ],
+    [
+        "23FD347A419429BACCD5E72D6BC4778054ACD246", "EF9DC276A172C881",
+        [["Zulu Test", "demo key", "zulu@example.net"]], 1
+    ],
 ]
 
+
 def check_global(key, uids, n_subkeys):
     assert not key.revoked, "Key unexpectedly revoked"
     assert not key.expired, "Key unexpectedly expired"
@@ -117,12 +172,12 @@ def check_global(key, uids, n_subkeys):
         "Key unexpectedly carries chain ID: {}".format(key.chain_id)
 
     # Only key Alfa is trusted
-    assert key.uids[0].name == 'Alfa Test' \
-      or key.owner_trust == gpg.constants.validity.UNKNOWN, \
-        "Key has unexpected owner trust: {}".format(key.owner_trust)
-    assert key.uids[0].name != 'Alfa Test' \
-      or key.owner_trust == gpg.constants.validity.ULTIMATE, \
-        "Key has unexpected owner trust: {}".format(key.owner_trust)
+    assert (key.uids[0].name == 'Alfa Test'
+            or key.owner_trust == gpg.constants.validity.UNKNOWN), \
+            "Key has unexpected owner trust: {}".format(key.owner_trust)
+    assert (key.uids[0].name != 'Alfa Test'
+            or key.owner_trust == gpg.constants.validity.ULTIMATE), \
+            "Key has unexpected owner trust: {}".format(key.owner_trust)
 
     assert len(key.subkeys) - 1 == n_subkeys, \
         "Key `{}' has unexpected number of subkeys".format(uids[0][0])
@@ -152,10 +207,11 @@ def check_subkey(fpr, which, subkey):
     assert not subkey.secret, which + " key unexpectedly secret"
     assert not subkey.is_cardkey, "Public key marked as card key"
     assert not subkey.card_number, "Public key with card number set"
-    assert not subkey.pubkey_algo != (gpg.constants.pk.DSA if which == "Primary"
-                                      else gpg.constants.pk.ELG_E), \
-        which + " key has unexpected public key algo: {}".\
-            format(subkey.pubkey_algo)
+    assert not subkey.pubkey_algo != \
+        (gpg.constants.pk.DSA if which == "Primary"
+         else gpg.constants.pk.ELG_E), \
+        which + " key has unexpected public key algo: {}".format(subkey.
+                                                             pubkey_algo)
     assert subkey.length == 1024, \
         which + " key has unexpected length: {}".format(subkey.length)
     assert fpr.endswith(subkey.keyid), \
@@ -165,6 +221,7 @@ def check_subkey(fpr, which, subkey):
     assert not subkey.expires, \
         which + " key unexpectedly expires: {}".format(subkey.expires)
 
+
 def check_uid(which, ref, uid):
     assert not uid.revoked, which + " user ID unexpectedly revoked"
     assert not uid.invalid, which + " user ID unexpectedly invalid"
@@ -172,19 +229,20 @@ def check_uid(which, ref, uid):
                             if uid.name.split()[0]
                             not in {'Alfa', 'Alpha', 'Alice'} else
                             gpg.constants.validity.ULTIMATE), \
-      which + " user ID has unexpectedly validity: {}".format(uid.validity)
+        which + " user ID has unexpectedly validity: {}".format(uid.validity)
     assert not uid.signatures, which + " user ID unexpectedly signed"
     assert uid.name == ref[0], \
-      "Unexpected name in {} user ID: {!r}".format(which.lower(), uid.name)
+        "Unexpected name in {} user ID: {!r}".format(which.lower(), uid.name)
     assert uid.comment == ref[1], \
-      "Unexpected comment in {} user ID: {!r}".format(which.lower(),
-                                                      uid.comment)
+        "Unexpected comment in {} user ID: {!r}".\
+        format(which.lower(), uid.comment)
     assert uid.email == ref[2], \
-      "Unexpected email in {} user ID: {!r}".format(which.lower(), uid.email)
+        "Unexpected email in {} user ID: {!r}".format(which.lower(), uid.email)
+
 
 i = 0
 c.op_keylist_start(None, False)
-key = c.op_keylist_next ()
+key = c.op_keylist_next()
 while key:
     try:
         if len(keys[i]) == 4:
@@ -204,20 +262,19 @@ while key:
     assert len(key.uids) == len(uids)
     check_uid("First", uids[0], key.uids[0])
     if len(key.uids) > 1:
-      check_uid("Second", uids[1], key.uids[1])
+        check_uid("Second", uids[1], key.uids[1])
     if len(key.uids) > 2:
-      check_uid("Third", uids[2], key.uids[2])
+        check_uid("Third", uids[2], key.uids[2])
 
     if misc_check:
-        misc_check (uids[0][0], key)
-    key = c.op_keylist_next ()
+        misc_check(uids[0][0], key)
+    key = c.op_keylist_next()
     i += 1
 
 c.op_keylist_end()
 result = c.op_keylist_result()
 assert not result.truncated, "Key listing unexpectedly truncated"
 
-
 # We test for a parameter-less keylist
 keyring_length = len(list(c.op_keylist_all()))
 assert keyring_length > 1,\
@@ -226,13 +283,12 @@ assert keyring_length > 1,\
 # Then we do want to call with a pattern, only
 # i.e. without giving secret=0
 alpha_keys = list(c.op_keylist_all(b"Alpha"))
-assert len(alpha_keys) == 1, "Expected only one key for 'Alpha', got %r" % len(alpha_keys)
-
+assert len(alpha_keys) == 1, "Expected only one key for 'Alpha', got %r" % len(
+    alpha_keys)
 
 # Check negative result.
 assert len(list(c.keylist("no such key in sight"))) == 0
 
-
 for i, key in enumerate(c.keylist()):
     try:
         if len(keys[i]) == 4:
@@ -252,31 +308,30 @@ for i, key in enumerate(c.keylist()):
     assert len(key.uids) == len(uids)
     check_uid("First", uids[0], key.uids[0])
     if len(key.uids) > 1:
-      check_uid("Second", uids[1], key.uids[1])
+        check_uid("Second", uids[1], key.uids[1])
     if len(key.uids) > 2:
-      check_uid("Third", uids[2], key.uids[2])
+        check_uid("Third", uids[2], key.uids[2])
 
     if misc_check:
-        misc_check (uids[0][0], key)
-
+        misc_check(uids[0][0], key)
 
 # check get_key()
 with gpg.Context() as c:
-  c.get_key(support.alpha)
-  c.get_key(support.alpha, secret=True)
-
-  c.get_key(support.bob)
-  try:
-    c.get_key(support.bob, secret=True)
-  except KeyError:
-    pass
-  else:
-    assert False, "Expected KeyError"
-
-  # Legacy error
-  try:
-    c.get_key(support.no_such_key)
-  except gpg.errors.GPGMEError:
-    pass
-  else:
-    assert False, "Expected GPGMEError"
+    c.get_key(support.alpha)
+    c.get_key(support.alpha, secret=True)
+
+    c.get_key(support.bob)
+    try:
+        c.get_key(support.bob, secret=True)
+    except KeyError:
+        pass
+    else:
+        assert False, "Expected KeyError"
+
+    # Legacy error
+    try:
+        c.get_key(support.no_such_key)
+    except gpg.errors.GPGMEError:
+        pass
+    else:
+        assert False, "Expected GPGMEError"
index 8da5035..c337c3b 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:
     # Do nothing.
     err = c.assuan_transact('nop')
-    assert err == None
+    assert err is None
     err = c.assuan_transact(b'NOP')
-    assert err == None
+    assert err is None
     err = c.assuan_transact(['NOP'])
-    assert err == None
+    assert err is None
 
     err = c.assuan_transact('idontexist')
     assert err.getsource() == gpg.errors.SOURCE_GPGAGENT
@@ -41,6 +42,7 @@ with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:
     c.assuan_transact(['GET_CONFIRMATION', 'Hello there'])
 
     data = []
+
     def data_cb(line):
         data.append(line)
 
@@ -57,6 +59,7 @@ with gpg.Context(protocol=gpg.constants.protocol.ASSUAN) as c:
     # XXX HELP sends status lines if we could use ASSUAN_CONVEY_COMMENTS.
 
     status = []
+
     def status_cb(line, args):
         status.append((line, args))
 
index 8b7372e..b3303ff 100755 (executable)
@@ -18,7 +18,6 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import itertools
@@ -27,6 +26,8 @@ import time
 import support
 support.assert_gpg_version((2, 1, 2))
 
+del absolute_import, print_function, unicode_literals
+
 alpha = "Alpha <alpha@invalid.example.net>"
 
 with support.EphemeralContext() as ctx:
@@ -51,14 +52,16 @@ with support.EphemeralContext() as ctx:
     res2 = ctx.create_key(alpha, force=True)
     assert res.fpr != res2.fpr
 
-
 # From here on, we use one context, and create unique UIDs
 uid_counter = 0
+
+
 def make_uid():
     global uid_counter
     uid_counter += 1
     return "user{0}@invalid.example.org".format(uid_counter)
 
+
 with support.EphemeralContext() as ctx:
     # Check gpg.constants.create.NOEXPIRE...
     res = ctx.create_key(make_uid(), expires=False)
@@ -77,10 +80,11 @@ with support.EphemeralContext() as ctx:
         "Primary keys expiration time is off"
 
     # Check capabilities
-    for sign, encrypt, certify, authenticate in itertools.product([False, True],
-                                                                  [False, True],
-                                                                  [False, True],
-                                                                  [False, True]):
+    for sign, encrypt, certify, authenticate \
+            in itertools.product([False, True],
+                                 [False, True],
+                                 [False, True],
+                                 [False, True]):
         # Filter some out
         if not (sign or encrypt or certify or authenticate):
             # This triggers the default capabilities tested before.
@@ -89,9 +93,13 @@ with support.EphemeralContext() as ctx:
             # The primary key always certifies.
             continue
 
-        res = ctx.create_key(make_uid(), algorithm="rsa",
-                             sign=sign, encrypt=encrypt, certify=certify,
-                             authenticate=authenticate)
+        res = ctx.create_key(
+            make_uid(),
+            algorithm="rsa",
+            sign=sign,
+            encrypt=encrypt,
+            certify=certify,
+            authenticate=authenticate)
         key = ctx.get_key(res.fpr, secret=True)
         assert key.fpr == res.fpr
         assert len(key.subkeys) == 1, \
@@ -125,13 +133,16 @@ with support.EphemeralContext() as ctx:
     recipient = make_uid()
     passphrase = "streng geheim"
     res = ctx.create_key(recipient, passphrase=passphrase)
-    ciphertext, _, _ = ctx.encrypt(b"hello there", recipients=[ctx.get_key(res.fpr)])
+    ciphertext, _, _ = ctx.encrypt(
+        b"hello there", recipients=[ctx.get_key(res.fpr)])
 
     cb_called = False
+
     def cb(*args):
         global cb_called
         cb_called = True
         return passphrase
+
     ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK
     ctx.set_passphrase_cb(cb)
 
index 37e05b3..ade171e 100755 (executable)
@@ -18,7 +18,6 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import gpg
@@ -27,6 +26,8 @@ import sys
 import support
 support.assert_gpg_version((2, 1, 14))
 
+del absolute_import, print_function, unicode_literals
+
 alpha = "Alpha <alpha@invalid.example.net>"
 bravo = "Bravo <bravo@invalid.example.net>"
 
@@ -111,9 +112,11 @@ with support.EphemeralContext() as ctx:
 
         ctx.key_tofu_policy(key, policy)
 
-        keys = list(ctx.keylist(key.uids[0].uid,
-                                mode=(gpg.constants.keylist.mode.LOCAL
-                                      |gpg.constants.keylist.mode.WITH_TOFU)))
+        keys = list(
+            ctx.keylist(
+                key.uids[0].uid,
+                mode=(gpg.constants.keylist.mode.LOCAL |
+                      gpg.constants.keylist.mode.WITH_TOFU)))
         assert len(keys) == 1
 
         if policy == gpg.constants.tofu.policy.AUTO:
index 3d648c5..6f9b8a7 100755 (executable)
@@ -18,7 +18,6 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import itertools
@@ -27,8 +26,11 @@ import time
 import support
 support.assert_gpg_version((2, 1, 1))
 
+del absolute_import, print_function, unicode_literals
+
 with support.EphemeralContext() as ctx:
     uid_counter = 0
+
     def make_uid():
         global uid_counter
         uid_counter += 1
@@ -43,10 +45,16 @@ with support.EphemeralContext() as ctx:
         return key, uids
 
     def check_sigs(key, expected_sigs):
-        keys = list(ctx.keylist(key.fpr, mode=(gpg.constants.keylist.mode.LOCAL
-                                               |gpg.constants.keylist.mode.SIGS)))
+        keys = list(
+            ctx.keylist(
+                key.fpr,
+                mode=(gpg.constants.keylist.mode.LOCAL |
+                      gpg.constants.keylist.mode.SIGS)))
         assert len(keys) == 1
-        key_uids = {uid.uid: [s for s in uid.signatures] for uid in keys[0].uids}
+        key_uids = {
+            uid.uid: [s for s in uid.signatures]
+            for uid in keys[0].uids
+        }
         expected = list(expected_sigs)
 
         while key_uids and expected:
@@ -76,9 +84,12 @@ with support.EphemeralContext() as ctx:
         assert s.exportable
         assert s.expires == 0
 
-    check_sigs(key_b, itertools.product(uids_b, [key_b], [exportable_non_expiring]))
+    check_sigs(key_b,
+               itertools.product(uids_b, [key_b], [exportable_non_expiring]))
     ctx.key_sign(key_b)
-    check_sigs(key_b, itertools.product(uids_b, [key_b, key_a], [exportable_non_expiring]))
+    check_sigs(
+        key_b,
+        itertools.product(uids_b, [key_b, key_a], [exportable_non_expiring]))
 
     # Create a non-exportable signature, and explicitly name all uids.
     key_c, uids_c = make_key()
@@ -89,11 +100,12 @@ with support.EphemeralContext() as ctx:
         assert s.expires == 0
 
     ctx.key_sign(key_c, local=True, uids=uids_c)
-    check_sigs(key_c,
-               list(itertools.product(uids_c, [key_c],
-                                      [exportable_non_expiring]))
-               + list(itertools.product(uids_c, [key_b, key_a],
-                                        [non_exportable_non_expiring])))
+    check_sigs(
+        key_c,
+        list(itertools.product(uids_c, [key_c], [exportable_non_expiring])) +
+        list(
+            itertools.product(uids_c, [key_b, key_a],
+                              [non_exportable_non_expiring])))
 
     # Create a non-exportable, expiring signature for a single uid.
     key_d, uids_d = make_key()
@@ -106,16 +118,16 @@ with support.EphemeralContext() as ctx:
         assert abs(time.time() + expires_in - s.expires) < slack
 
     ctx.key_sign(key_d, local=True, expires_in=expires_in, uids=uids_d[0])
-    check_sigs(key_d,
-               list(itertools.product(uids_d, [key_d],
-                                      [exportable_non_expiring]))
-               + list(itertools.product(uids_d[:1], [key_c],
-                                        [non_exportable_expiring])))
+    check_sigs(
+        key_d,
+        list(itertools.product(uids_d, [key_d], [exportable_non_expiring])) +
+        list(
+            itertools.product(uids_d[:1], [key_c], [non_exportable_expiring])))
 
     # Now sign the second in the same fashion, but use a singleton list.
     ctx.key_sign(key_d, local=True, expires_in=expires_in, uids=uids_d[1:2])
-    check_sigs(key_d,
-               list(itertools.product(uids_d, [key_d],
-                                      [exportable_non_expiring]))
-               + list(itertools.product(uids_d[:2], [key_c],
-                                        [non_exportable_expiring])))
+    check_sigs(
+        key_d,
+        list(itertools.product(uids_d, [key_d], [exportable_non_expiring])) +
+        list(
+            itertools.product(uids_d[:2], [key_c], [non_exportable_expiring])))
index ad4f35c..cdbb71b 100755 (executable)
@@ -18,7 +18,6 @@
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import itertools
@@ -26,6 +25,8 @@ import time
 
 import support
 
+del absolute_import, print_function, unicode_literals
+
 alpha = "Alpha <alpha@invalid.example.net>"
 bravo = "Bravo <bravo@invalid.example.net>"
 
@@ -59,16 +60,17 @@ with support.EphemeralContext() as ctx:
         "subkeys expiration time is off"
 
     # Check capabilities
-    for sign, encrypt, authenticate in itertools.product([False, True],
-                                                         [False, True],
-                                                         [False, True]):
+    for sign, encrypt, authenticate \
+            in itertools.product([False, True],
+                                 [False, True],
+                                 [False, True]):
         # Filter some out
         if not (sign or encrypt or authenticate):
             # This triggers the default capabilities tested before.
             continue
 
-        res = ctx.create_subkey(key, sign=sign, encrypt=encrypt,
-                                authenticate=authenticate)
+        res = ctx.create_subkey(
+            key, sign=sign, encrypt=encrypt, authenticate=authenticate)
         subkey = get_subkey(res.fpr)
         assert sign == subkey.can_sign
         assert encrypt == subkey.can_encrypt
@@ -92,18 +94,21 @@ with support.EphemeralContext() as ctx:
     # so that we have a key with just one encryption subkey.
     bravo_res = ctx.create_key(bravo, certify=True)
     bravo_key = ctx.get_key(bravo_res.fpr)
-    assert len(bravo_key.subkeys) == 1, "Expected one primary key and no subkeys"
+    assert len(
+        bravo_key.subkeys) == 1, "Expected one primary key and no subkeys"
 
     passphrase = "streng geheim"
     res = ctx.create_subkey(bravo_key, passphrase=passphrase)
-    ciphertext, _, _ = ctx.encrypt(b"hello there",
-                                   recipients=[ctx.get_key(bravo_res.fpr)])
+    ciphertext, _, _ = ctx.encrypt(
+        b"hello there", recipients=[ctx.get_key(bravo_res.fpr)])
 
     cb_called = False
+
     def cb(*args):
         global cb_called
         cb_called = True
         return passphrase
+
     ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK
     ctx.set_passphrase_cb(cb)
 
index bc8da2e..5960f44 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 expected_notations = {
-    "laughing@me": ("Just Squeeze Me", gpg.constants.sig.notation.HUMAN_READABLE),
-    "preferred-email-encoding@pgp.com": ("pgpmime",
-                                         gpg.constants.sig.notation.HUMAN_READABLE
-                                         | gpg.constants.sig.notation.CRITICAL),
+    "laughing@me": ("Just Squeeze Me",
+                    gpg.constants.sig.notation.HUMAN_READABLE),
+    "preferred-email-encoding@pgp.com":
+    ("pgpmime", gpg.constants.sig.notation.HUMAN_READABLE |
+     gpg.constants.sig.notation.CRITICAL),
     None: ("http://www.gnu.org/policy/", 0),
 }
 
 # GnuPG prior to 2.1.13 did not report the critical flag correctly.
 with gpg.Context() as c:
     version = c.engine_info.version
-    have_correct_sig_data = not (version.startswith("1.")
-                                 or version.startswith("2.0.")
-                                 or version == "2.1.1"
-                                 or (version.startswith("2.1.1")
-                                     and version[5] < '3'))
+    have_correct_sig_data = not (
+        version.startswith("1.") or version.startswith("2.0.") or
+        (version.startswith("2.1.") and int(version[4:]) < 13))
+
 
 def check_result(result):
     assert len(result.signatures) == 1, "Unexpected number of signatures"
@@ -48,8 +49,8 @@ def check_result(result):
     assert len(sig.notations) == len(expected_notations)
 
     for r in sig.notations:
-        assert not 'name_len' in dir(r)
-        assert not 'value_len' in dir(r)
+        assert 'name_len' not in dir(r)
+        assert 'value_len' not in dir(r)
         assert r.name in expected_notations
         value, flags = expected_notations.pop(r.name)
 
@@ -63,6 +64,7 @@ def check_result(result):
 
     assert len(expected_notations) == 0
 
+
 source = gpg.Data("Hallo Leute\n")
 signed = gpg.Data()
 
index d375729..3ad05e8 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import os
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
+
 def fail(msg):
     raise RuntimeError(msg)
 
+
 def check_result(r, typ):
     if r.invalid_signers:
         fail("Invalid signer found: {}".format(r.invalid_signers.fpr))
@@ -43,16 +46,15 @@ def check_result(r, typ):
             signature.pubkey_algo))
 
     if signature.hash_algo != gpg.constants.md.SHA1:
-        fail("Wrong hash algorithm reported: {}".format(
-            signature.hash_algo))
+        fail("Wrong hash algorithm reported: {}".format(signature.hash_algo))
 
     if signature.sig_class != 1:
-        fail("Wrong signature class reported: {}".format(
-            signature.sig_class))
+        fail("Wrong signature class reported: {}".format(signature.sig_class))
 
     if signature.fpr != "A0FF4590BB6122EDEF6E3C542D727CC768697734":
         fail("Wrong fingerprint reported: {}".format(signature.fpr))
 
+
 c = gpg.Context()
 c.set_textmode(True)
 c.set_armor(True)
index 5864ee5..119ab77 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
 
+del absolute_import, print_function, unicode_literals
+
+
 def fail(msg):
     raise RuntimeError(msg)
 
+
 def check_result(r, typ):
     if r.invalid_signers:
         fail("Invalid signer found: {}".format(r.invalid_signers.fpr))
@@ -53,6 +56,7 @@ def check_result(r, typ):
                                  "23FD347A419429BACCD5E72D6BC4778054ACD246"):
             fail("Wrong fingerprint reported: {}".format(signature.fpr))
 
+
 c = gpg.Context()
 c.set_textmode(True)
 c.set_armor(True)
index 89524bb..ffa0b96 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 c = gpg.Context()
 
+
 def dump_item(item):
-    print("l={} k={} t={} o={} v={} u={}".format(
-        item.level, item.keyid, item.type, item.owner_trust,
-        item.validity, item.name))
+    print("l={} k={} t={} o={} v={} u={}".format(item.level, item.keyid,
+                                                 item.type, item.owner_trust,
+                                                 item.validity, item.name))
+
 
 c.op_trustlist_start("alice", 0)
 while True:
index 320dae6..70a6c1c 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import sys
 import os
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 test_text1 = b"Just GNU it!\n"
-test_text1f= b"Just GNU it?\n"
+test_text1f = b"Just GNU it?\n"
 test_sig1 = b"""-----BEGIN PGP SIGNATURE-----
 
 iN0EABECAJ0FAjoS+i9FFIAAAAAAAwA5YmFyw7bDpMO8w58gZGFzIHdhcmVuIFVt
@@ -60,6 +61,7 @@ UqVooWlGXHwNw/xg/fVzt9VNbtjtJ/fhUqYo0/LyCGEA
 -----END PGP MESSAGE-----
 """
 
+
 def check_result(result, summary, validity, fpr, status, notation):
     assert len(result.signatures) == 1, "Unexpected number of signatures"
     sig = result.signatures[0]
@@ -76,14 +78,16 @@ def check_result(result, summary, validity, fpr, status, notation):
                     if sys.version_info[0] < 3 else
                     b"\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f".decode() +
                     " das waren Umlaute und jetzt ein prozent%-Zeichen"),
-            "foobar.1":  "this is a notation data with 2 lines",
-            None: "http://www.gu.org/policy/",
+            "foobar.1":
+            "this is a notation data with 2 lines",
+            None:
+            "http://www.gu.org/policy/",
         }
         assert len(sig.notations) == len(expected_notations)
 
         for r in sig.notations:
-            assert not 'name_len' in dir(r)
-            assert not 'value_len' in dir(r)
+            assert 'name_len' not in dir(r)
+            assert 'value_len' not in dir(r)
             assert r.name in expected_notations
             assert r.value == expected_notations[r.name], \
                 "Expected {!r}, got {!r}".format(expected_notations[r.name],
@@ -96,7 +100,9 @@ def check_result(result, summary, validity, fpr, status, notation):
     assert sig.validity == validity, \
         "Unexpected signature validity: {}, want: {}".format(
             sig.validity, validity)
-    assert gpg.errors.GPGMEError(sig.validity_reason).getcode() == gpg.errors.NO_ERROR
+    assert gpg.errors.GPGMEError(
+        sig.validity_reason).getcode() == gpg.errors.NO_ERROR
+
 
 c = gpg.Context()
 c.set_armor(True)
@@ -108,9 +114,8 @@ c.op_verify(sig, text, None)
 result = c.op_verify_result()
 check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
              gpg.constants.validity.FULL,
-             "A0FF4590BB6122EDEF6E3C542D727CC768697734",
-             gpg.errors.NO_ERROR, True)
-
+             "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR,
+             True)
 
 # Checking a manipulated message.
 text = gpg.Data(test_text1f)
@@ -127,8 +132,8 @@ c.op_verify(sig, None, text)
 result = c.op_verify_result()
 check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
              gpg.constants.validity.FULL,
-             "A0FF4590BB6122EDEF6E3C542D727CC768697734",
-             gpg.errors.NO_ERROR, False)
+             "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR,
+             False)
 
 # Checking an invalid message.
 text = gpg.Data()
@@ -141,33 +146,32 @@ except Exception as e:
 else:
     assert False, "Expected an error but got none."
 
-
 # Idiomatic interface.
 with gpg.Context(armor=True) as c:
     # Checking a valid message.
     _, result = c.verify(test_text1, test_sig1)
-    check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
-                 gpg.constants.validity.FULL,
-                 "A0FF4590BB6122EDEF6E3C542D727CC768697734",
-                 gpg.errors.NO_ERROR, True)
+    check_result(
+        result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
+        gpg.constants.validity.FULL,
+        "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR, True)
 
     # Checking a manipulated message.
     try:
         c.verify(test_text1f, test_sig1)
     except gpg.errors.BadSignatures as e:
         check_result(e.result, gpg.constants.sigsum.RED,
-                     gpg.constants.validity.UNKNOWN,
-                     "2D727CC768697734", gpg.errors.BAD_SIGNATURE, False)
+                     gpg.constants.validity.UNKNOWN, "2D727CC768697734",
+                     gpg.errors.BAD_SIGNATURE, False)
     else:
         assert False, "Expected an error but got none."
 
     # Checking a normal signature.
     sig = gpg.Data(test_sig2)
     data, result = c.verify(test_sig2)
-    check_result(result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
-                 gpg.constants.validity.FULL,
-                 "A0FF4590BB6122EDEF6E3C542D727CC768697734",
-                 gpg.errors.NO_ERROR, False)
+    check_result(
+        result, gpg.constants.sigsum.VALID | gpg.constants.sigsum.GREEN,
+        gpg.constants.validity.FULL,
+        "A0FF4590BB6122EDEF6E3C542D727CC768697734", gpg.errors.NO_ERROR, False)
     assert data == test_text1
 
     # Checking an invalid message.
index 3101301..907f450 100755 (executable)
 # License along with this program; if not, see <http://www.gnu.org/licenses/>.
 
 from __future__ import absolute_import, print_function, unicode_literals
-del absolute_import, print_function, unicode_literals
 
 import time
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
+
+del absolute_import, print_function, unicode_literals
 
 c = gpg.Context()
 c.set_armor(True)
index 08a320d..020e71e 100755 (executable)
@@ -19,9 +19,9 @@
 
 import gpg
 import support
-_ = support # to appease pyflakes.
+_ = support  # to appease pyflakes.
 
 d0 = gpg.Data()
-d0.seek # trigger on-demand-wrapping
+d0.seek  # trigger on-demand-wrapping
 assert d0.seek == d0.seek, "Generated wrapper functions are not cached"
 assert hasattr(gpg.Data, 'seek'), "Generated wrapper functions are not shared"
index 1a1baf0..ad76eda 100644 (file)
@@ -1,4 +1,6 @@
-# Copyright (C) 2016 g10 Code GmbH
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2018 g10 Code GmbH
 # Copyright (C) 2015 Ben McGinnes <ben@adversary.org>
 # Copyright (C) 2004 Igor Belyi <belyi@users.sourceforge.net>
 #
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
 from __future__ import absolute_import, print_function
-del absolute_import, print_function
 
 from . import gpgme
 
+del absolute_import, print_function
+
 productname = 'gpg'
 versionstr = "@VERSION@"
 gpgme_versionstr = gpgme.GPGME_VERSION
@@ -32,8 +35,8 @@ minor = versionlist[1]
 patch = versionlist[2]
 
 copyright = """\
-Copyright (C) 2016 g10 Code GmbH
-Copyright (C) 2015 Ben McGinnes
+Copyright (C) 2016-2018 g10 Code GmbH
+Copyright (C) 2015 Benjamin D. McGinnes
 Copyright (C) 2014-2015 Martin Albrecht
 Copyright (C) 2004-2008 Igor Belyi
 Copyright (C) 2002 John Goerzen"""
@@ -44,8 +47,8 @@ author_email = "gnupg-devel@gnupg.org"
 description = "Python support for GPGME GnuPG cryptography library"
 homepage = "https://gnupg.org"
 
-license = """Copyright (C) 2016 g10 Code GmbH
-Copyright (C) 2015 Ben McGinnes <ben@adversary.org>
+license = """Copyright (C) 2016-2018 g10 Code GmbH
+Copyright (C) 2015 Benjamin D. McGinnes <ben@adversary.org>
 Copyright (C) 2014, 2015 Martin Albrecht <martinralbrecht@googlemail.com>
 Copyright (C) 2004, 2008 Igor Belyi <belyi@users.sourceforge.net>
 Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
index ab85960..a1b83e8 100644 (file)
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 # 02111-1307, USA
 
-SUBDIRS = src tests doc
+if RUN_GPG_TESTS
+tests = tests
+else
+tests =
+endif
+
+SUBDIRS = src ${tests} doc
 
 EXTRA_DIST = README
index c17e359..e979cfe 100644 (file)
@@ -116,7 +116,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
@@ -173,7 +173,7 @@ am__define_uniq_tagged_files = \
   done | $(am__uniquify_input)`
 ETAGS = etags
 CTAGS = ctags
-DIST_SUBDIRS = $(SUBDIRS)
+DIST_SUBDIRS = src tests doc
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 am__relativize = \
   dir0=`pwd`; \
@@ -394,7 +394,9 @@ target_alias = @target_alias@
 top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
-SUBDIRS = src tests doc
+@RUN_GPG_TESTS_FALSE@tests = 
+@RUN_GPG_TESTS_TRUE@tests = tests
+SUBDIRS = src ${tests} doc
 EXTRA_DIST = README
 all: all-recursive
 
index 3b2b494..2dcd41b 100644 (file)
@@ -114,7 +114,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES = Doxyfile
 CONFIG_CLEAN_VPATH_FILES =
 AM_V_P = $(am__v_P_@AM_V@)
index c35e288..9290424 100644 (file)
@@ -102,7 +102,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES = QGpgmeConfig-w32.cmake.in QGpgmeConfig.cmake.in \
        QGpgmeConfigVersion.cmake qgpgme_version.h
 CONFIG_CLEAN_VPATH_FILES =
@@ -181,7 +181,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
index ba028a9..070ab69 100644 (file)
@@ -42,6 +42,7 @@
 #include "gpgme_backend_debug.h"
 
 #include <QFile>
+#include <QDir>
 
 #include "global.h"
 #include "error.h"
@@ -521,8 +522,7 @@ QUrl QGpgMENewCryptoConfigEntry::urlValue() const
     Q_ASSERT(type == FilenameType || type == LdapServerType);
     Q_ASSERT(!isList());
     if (type == FilenameType) {
-        QUrl url;
-        url.setPath(QFile::decodeName(m_option.currentValue().stringValue()));
+        QUrl url = QUrl::fromLocalFile(m_option.currentValue().stringValue());
         return url;
     }
     return parseURL(type, stringValue());
@@ -635,7 +635,7 @@ void QGpgMENewCryptoConfigEntry::setURLValue(const QUrl &url)
     if (str.isEmpty() && !isOptional()) {
         m_option.resetToDefaultValue();
     } else if (type == FilenameType) {
-        m_option.setNewValue(m_option.createStringArgument(QFile::encodeName(str).constData()));
+        m_option.setNewValue(m_option.createStringArgument(QDir::toNativeSeparators(url.toLocalFile()).toUtf8().constData()));
     } else {
         m_option.setNewValue(m_option.createStringArgument(str.toUtf8().constData()));
     }
index 74755c5..cd7c494 100644 (file)
 using namespace QGpgME;
 using namespace GpgME;
 
-static const unsigned int GetAuditLogFlags = Context::AuditLogWithHelp | Context::HtmlAuditLog;
+#ifdef Q_OS_WIN
+#include <windows.h>
+
+static QString fromEncoding (unsigned int src_encoding, const char *data)
+{
+    int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0);
+    if (n < 0) {
+        return QString();
+    }
+
+    wchar_t *result = (wchar_t *) malloc ((n+1) * sizeof *result);
+
+    n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n);
+    if (n < 0) {
+        free(result);
+        return QString();
+    }
+    const auto ret = QString::fromWCharArray(result, n);
+    free(result);
+    return ret;
+}
+#endif
+
+static QString stringFromGpgOutput(const QByteArray &ba)
+{
+#ifdef Q_OS_WIN
+    /* Qt on Windows uses GetACP while GnuPG prefers
+     * GetConsoleOutputCP.
+     *
+     * As we are not a console application GetConsoleOutputCP
+     * usually returns 0.
+     * From experience the closest thing that let's us guess
+     * what GetConsoleOutputCP returns for a console application
+     * it appears to be the OEMCP.
+     */
+    unsigned int cpno = GetConsoleOutputCP ();
+    if (!cpno) {
+        cpno = GetOEMCP();
+    }
+    if (!cpno) {
+        cpno = GetACP();
+    }
+    if (!cpno) {
+        return QString();
+    }
+
+    return fromEncoding(cpno, ba.constData());
+#else
+    return QString::fromLocal8Bit(ba);
+#endif
+}
+
+static QString markupDiagnostics(const QString &data)
+{
+    // First ensure that we don't have html in the diag.
+    QString ret = QStringLiteral("<pre>%1</pre>").arg(data.toHtmlEscaped());
+
+    return ret;
+}
+
+static const unsigned int CMSAuditLogFlags = Context::AuditLogWithHelp | Context::HtmlAuditLog;
+static const unsigned int OpenPGPAuditLogFlags = Context::DiagnosticAuditLog;
 
 QString _detail::audit_log_as_html(Context *ctx, GpgME::Error &err)
 {
@@ -61,11 +122,24 @@ QString _detail::audit_log_as_html(Context *ctx, GpgME::Error &err)
     QGpgME::QByteArrayDataProvider dp;
     Data data(&dp);
     assert(!data.isNull());
-    if ((err = ctx->lastError()) || (err = ctx->getAuditLog(data, GetAuditLogFlags))) {
-        return QString::fromLocal8Bit(err.asString());
+
+    if (ctx->protocol() == OpenPGP) {
+        if ((err = ctx->getAuditLog(data, OpenPGPAuditLogFlags))) {
+            return QString::fromLocal8Bit(err.asString());
+        }
+        const QByteArray ba = dp.data();
+        return markupDiagnostics(stringFromGpgOutput(ba));
     }
-    const QByteArray ba = dp.data();
-    return QString::fromUtf8(ba.data(), ba.size());
+
+    if (ctx->protocol() == CMS) {
+        if ((err = ctx->lastError()) || (err = ctx->getAuditLog(data, CMSAuditLogFlags))) {
+            return QString::fromLocal8Bit(err.asString());
+        }
+        const QByteArray ba = dp.data();
+        return QString::fromUtf8(ba.data(), ba.size());
+    }
+
+    return QStringLiteral("Unsupported protocol for Audit Log");
 }
 
 static QList<QByteArray> from_sl(const QStringList &sl)
index 104672e..08b1c4c 100644 (file)
@@ -21,7 +21,8 @@
 
 GPG = gpg
 
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir)
+GNUPGHOME=$(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME)
 
 EXTRA_DIST = initial.test
 
@@ -67,7 +68,7 @@ noinst_PROGRAMS = t-keylist t-keylocate t-ownertrust t-tofuinfo t-encrypt \
 CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \
        gpg-agent.conf pubring.kbx~ S.gpg-agent gpg.conf pubring.gpg~ \
        random_seed S.gpg-agent .gpg-v21-migrated pubring-stamp $(moc_files) \
-       gpg.conf tofu.db
+       gpg.conf tofu.db reader_0.status reader_1.status
 
 clean-local:
        -$(TESTS_ENVIRONMENT) $(top_srcdir)/tests/start-stop-agent --stop
index 075747f..1546836 100644 (file)
@@ -123,7 +123,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 PROGRAMS = $(noinst_PROGRAMS)
@@ -197,7 +197,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
@@ -490,7 +490,8 @@ top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 GPG = gpg
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir)
+GNUPGHOME = $(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME)
 EXTRA_DIST = initial.test
 moc_files = t-keylist.moc t-keylocate.moc t-ownertrust.moc t-tofuinfo.moc \
             t-encrypt.moc t-support.hmoc t-wkspublish.moc t-verify.moc \
@@ -523,7 +524,7 @@ BUILT_SOURCES = $(moc_files) pubring-stamp
 CLEANFILES = secring.gpg pubring.gpg pubring.kbx trustdb.gpg dirmngr.conf \
        gpg-agent.conf pubring.kbx~ S.gpg-agent gpg.conf pubring.gpg~ \
        random_seed S.gpg-agent .gpg-v21-migrated pubring-stamp $(moc_files) \
-       gpg.conf tofu.db
+       gpg.conf tofu.db reader_0.status reader_1.status
 
 all: $(BUILT_SOURCES)
        $(MAKE) $(AM_MAKEFLAGS) all-am
index 7545628..76e6806 100644 (file)
@@ -98,6 +98,25 @@ private Q_SLOTS:
         QVERIFY(key.primaryFingerprint() == QStringLiteral("7A0904B6950DA998020A1AD4BE41C0C3A5FF1F3C"));
     }
 
+    void testDataRewind()
+    {
+        if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.14") {
+            return;
+        }
+        QGpgME::QByteArrayDataProvider dp(aKey);
+        Data data(&dp);
+        char buf[20];
+        data.read(buf, 20);
+
+        auto keys = data.toKeys();
+        QVERIFY(keys.size() == 0);
+
+        data.rewind();
+
+        keys = data.toKeys();
+        QVERIFY(keys.size() == 1);
+    }
+
     void testQuickUid()
     {
         if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.13") {
index 822b2dd..7097db2 100644 (file)
 AC_DEFUN([AM_PATH_PYTHON],
  [
   dnl Find a Python interpreter.  Python versions prior to 2.0 are not
-  dnl supported. (2.0 was released on October 16, 2000).
+  dnl supported. (2.0 was released on October 16, 2000).  Python 3.0
+  dnl through to Python 3.3 are also not supported.
   m4_define_default([_AM_PYTHON_INTERPRETER_LIST],
 [python2 python2.7 dnl
  python dnl
- python3 python3.0 python3.1 python3.2 python3.3  dnl
python3.4 python3.5 python3.6 python3.7 python3.8])
+ python3 python3.7 python3.6 python3.5 python3.4 dnl python3.8
+ ])
 
   AC_ARG_VAR([PYTHON], [the Python interpreter])
 
index 0a196e0..1394c02 100644 (file)
@@ -70,6 +70,7 @@ main_sources =                                                                \
        parsetlv.c parsetlv.h                                           \
        mbox-util.c mbox-util.h                                         \
        data.h data.c data-fd.c data-stream.c data-mem.c data-user.c    \
+       data-estream.c                                                  \
        data-compat.c data-identify.c                                   \
        signers.c sig-notation.c                                        \
        wait.c wait-global.c wait-private.c wait-user.c wait.h          \
index 40fec0f..9423e3f 100644 (file)
@@ -124,7 +124,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES = versioninfo.rc gpgme.h gpgme-config
 CONFIG_CLEAN_VPATH_FILES =
 am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
@@ -162,14 +162,14 @@ LTLIBRARIES = $(lib_LTLIBRARIES)
 am__libgpgme_glib_la_SOURCES_DIST = util.h conversion.c b64dec.c \
        get-env.c context.h ops.h parsetlv.c parsetlv.h mbox-util.c \
        mbox-util.h data.h data.c data-fd.c data-stream.c data-mem.c \
-       data-user.c data-compat.c data-identify.c signers.c \
-       sig-notation.c wait.c wait-global.c wait-private.c wait-user.c \
-       wait.h op-support.c encrypt.c encrypt-sign.c decrypt.c \
-       decrypt-verify.c verify.c sign.c passphrase.c progress.c key.c \
-       keylist.c keysign.c trust-item.c trustlist.c tofupolicy.c \
-       import.c export.c genkey.c delete.c edit.c getauditlog.c \
-       opassuan.c passwd.c spawn.c assuan-support.c engine.h \
-       engine-backend.h engine.c engine-gpg.c status-table.c \
+       data-user.c data-estream.c data-compat.c data-identify.c \
+       signers.c sig-notation.c wait.c wait-global.c wait-private.c \
+       wait-user.c wait.h op-support.c encrypt.c encrypt-sign.c \
+       decrypt.c decrypt-verify.c verify.c sign.c passphrase.c \
+       progress.c key.c keylist.c keysign.c trust-item.c trustlist.c \
+       tofupolicy.c import.c export.c genkey.c delete.c edit.c \
+       getauditlog.c opassuan.c passwd.c spawn.c assuan-support.c \
+       engine.h engine-backend.h engine.c engine-gpg.c status-table.c \
        engine-gpgsm.c engine-assuan.c engine-gpgconf.c \
        engine-uiserver.c engine-g13.c vfs-mount.c vfs-create.c \
        engine-spawn.c gpgconf.c queryswdb.c sema.h priv-io.h ath.h \
@@ -183,14 +183,14 @@ am__libgpgme_glib_la_SOURCES_DIST = util.h conversion.c b64dec.c \
 @HAVE_DOSISH_SYSTEM_TRUE@am__objects_3 = w32-util.lo $(am__objects_2)
 am__objects_4 = conversion.lo b64dec.lo get-env.lo parsetlv.lo \
        mbox-util.lo data.lo data-fd.lo data-stream.lo data-mem.lo \
-       data-user.lo data-compat.lo data-identify.lo signers.lo \
-       sig-notation.lo wait.lo wait-global.lo wait-private.lo \
-       wait-user.lo op-support.lo encrypt.lo encrypt-sign.lo \
-       decrypt.lo decrypt-verify.lo verify.lo sign.lo passphrase.lo \
-       progress.lo key.lo keylist.lo keysign.lo trust-item.lo \
-       trustlist.lo tofupolicy.lo import.lo export.lo genkey.lo \
-       delete.lo edit.lo getauditlog.lo opassuan.lo passwd.lo \
-       spawn.lo assuan-support.lo engine.lo engine-gpg.lo \
+       data-user.lo data-estream.lo data-compat.lo data-identify.lo \
+       signers.lo sig-notation.lo wait.lo wait-global.lo \
+       wait-private.lo wait-user.lo op-support.lo encrypt.lo \
+       encrypt-sign.lo decrypt.lo decrypt-verify.lo verify.lo sign.lo \
+       passphrase.lo progress.lo key.lo keylist.lo keysign.lo \
+       trust-item.lo trustlist.lo tofupolicy.lo import.lo export.lo \
+       genkey.lo delete.lo edit.lo getauditlog.lo opassuan.lo \
+       passwd.lo spawn.lo assuan-support.lo engine.lo engine-gpg.lo \
        status-table.lo engine-gpgsm.lo engine-assuan.lo \
        engine-gpgconf.lo $(am__objects_1) engine-g13.lo vfs-mount.lo \
        vfs-create.lo engine-spawn.lo gpgconf.lo queryswdb.lo \
@@ -211,19 +211,20 @@ libgpgme_glib_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
 am__libgpgme_la_SOURCES_DIST = util.h conversion.c b64dec.c get-env.c \
        context.h ops.h parsetlv.c parsetlv.h mbox-util.c mbox-util.h \
        data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \
-       data-compat.c data-identify.c signers.c sig-notation.c wait.c \
-       wait-global.c wait-private.c wait-user.c wait.h op-support.c \
-       encrypt.c encrypt-sign.c decrypt.c decrypt-verify.c verify.c \
-       sign.c passphrase.c progress.c key.c keylist.c keysign.c \
-       trust-item.c trustlist.c tofupolicy.c import.c export.c \
-       genkey.c delete.c edit.c getauditlog.c opassuan.c passwd.c \
-       spawn.c assuan-support.c engine.h engine-backend.h engine.c \
-       engine-gpg.c status-table.c engine-gpgsm.c engine-assuan.c \
-       engine-gpgconf.c engine-uiserver.c engine-g13.c vfs-mount.c \
-       vfs-create.c engine-spawn.c gpgconf.c queryswdb.c sema.h \
-       priv-io.h ath.h posix-util.c posix-io.c w32-ce.h w32-ce.c \
-       w32-util.c sys-util.h dirinfo.c debug.c debug.h gpgme.c \
-       version.c error.c ath.c w32-io.c
+       data-estream.c data-compat.c data-identify.c signers.c \
+       sig-notation.c wait.c wait-global.c wait-private.c wait-user.c \
+       wait.h op-support.c encrypt.c encrypt-sign.c decrypt.c \
+       decrypt-verify.c verify.c sign.c passphrase.c progress.c key.c \
+       keylist.c keysign.c trust-item.c trustlist.c tofupolicy.c \
+       import.c export.c genkey.c delete.c edit.c getauditlog.c \
+       opassuan.c passwd.c spawn.c assuan-support.c engine.h \
+       engine-backend.h engine.c engine-gpg.c status-table.c \
+       engine-gpgsm.c engine-assuan.c engine-gpgconf.c \
+       engine-uiserver.c engine-g13.c vfs-mount.c vfs-create.c \
+       engine-spawn.c gpgconf.c queryswdb.c sema.h priv-io.h ath.h \
+       posix-util.c posix-io.c w32-ce.h w32-ce.c w32-util.c \
+       sys-util.h dirinfo.c debug.c debug.h gpgme.c version.c error.c \
+       ath.c w32-io.c
 @HAVE_DOSISH_SYSTEM_TRUE@am__objects_5 = w32-io.lo
 am_libgpgme_la_OBJECTS = $(am__objects_4) $(am__objects_5)
 libgpgme_la_OBJECTS = $(am_libgpgme_la_OBJECTS)
@@ -254,7 +255,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
@@ -533,6 +534,7 @@ main_sources = \
        parsetlv.c parsetlv.h                                           \
        mbox-util.c mbox-util.h                                         \
        data.h data.c data-fd.c data-stream.c data-mem.c data-user.c    \
+       data-estream.c                                                  \
        data-compat.c data-identify.c                                   \
        signers.c sig-notation.c                                        \
        wait.c wait-global.c wait-private.c wait-user.c wait.h          \
@@ -837,6 +839,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cJSON.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conversion.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-compat.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-estream.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-fd.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-identify.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data-mem.Plo@am__quote@
index cf0cb13..9e53012 100644 (file)
  * SPDX-License-Identifier: MIT
  *
  * Note that this code has been modified from the original code taken
- * from cjson-code-58.zip.
+ * from cjson-code-58.zip before 2014 (my first local commit was in
+ * 2014 but I may used the code even earlier).  Since 2016 the project
+ * was revived and moved to https://github.com/DaveGamble/cJSON.git.
+ * It is now a lot more complex and has substantial changes so that it
+ * is not possible to merge them directly.  In any case we only need a
+ * simple parser and not a complete library.  I have looked through
+ * the commits and fixed a few things which should apply; I also added
+ * a few references to the upstream code.  Regression test are missing!
  */
 
 #ifdef HAVE_CONFIG_H
 #include <ctype.h>
 #include <errno.h>
 
+#include <gpg-error.h>
+
 #include "cJSON.h"
 
+/* Only use calloc. */
+#define CALLOC_ONLY 1
+
+/* To avoid that a compiler optimizes certain memset calls away, these
+   macros may be used instead. */
+#define wipememory2(_ptr,_set,_len) do { \
+        volatile char *_vptr=(volatile char *)(_ptr); \
+        size_t _vlen=(_len); \
+        while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \
+    } while(0)
+#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len)
+
 /* We use malloc function wrappers from gpgrt (aka libgpg-error).  */
-#if 1
+#if GPGRT_VERSION_NUMBER >= 0x011c00 /* 1.28 */
 # include <gpgrt.h>
-# define xtrymalloc(a)   gpgrt_malloc ((a))
 # define xtrycalloc(a,b) gpgrt_calloc ((a), (b))
 # define xtrystrdup(a)   gpgrt_strdup ((a))
 # define xfree(a)        gpgrt_free ((a))
-#else
-# define xtrymalloc(a)   malloc ((a))
+# if CALLOC_ONLY
+#  define xtrymalloc(a)  gpgrt_calloc (1, (a))
+# else
+#  define xtrymalloc(a)  gpgrt_malloc ((a))
+# endif
+#else /* Without gpgrt (aka libgpg-error).  */
 # define xtrycalloc(a,b) calloc ((a), (b))
 # define xtrystrdup(a)   strdup ((a))
 # define xfree(a)        free ((a))
+# if CALLOC_ONLY
+#  define xtrymalloc(a)  calloc (1, (a))
+# else
+#  define xtrymalloc(a)  malloc ((a))
+# endif
 #endif
 
 
@@ -94,9 +123,15 @@ cJSON_Delete (cJSON * c)
       if (!(c->type & cJSON_IsReference) && c->child)
        cJSON_Delete (c->child);
       if (!(c->type & cJSON_IsReference) && c->valuestring)
-       xfree (c->valuestring);
+        {
+          wipememory (c->valuestring, strlen (c->valuestring));
+          xfree (c->valuestring);
+        }
       if (c->string)
-       xfree (c->string);
+        {
+          wipememory (c->string, strlen (c->string));
+          xfree (c->string);
+        }
       xfree (c);
       c = next;
     }
@@ -232,6 +267,9 @@ parse_string (cJSON * item, const char *str, const char **ep)
   char *out;
   int len = 0;
   unsigned uc, uc2;
+
+  /* FIXME: We should consider eary failure like it is done with
+   * commit 8656386c4f4a12f1cf3d6b26158407fd05e65029 in upstream.  */
   if (*str != '\"')
     {
       *ep = str;
@@ -239,11 +277,13 @@ parse_string (cJSON * item, const char *str, const char **ep)
     }                          /* not a string! */
 
   while (*ptr != '\"' && *ptr && ++len)
-    if (*ptr++ == '\\')
+    if (*ptr++ == '\\' && *ptr)
       ptr++;                   /* Skip escaped quotes. */
 
-  out = xtrymalloc (len + 1);  /* This is how long we need for the
-                                   string, roughly. */
+  out = xtrymalloc (len + 2);  /* This is how long we need for the
+                                 * string, roughly.  We add one extra
+                                 * byte in case the last input
+                                 * character is a backslash.  */
   if (!out)
     return 0;
 
@@ -256,6 +296,8 @@ parse_string (cJSON * item, const char *str, const char **ep)
       else
        {
          ptr++;
+          if (!*ptr)
+            break;
          switch (*ptr)
            {
            case 'b':
@@ -275,17 +317,22 @@ parse_string (cJSON * item, const char *str, const char **ep)
              break;
            case 'u':           /* transcode utf16 to utf8. */
              uc = parse_hex4 (ptr + 1);
+              if (!uc)
+                break;          /* Bad hex; continue right after the 'u'. */
              ptr += 4;         /* get the unicode char. */
 
-             if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0)
+             if ((uc >= 0xDC00 && uc <= 0xDFFF))
                break;          /* check for invalid.   */
 
              if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */
                {
                  if (ptr[1] != '\\' || ptr[2] != 'u')
                    break;      /* missing second-half of surrogate.    */
-                 uc2 = parse_hex4 (ptr + 3);
-                 ptr += 6;
+                  ptr += 2;
+                 uc2 = parse_hex4 (ptr + 1);
+                  if (!uc2)
+                    break;      /* Bad hex; continue right after the 'u'. */
+                 ptr += 4;
                  if (uc2 < 0xDC00 || uc2 > 0xDFFF)
                    break;      /* invalid second-half of surrogate.    */
                  uc = 0x10000 + (((uc & 0x3FF) << 10) | (uc2 & 0x3FF));
@@ -317,6 +364,8 @@ parse_string (cJSON * item, const char *str, const char **ep)
              ptr2 += len;
              break;
            default:
+              /* Fixme: Should we fail here: See
+               * https://github.com/DaveGamble/cJSON/issues/10  */
              *ptr2++ = *ptr;
              break;
            }
@@ -929,9 +978,11 @@ create_reference (cJSON * item)
 void
 cJSON_AddItemToArray (cJSON * array, cJSON * item)
 {
-  cJSON *c = array->child;
-  if (!item)
+  cJSON *c;
+
+  if (!item || !array)
     return;
+  c = array->child;
   if (!c)
     {
       array->child = item;
@@ -1132,6 +1183,8 @@ cJSON_ReplaceItemInObject (cJSON * object, const char *string,
     i++, c = c->next;
   if (c)
     {
+      /* FIXME: I guess we should free newitem->string here.  See
+       * upstream commit 0d10e279c8b604f71829b5d49d092719f4ae96b6.  */
       newitem->string = xtrystrdup (string);
       cJSON_ReplaceItemInArray (object, i, newitem);
     }
@@ -1393,9 +1446,11 @@ cJSON_Minify (char *json)
            {
              if (*json == '\\')
                *into++ = *json++;
-             *into++ = *json++;
+             if (*json)
+               *into++ = *json++;
            }
-         *into++ = *json++;
+          if (*json)
+            *into++ = *json++;
        }                       /* String literals, which are \" sensitive.  */
       else
        *into++ = *json++;      /* All other characters.  */
index c8e75ba..1c9379b 100644 (file)
@@ -124,6 +124,10 @@ struct gpgme_context
   /* Do not use the symmtric encryption passphrase cache.  */
   unsigned int no_symkey_cache : 1;
 
+  /* Pass --ignore-mdc-error to gpg.  Note that this flag is reset
+   * after the operation.  */
+  unsigned int ignore_mdc_error : 1;
+
   /* Flags for keylist mode.  */
   gpgme_keylist_mode_t keylist_mode;
 
@@ -151,6 +155,9 @@ struct gpgme_context
   /* The optional request origin.  */
   char *request_origin;
 
+  /* The optional auto key locate options.  */
+  char *auto_key_locate;
+
   /* The locale for the pinentry.  */
   char *lc_ctype;
   char *lc_messages;
diff --git a/src/data-estream.c b/src/data-estream.c
new file mode 100644 (file)
index 0000000..34f88a7
--- /dev/null
@@ -0,0 +1,99 @@
+/* data-stream.c - A stream based data object.
+ * Copyright (C) 2002, 2004, 2018 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 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.
+ *
+ * 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/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+
+#include "debug.h"
+#include "data.h"
+
+\f
+static gpgme_ssize_t
+stream_es_read (gpgme_data_t dh, void *buffer, size_t size)
+{
+  size_t amt = gpgrt_fread (buffer, 1, size, dh->data.e_stream);
+  if (amt > 0)
+    return amt;
+  return gpgrt_ferror (dh->data.e_stream) ? -1 : 0;
+}
+
+
+static gpgme_ssize_t
+stream_es_write (gpgme_data_t dh, const void *buffer, size_t size)
+{
+  size_t amt = gpgrt_fwrite (buffer, 1, size, dh->data.e_stream);
+  if (amt > 0)
+    return amt;
+  return gpgrt_ferror (dh->data.e_stream) ? -1 : 0;
+}
+
+
+static gpgme_off_t
+stream_es_seek (gpgme_data_t dh, gpgme_off_t offset, int whence)
+{
+  int err;
+
+  err = gpgrt_fseeko (dh->data.e_stream, offset, whence);
+  if (err)
+    return -1;
+
+  return gpgrt_ftello (dh->data.e_stream);
+}
+
+
+static int
+stream_es_get_fd (gpgme_data_t dh)
+{
+  gpgrt_fflush (dh->data.e_stream);
+  return gpgrt_fileno (dh->data.e_stream);
+}
+
+
+static struct _gpgme_data_cbs stream_es_cbs =
+  {
+    stream_es_read,
+    stream_es_write,
+    stream_es_seek,
+    NULL,
+    stream_es_get_fd
+  };
+
+
+\f
+gpgme_error_t
+gpgme_data_new_from_estream (gpgme_data_t *r_dh, gpgrt_stream_t stream)
+{
+  gpgme_error_t err;
+  TRACE_BEG1 (DEBUG_DATA, "gpgme_data_new_from_estream", r_dh, "estream=%p",
+             stream);
+
+  err = _gpgme_data_new (r_dh, &stream_es_cbs);
+  if (err)
+    return TRACE_ERR (err);
+
+  (*r_dh)->data.e_stream = stream;
+  return TRACE_SUC1 ("dh=%p", *r_dh);
+}
index a498b82..7569f7d 100644 (file)
@@ -224,7 +224,10 @@ gpgme_data_new_from_mem (gpgme_data_t *r_dh, const char *buffer,
 char *
 gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
 {
+  gpg_error_t err;
   char *str = NULL;
+  size_t len;
+  int blankout;
 
   TRACE_BEG1 (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh,
              "r_len=%p", r_len);
@@ -236,10 +239,22 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
       return NULL;
     }
 
+  err = _gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout);
+  if (err)
+    {
+      gpgme_data_release (dh);
+      TRACE_ERR (err);
+      return NULL;
+    }
+
   str = dh->data.mem.buffer;
+  len = dh->data.mem.length;
+  if (blankout && len)
+    len = 1;
+
   if (!str && dh->data.mem.orig_buffer)
     {
-      str = malloc (dh->data.mem.length);
+      str = malloc (len);
       if (!str)
        {
          int saved_err = gpg_error_from_syserror ();
@@ -247,15 +262,22 @@ gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len)
          TRACE_ERR (saved_err);
          return NULL;
        }
-      memcpy (str, dh->data.mem.orig_buffer, dh->data.mem.length);
+      if (blankout)
+        memset (str, 0, len);
+      else
+        memcpy (str, dh->data.mem.orig_buffer, len);
     }
   else
-    /* Prevent mem_release from releasing the buffer memory.  We must
-       not fail from this point.  */
-    dh->data.mem.buffer = NULL;
+    {
+      if (blankout && len)
+        *str = 0;
+      /* Prevent mem_release from releasing the buffer memory.  We
+       * must not fail from this point.  */
+      dh->data.mem.buffer = NULL;
+    }
 
   if (r_len)
-    *r_len = dh->data.mem.length;
+    *r_len = len;
 
   gpgme_data_release (dh);
 
index 7ae5b32..1df6b0a 100644 (file)
@@ -28,6 +28,7 @@
 #endif
 #include <errno.h>
 #include <string.h>
+#include <assert.h>
 
 #include "gpgme.h"
 #include "data.h"
 #include "priv-io.h"
 #include "debug.h"
 
+
+/* The property table which has an entry for each active data object.
+ * The data object itself uses an index into this table and the table
+ * has a pointer back to the data object.  All access to that table is
+ * controlled by the property_table_lock.
+ *
+ * We use a separate table instead of linking all data objects
+ * together for faster locating properties of the data object using
+ * the data objects serial number.  We use 64 bit for the serial
+ * number which is good enough to create a new data object every
+ * nanosecond for more than 500 years.  Thus no wrap around will ever
+ * happen.
+ */
+struct property_s
+{
+  gpgme_data_t dh;   /* The data objcet or NULL if the slot is not used.  */
+  uint64_t dserial;  /* The serial number of the data object.  */
+  struct {
+    unsigned int blankout : 1;  /* Void the held data.  */
+  } flags;
+};
+typedef struct property_s *property_t;
+
+static property_t property_table;
+static unsigned int property_table_size;
+DEFINE_STATIC_LOCK (property_table_lock);
+#define PROPERTY_TABLE_ALLOCATION_CHUNK 32
+
+
+\f
+/* Insert the newly created data object DH into the property table and
+ * store the index of it at R_IDX.  An error code is returned on error
+ * and the table is not changed.  */
+static gpg_error_t
+insert_into_property_table (gpgme_data_t dh, unsigned int *r_idx)
+{
+  static uint64_t last_dserial;
+  gpg_error_t err;
+  unsigned int idx;
+
+  LOCK (property_table_lock);
+  if (!property_table)
+    {
+      property_table_size = PROPERTY_TABLE_ALLOCATION_CHUNK;
+      property_table = calloc (property_table_size, sizeof *property_table);
+      if (!property_table)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+
+  /* Find an empty slot.  */
+  for (idx = 0; idx < property_table_size; idx++)
+    if (!property_table[idx].dh)
+      break;
+  if (!(idx < property_table_size))
+    {
+      /* No empty slot found.  Enlarge the table.  */
+      property_t newtbl;
+      unsigned int newsize;
+
+      newsize = property_table_size + PROPERTY_TABLE_ALLOCATION_CHUNK;;
+      if ((newsize * sizeof *property_table)
+          < (property_table_size * sizeof *property_table))
+        {
+          err = gpg_error (GPG_ERR_ENOMEM);
+          goto leave;
+        }
+      newtbl = realloc (property_table, newsize * sizeof *property_table);
+      if (!newtbl)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      property_table = newtbl;
+      for (idx = property_table_size; idx < newsize; idx++)
+        property_table[idx].dh = NULL;
+      idx = property_table_size;
+      property_table_size = newsize;
+    }
+
+  /* Slot found. */
+  property_table[idx].dh = dh;
+  property_table[idx].dserial = ++last_dserial;
+  memset (&property_table[idx].flags, 0, sizeof property_table[idx].flags);
+  *r_idx = idx;
+  err = 0;
+
+ leave:
+  UNLOCK (property_table_lock);
+  return err;
+}
+
+
+/* Remove the data object at PROPIDX from the table.  DH is only used
+ * for cross checking.  */
+static void
+remove_from_property_table (gpgme_data_t dh, unsigned int propidx)
+{
+  LOCK (property_table_lock);
+  assert (property_table);
+  assert (propidx < property_table_size);
+  assert (property_table[propidx].dh == dh);
+  property_table[propidx].dh = NULL;
+  UNLOCK (property_table_lock);
+}
+
+
+/* Return the data object's serial number for handle DH.  This is a
+ * unique serial number for each created data object.  */
+uint64_t
+_gpgme_data_get_dserial (gpgme_data_t dh)
+{
+  uint64_t dserial;
+  unsigned int idx;
+
+  if (!dh)
+    return 0;
+
+  idx = dh->propidx;
+  LOCK (property_table_lock);
+  assert (property_table);
+  assert (idx < property_table_size);
+  assert (property_table[idx].dh == dh);
+  dserial = property_table[idx].dserial;
+  UNLOCK (property_table_lock);
+
+  return dserial;
+}
+
+
+/* Set an internal property of a data object.  The data object may
+ * either be identified by the usual DH or by using the data serial
+ * number DSERIAL.  */
+gpg_error_t
+_gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial,
+                      data_prop_t name, int value)
+{
+  gpg_error_t err = 0;
+  int idx;
+  TRACE_BEG3 (DEBUG_DATA, "gpgme_data_set_prop", dh,
+             "dserial=%llu %lu=%d",
+              (unsigned long long)dserial,
+              (unsigned long)name, value);
+
+  LOCK (property_table_lock);
+  if ((!dh && !dserial) || (dh && dserial))
+    {
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  if (dh) /* Lookup via handle.  */
+    {
+      idx = dh->propidx;
+      assert (property_table);
+      assert (idx < property_table_size);
+      assert (property_table[idx].dh == dh);
+    }
+  else /* Lookup via DSERIAL.  */
+    {
+      if (!property_table)
+        {
+          err = gpg_error (GPG_ERR_NOT_FOUND);
+          goto leave;
+        }
+      for (idx = 0; idx < property_table_size; idx++)
+        if (property_table[idx].dh && property_table[idx].dserial == dserial)
+          break;
+      if (!(idx < property_table_size))
+        {
+          err = gpg_error (GPG_ERR_NOT_FOUND);
+          goto leave;
+        }
+    }
+
+  switch (name)
+    {
+    case DATA_PROP_NONE: /* Nothing to to do.  */
+      break;
+    case DATA_PROP_BLANKOUT:
+      property_table[idx].flags.blankout = !!value;
+      break;
+
+    default:
+      err = gpg_error (GPG_ERR_UNKNOWN_NAME);
+      break;
+    }
+
+ leave:
+  UNLOCK (property_table_lock);
+  return TRACE_ERR (err);
+}
+
+
+/* Get an internal property of a data object.  This is the counter
+ * part to _gpgme_data_set_property.  The value of the property is
+ * stored at R_VALUE.  On error 0 is stored at R_VALUE.  */
+gpg_error_t
+_gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial,
+                      data_prop_t name, int *r_value)
+{
+  gpg_error_t err = 0;
+  int idx;
+  TRACE_BEG2 (DEBUG_DATA, "gpgme_data_get_prop", dh,
+             "dserial=%llu %lu",
+              (unsigned long long)dserial,
+              (unsigned long)name);
+
+  *r_value = 0;
+
+  LOCK (property_table_lock);
+  if ((!dh && !dserial) || (dh && dserial))
+    {
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  if (dh) /* Lookup via handle.  */
+    {
+      idx = dh->propidx;
+      assert (property_table);
+      assert (idx < property_table_size);
+      assert (property_table[idx].dh == dh);
+    }
+  else /* Lookup via DSERIAL.  */
+    {
+      if (!property_table)
+        {
+          err = gpg_error (GPG_ERR_NOT_FOUND);
+          goto leave;
+        }
+      for (idx = 0; idx < property_table_size; idx++)
+        if (property_table[idx].dh && property_table[idx].dserial == dserial)
+          break;
+      if (!(idx < property_table_size))
+        {
+          err = gpg_error (GPG_ERR_NOT_FOUND);
+          goto leave;
+        }
+    }
+
+  switch (name)
+    {
+    case DATA_PROP_NONE: /* Nothing to to do.  */
+      break;
+    case DATA_PROP_BLANKOUT:
+      *r_value = property_table[idx].flags.blankout;
+      break;
+
+    default:
+      err = gpg_error (GPG_ERR_UNKNOWN_NAME);
+      break;
+    }
+
+ leave:
+  UNLOCK (property_table_lock);
+  return TRACE_ERR (err);
+}
+
+
 \f
 gpgme_error_t
 _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs)
 {
+  gpgme_error_t err;
   gpgme_data_t dh;
 
   if (!r_dh)
@@ -56,6 +318,13 @@ _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs)
 
   dh->cbs = cbs;
 
+  err = insert_into_property_table (dh, &dh->propidx);
+  if (err)
+    {
+      free (dh);
+      return err;
+    }
+
   *r_dh = dh;
   return 0;
 }
@@ -67,11 +336,13 @@ _gpgme_data_release (gpgme_data_t dh)
   if (!dh)
     return;
 
+  remove_from_property_table (dh, dh->propidx);
   if (dh->file_name)
     free (dh->file_name);
   free (dh);
 }
 
+
 \f
 /* Read up to SIZE bytes into buffer BUFFER from the data object with
    the handle DH.  Return the number of characters read, 0 on EOF and
@@ -80,6 +351,7 @@ gpgme_ssize_t
 gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size)
 {
   gpgme_ssize_t res;
+  int blankout;
   TRACE_BEG2 (DEBUG_DATA, "gpgme_data_read", dh,
              "buffer=%p, size=%u", buffer, size);
 
@@ -93,9 +365,16 @@ gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size)
       gpg_err_set_errno (ENOSYS);
       return TRACE_SYSRES (-1);
     }
-  do
-    res = (*dh->cbs->read) (dh, buffer, size);
-  while (res < 0 && errno == EINTR);
+
+  if (_gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout)
+      || blankout)
+    res = 0;
+  else
+    {
+      do
+        res = (*dh->cbs->read) (dh, buffer, size);
+      while (res < 0 && errno == EINTR);
+    }
 
   return TRACE_SYSRES (res);
 }
index 0a15b61..692eb9a 100644 (file)
@@ -29,6 +29,7 @@
 # include <sys/types.h>
 #endif
 #include <limits.h>
+#include <stdint.h>
 
 #include "gpgme.h"
 
@@ -73,6 +74,7 @@ struct gpgme_data
 {
   struct _gpgme_data_cbs *cbs;
   gpgme_data_encoding_t encoding;
+  unsigned int propidx;  /* Index into the property table.  */
 
 #ifdef PIPE_BUF
 #define BUFFER_SIZE PIPE_BUF
@@ -89,7 +91,7 @@ struct gpgme_data
   /* File name of the data object.  */
   char *file_name;
 
-  /* Hint on the to be expected toatl size of the data.  */
+  /* Hint on the to be expected total size of the data.  */
   gpgme_off_t size_hint;
 
   union
@@ -100,6 +102,9 @@ struct gpgme_data
     /* For gpgme_data_new_from_stream.  */
     FILE *stream;
 
+    /* For gpgme_data_new_from_estream.  */
+    gpgrt_stream_t e_stream;
+
     /* For gpgme_data_new_from_cbs.  */
     struct
     {
@@ -127,7 +132,28 @@ struct gpgme_data
   } data;
 };
 
+
+/* The data property types.  */
+typedef enum
+  {
+    DATA_PROP_NONE = 0,   /* Dummy property. */
+    DATA_PROP_BLANKOUT    /* Do not return the held data.  */
+  } data_prop_t;
+
+
 \f
+/* Return the data object's serial number for handle DH.  */
+uint64_t _gpgme_data_get_dserial (gpgme_data_t dh);
+
+/* Set an internal property of a data object.  */
+gpg_error_t _gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial,
+                                  data_prop_t name, int value);
+
+/* Get an internal property of a data object.  */
+gpg_error_t _gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial,
+                                  data_prop_t name, int *r_value);
+
+/* Create a new data object.  */
 gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh,
                               struct _gpgme_data_cbs *cbs);
 
@@ -140,4 +166,5 @@ int _gpgme_data_get_fd (gpgme_data_t dh);
 /* Get the size-hint value for DH or 0 if not available.  */
 gpgme_off_t _gpgme_data_get_size_hint (gpgme_data_t dh);
 
+
 #endif /* DATA_H */
index 17f79ac..224edc1 100644 (file)
@@ -58,7 +58,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous,
   if (err)
     return err;
 
-  err = _gpgme_op_decrypt_init_result (ctx);
+  err = _gpgme_op_decrypt_init_result (ctx, plain);
   if (err)
     return err;
 
@@ -74,7 +74,7 @@ decrypt_verify_start (gpgme_ctx_t ctx, int synchronous,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-       (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+       (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
        return err;
     }
@@ -127,6 +127,7 @@ gpgme_op_decrypt_verify (gpgme_ctx_t ctx, gpgme_data_t cipher,
   err = decrypt_verify_start (ctx, 1, GPGME_DECRYPT_VERIFY, cipher, plain);
   if (!err)
     err = _gpgme_wait_one (ctx);
+  ctx->ignore_mdc_error = 0;  /* Always reset.  */
   return TRACE_ERR (err);
 }
 
@@ -177,5 +178,6 @@ gpgme_op_decrypt_ext (gpgme_ctx_t ctx,
     err = _gpgme_decrypt_start (ctx, 1, flags, cipher, plain);
   if (!err)
     err = _gpgme_wait_one (ctx);
+  ctx->ignore_mdc_error = 0;  /* Always reset.  */
   return TRACE_ERR (err);
 }
index 0fc7019..b51603a 100644 (file)
@@ -32,7 +32,7 @@
 #include "util.h"
 #include "context.h"
 #include "ops.h"
-
+#include "data.h"
 
 \f
 typedef struct
@@ -53,13 +53,25 @@ typedef struct
    * status lines for each key the message has been encrypted to but
    * that secret key is not available.  This can't be done for hidden
    * recipients, though.  We track it here to allow for a better error
-   * message that the general DECRYPTION_FAILED. */
+   * message than the general DECRYPTION_FAILED. */
   int any_no_seckey;
 
+  /* If the engine emits a DECRYPTION_INFO status and that does not
+   * indicate that an integrity protection mode is active, this flag
+   * is set.  */
+  int not_integrity_protected;
+
+  /* The error code from the first ERROR line.  This is in some cases
+   * used to return a better matching error code to the caller.  */
+  gpg_error_t first_status_error;
+
   /* A pointer to the next pointer of the last recipient in the list.
      This makes appending new invalid signers painless while
      preserving the order.  */
   gpgme_recipient_t *last_recipient_p;
+
+  /* The data object serial number of the plaintext.  */
+  uint64_t plaintext_dserial;
 } *op_data_t;
 
 
@@ -92,6 +104,8 @@ gpgme_op_decrypt_result (gpgme_ctx_t ctx)
 
   TRACE_BEG (DEBUG_CTX, "gpgme_op_decrypt_result", ctx);
 
+  ctx->ignore_mdc_error = 0;  /* Always reset this flag.  */
+
   err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL);
   opd = hook;
   if (err || !opd)
@@ -209,6 +223,15 @@ parse_status_error (char *args, op_data_t opd)
           break;
         }
     }
+  else if (!strcmp (field[0], "nomdc_with_legacy_cipher"))
+    {
+      opd->result.legacy_cipher_nomdc = 1;
+      opd->not_integrity_protected = 1;
+    }
+
+  /* Record the first error code.  */
+  if (err && !opd->first_status_error)
+    opd->first_status_error = err;
 
 
   free (args2);
@@ -280,7 +303,7 @@ parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol)
   char *field[3];
   int nfields;
   char *args2;
-  int mdc, mode;
+  int mdc, aead_algo;
   const char *algostr, *modestr;
 
   if (!args)
@@ -296,19 +319,22 @@ parse_decryption_info (char *args, op_data_t opd, gpgme_protocol_t protocol)
 
   mdc     = atoi (field[0]);
   algostr = _gpgme_cipher_algo_name (atoi (field[1]), protocol);
-  mode    = nfields < 3? 0 : atoi (field[2]);
-  modestr = _gpgme_cipher_mode_name (mode, protocol);
+  aead_algo    = nfields < 3? 0 : atoi (field[2]);
+  modestr = _gpgme_cipher_mode_name (aead_algo, protocol);
 
   free (args2);
 
   free (opd->result.symkey_algo);
-  if (!mode && mdc != 2)
+  if (!aead_algo && mdc != 2)
     opd->result.symkey_algo = _gpgme_strconcat (algostr, ".PGPCFB", NULL);
   else
     opd->result.symkey_algo = _gpgme_strconcat (algostr, ".", modestr, NULL);
   if (!opd->result.symkey_algo)
     return gpg_error_from_syserror ();
 
+  if (!mdc && !aead_algo)
+    opd->not_integrity_protected = 1;
+
   return 0;
 }
 
@@ -338,18 +364,50 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code,
       break;
 
     case GPGME_STATUS_EOF:
-      /* FIXME: These error values should probably be attributed to
-        the underlying crypto engine (as error source).  */
-      if (opd->failed && opd->pkdecrypt_failed)
-        return opd->pkdecrypt_failed;
-      else if (opd->failed && opd->any_no_seckey)
-       return gpg_error (GPG_ERR_NO_SECKEY);
-      else if (opd->failed)
-       return gpg_error (GPG_ERR_DECRYPT_FAILED);
+      /* We force an encryption failure if we know that integrity
+       * protection is missing.  For modern version of gpg using
+       * modern cipher algorithms this is not required because gpg
+       * will issue a failure anyway.  However older gpg versions emit
+       * only a warning.
+       * Fixme: These error values should probably be attributed to
+       * the underlying crypto engine (as error source).  */
+      if (opd->failed)
+        {
+          /* This comes from a specialized ERROR status line.  */
+          if (opd->pkdecrypt_failed)
+            return opd->pkdecrypt_failed;
+
+          /* For an integrity failure return just DECRYPTION_FAILED;
+           * the actual cause can be taken from an already set
+           * decryption result flag.  */
+          if ((opd->not_integrity_protected && !ctx->ignore_mdc_error))
+            return gpg_error (GPG_ERR_DECRYPT_FAILED);
+
+          /* If we have any other ERROR code we prefer that over
+           * NO_SECKEY because it is probably the better matching
+           * code.  For example a garbled message with multiple
+           * plaintext will return BAD_DATA here but may also have
+           * indicated a NO_SECKEY.  */
+          if (opd->first_status_error)
+            return opd->first_status_error;
+
+          /* No secret key is pretty common reason.  */
+          if (opd->any_no_seckey)
+            return gpg_error (GPG_ERR_NO_SECKEY);
+
+          /* Generic decryption failed error code.  */
+          return gpg_error (GPG_ERR_DECRYPT_FAILED);
+        }
       else if (!opd->okay)
-       return gpg_error (GPG_ERR_NO_DATA);
+        {
+          /* No data was found.  */
+          return gpg_error (GPG_ERR_NO_DATA);
+        }
       else if (opd->failure_code)
-        return opd->failure_code;
+        {
+          /* The engine returned failure code at program exit.  */
+          return opd->failure_code;
+        }
       break;
 
     case GPGME_STATUS_DECRYPTION_INFO:
@@ -364,12 +422,21 @@ _gpgme_decrypt_status_handler (void *priv, gpgme_status_code_t code,
 
     case GPGME_STATUS_DECRYPTION_FAILED:
       opd->failed = 1;
+      /* Tell the data object that it shall not return any data.  We
+       * use the serial number because the data object may be owned by
+       * another thread.  We also don't check for an error because it
+       * is possible that the data object has already been destroyed
+       * and we are then not interested in returning an error.  */
+      if (!ctx->ignore_mdc_error)
+        _gpgme_data_set_prop (NULL, opd->plaintext_dserial,
+                              DATA_PROP_BLANKOUT, 1);
       break;
 
     case GPGME_STATUS_ERROR:
       /* Note that this is an informational status code which should
-         not lead to an error return unless it is something not
-         related to the backend.  */
+       * not lead to an error return unless it is something not
+       * related to the backend.  However, it is used to return a
+       * better matching final error code.  */
       err = parse_status_error (args, opd);
       if (err)
         return err;
@@ -452,7 +519,7 @@ decrypt_status_handler (void *priv, gpgme_status_code_t code, char *args)
 
 
 gpgme_error_t
-_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx)
+_gpgme_op_decrypt_init_result (gpgme_ctx_t ctx, gpgme_data_t plaintext)
 {
   gpgme_error_t err;
   void *hook;
@@ -465,6 +532,7 @@ _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx)
     return err;
 
   opd->last_recipient_p = &opd->result.recipients;
+  opd->plaintext_dserial = _gpgme_data_get_dserial (plaintext);
   return 0;
 }
 
@@ -482,7 +550,7 @@ _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous,
   if (err)
     return err;
 
-  err = _gpgme_op_decrypt_init_result (ctx);
+  err = _gpgme_op_decrypt_init_result (ctx, plain);
   if (err)
     return err;
 
@@ -497,7 +565,7 @@ _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-       (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+       (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
        return err;
     }
@@ -546,5 +614,6 @@ gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain)
   err = _gpgme_decrypt_start (ctx, 1, 0, cipher, plain);
   if (!err)
     err = _gpgme_wait_one (ctx);
+  ctx->ignore_mdc_error = 0;  /* Always reset.  */
   return TRACE_ERR (err);
 }
index ca4d595..2867efb 100644 (file)
@@ -139,8 +139,7 @@ interact_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key,
   opd->fnc_old = NULL;
   opd->fnc_value = fnc_value;
 
-  err = _gpgme_engine_set_command_handler (ctx->engine, command_handler,
-                                          ctx, out);
+  err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, ctx);
   if (err)
     return err;
 
@@ -219,8 +218,7 @@ edit_start (gpgme_ctx_t ctx, int synchronous, int type, gpgme_key_t key,
   opd->fnc_old = fnc;
   opd->fnc_value = fnc_value;
 
-  err = _gpgme_engine_set_command_handler (ctx->engine, command_handler,
-                                          ctx, out);
+  err = _gpgme_engine_set_command_handler (ctx->engine, command_handler, ctx);
   if (err)
     return err;
 
index 4db46e2..cc34fbd 100644 (file)
@@ -93,7 +93,7 @@ encrypt_sign_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t recp[],
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-       (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+       (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
        return err;
     }
index 2318497..a27a53a 100644 (file)
@@ -242,7 +242,7 @@ encrypt_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t recp[],
     {
       /* Symmetric encryption requires a passphrase.  */
       err = _gpgme_engine_set_command_handler
-       (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+       (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
        return err;
     }
index f692666..4f33da1 100644 (file)
@@ -55,7 +55,7 @@ struct engine_ops
                              void *fnc_value);
   gpgme_error_t (*set_command_handler) (void *engine,
                                        engine_command_handler_t fnc,
-                                       void *fnc_value, gpgme_data_t data);
+                                       void *fnc_value);
   gpgme_error_t (*set_colon_line_handler) (void *engine,
                                           engine_colon_line_handler_t fnc,
                                           void *fnc_value);
index 173e940..2833374 100644 (file)
@@ -26,7 +26,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
-#include <errno.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
@@ -136,23 +135,24 @@ struct engine_gpg
     char *keyword;       /* what has been requested (malloced) */
     engine_command_handler_t fnc;
     void *fnc_value;
-    /* The kludges never end.  This is used to couple command handlers
-       with output data in edit key mode.  */
-    gpgme_data_t linked_data;
-    int linked_idx;
   } cmd;
 
   struct gpgme_io_cbs io_cbs;
   gpgme_pinentry_mode_t pinentry_mode;
   char request_origin[10];
+  char *auto_key_locate;
 
   struct {
     unsigned int no_symkey_cache : 1;
     unsigned int offline : 1;
+    unsigned int ignore_mdc_error : 1;
   } flags;
 
   /* NULL or the data object fed to --override_session_key-fd.  */
   gpgme_data_t override_session_key;
+
+  /* Memory data containing diagnostics (--logger-fd) of gpg */
+  gpgme_data_t diagnostics;
 };
 
 typedef struct engine_gpg *engine_gpg_t;
@@ -454,8 +454,10 @@ gpg_release (void *engine)
     free_argv (gpg->argv);
   if (gpg->cmd.keyword)
     free (gpg->cmd.keyword);
+  free (gpg->auto_key_locate);
 
   gpgme_data_release (gpg->override_session_key);
+  gpgme_data_release (gpg->diagnostics);
 
   free (gpg);
 }
@@ -503,8 +505,6 @@ gpg_new (void **engine, const char *file_name, const char *home_dir,
   gpg->colon.fd[1] = -1;
   gpg->cmd.fd = -1;
   gpg->cmd.idx = -1;
-  gpg->cmd.linked_data = NULL;
-  gpg->cmd.linked_idx = -1;
 
   /* Allocate the read buffer for the status pipe.  */
   gpg->status.bufsize = 1024;
@@ -626,6 +626,16 @@ gpg_new (void **engine, const char *file_name, const char *home_dir,
        }
     }
 
+  rc = gpgme_data_new (&gpg->diagnostics);
+  if (rc)
+    goto leave;
+
+  rc = add_arg (gpg, "--logger-fd");
+  if (rc)
+    goto leave;
+
+  rc = add_data (gpg, gpg->diagnostics, -2, 1);
+
  leave:
   if (rc)
     gpg_release (gpg);
@@ -651,11 +661,20 @@ gpg_set_engine_flags (void *engine, const gpgme_ctx_t ctx)
   else
     *gpg->request_origin = 0;
 
+  if (ctx->auto_key_locate && have_gpg_version (gpg, "2.1.18"))
+    {
+      if (gpg->auto_key_locate)
+        free (gpg->auto_key_locate);
+      gpg->auto_key_locate = _gpgme_strconcat ("--auto-key-locate=",
+                                               ctx->auto_key_locate, NULL);
+    }
+
   gpg->flags.no_symkey_cache = (ctx->no_symkey_cache
                                 && have_gpg_version (gpg, "2.2.7"));
-
   gpg->flags.offline = (ctx->offline && have_gpg_version (gpg, "2.1.23"));
 
+  gpg->flags.ignore_mdc_error = !!ctx->ignore_mdc_error;
+
 }
 
 
@@ -793,14 +812,14 @@ command_handler (void *opaque, int fd)
 
 
 
-/* The Fnc will be called to get a value for one of the commands with
-   a key KEY.  If the Code passed to FNC is 0, the function may release
-   resources associated with the returned value from another call.  To
-   match such a second call to a first call, the returned value from
  the first call is passed as keyword.  */
+/* The FNC will be called to get a value for one of the commands with
+ * a key KEY.  If the code passed to FNC is 0, the function may
+ * release resources associated with the returned value from another
+ * call.  To match such a second call to a first call, the returned
* value from the first call is passed as keyword.  */
 static gpgme_error_t
 gpg_set_command_handler (void *engine, engine_command_handler_t fnc,
-                        void *fnc_value, gpgme_data_t linked_data)
+                        void *fnc_value)
 {
   engine_gpg_t gpg = engine;
   gpgme_error_t rc;
@@ -819,7 +838,6 @@ gpg_set_command_handler (void *engine, engine_command_handler_t fnc,
   gpg->cmd.fnc = fnc;
   gpg->cmd.cb_data = (void *) &gpg->cmd;
   gpg->cmd.fnc_value = fnc_value;
-  gpg->cmd.linked_data = linked_data;
   gpg->cmd.used = 1;
   return 0;
 }
@@ -950,6 +968,19 @@ build_argv (engine_gpg_t gpg, const char *pgmname)
       argc++;
     }
 
+  if (gpg->auto_key_locate)
+    {
+      argv[argc] = strdup (gpg->auto_key_locate);
+      if (!argv[argc])
+        {
+          int saved_err = gpg_error_from_syserror ();
+          free (fd_data_map);
+          free_argv (argv);
+          return saved_err;
+        }
+      argc++;
+    }
+
   if (gpg->flags.no_symkey_cache)
     {
       argv[argc] = strdup ("--no-symkey-cache");
@@ -963,6 +994,19 @@ build_argv (engine_gpg_t gpg, const char *pgmname)
       argc++;
     }
 
+  if (gpg->flags.ignore_mdc_error)
+    {
+      argv[argc] = strdup ("--ignore-mdc-error");
+      if (!argv[argc])
+       {
+          int saved_err = gpg_error_from_syserror ();
+         free (fd_data_map);
+         free_argv (argv);
+         return saved_err;
+        }
+      argc++;
+    }
+
   if (gpg->flags.offline)
     {
       argv[argc] = strdup ("--disable-dirmngr");
@@ -1039,10 +1083,10 @@ build_argv (engine_gpg_t gpg, const char *pgmname)
            if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound ? 1 : 0)
                == -1)
              {
-               int saved_errno = errno;
+               int saved_err = gpg_error_from_syserror ();
                free (fd_data_map);
                free_argv (argv);
-               return gpg_error (saved_errno);
+               return saved_err;
              }
            if (_gpgme_io_set_close_notify (fds[0],
                                            close_notify_handler, gpg)
@@ -1077,11 +1121,6 @@ build_argv (engine_gpg_t gpg, const char *pgmname)
                  assert (gpg->cmd.idx == -1);
                  gpg->cmd.idx = datac;
                }
-             else if (gpg->cmd.linked_data == a->data)
-               {
-                 assert (gpg->cmd.linked_idx == -1);
-                 gpg->cmd.linked_idx = datac;
-               }
            }
 
          fd_data_map[datac].data = a->data;
@@ -1268,44 +1307,6 @@ read_status (engine_gpg_t gpg)
                          if (err)
                            return err;
                         }
-
-                     if (r == GPGME_STATUS_END_STREAM)
-                       {
-                         if (gpg->cmd.used)
-                           {
-                             /* Before we can actually add the
-                                command fd, we might have to flush
-                                the linked output data pipe.  */
-                             if (gpg->cmd.linked_idx != -1
-                                 && gpg->fd_data_map[gpg->cmd.linked_idx].fd
-                                 != -1)
-                               {
-                                 struct io_select_fd_s fds;
-                                 fds.fd =
-                                   gpg->fd_data_map[gpg->cmd.linked_idx].fd;
-                                 fds.for_read = 1;
-                                 fds.for_write = 0;
-                                 fds.opaque = NULL;
-                                 do
-                                   {
-                                     fds.signaled = 0;
-                                     _gpgme_io_select (&fds, 1, 1);
-                                     if (fds.signaled)
-                                       _gpgme_data_inbound_handler
-                                         (gpg->cmd.linked_data, fds.fd);
-                                   }
-                                 while (fds.signaled);
-                               }
-
-                             /* XXX We must check if there are any
-                                more fds active after removing this
-                                one.  */
-                             (*gpg->io_cbs.remove)
-                               (gpg->fd_data_map[gpg->cmd.idx].tag);
-                             gpg->cmd.fd = gpg->fd_data_map[gpg->cmd.idx].fd;
-                             gpg->fd_data_map[gpg->cmd.idx].fd = -1;
-                           }
-                        }
                     }
                 }
              /* To reuse the buffer for the next line we have to
@@ -2240,13 +2241,22 @@ export_common (engine_gpg_t gpg, gpgme_export_mode_t mode,
     return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
   if ((mode & GPGME_EXPORT_MODE_MINIMAL))
-    err = add_arg (gpg, "--export-options=export-minimal");
+    {
+      if ((mode & GPGME_EXPORT_MODE_NOUID))
+        err = add_arg (gpg, "--export-options=export-minimal,export-drop-uids");
+      else
+        err = add_arg (gpg, "--export-options=export-minimal");
+    }
+  else if ((mode & GPGME_EXPORT_MODE_NOUID))
+    err = add_arg (gpg, "--export-options=export-drop-uids");
 
   if (err)
     ;
   else if ((mode & GPGME_EXPORT_MODE_EXTERN))
     {
       err = add_arg (gpg, "--send-keys");
+      if (!err && (mode & GPGME_EXPORT_MODE_NOUID))
+        err = add_arg (gpg, "--keyserver-options=export-drop-uids");
     }
   else
     {
@@ -3279,6 +3289,52 @@ gpg_set_pinentry_mode (void *engine, gpgme_pinentry_mode_t mode)
 }
 
 
+static gpgme_error_t
+gpg_getauditlog (void *engine, gpgme_data_t output, unsigned int flags)
+{
+  engine_gpg_t gpg = engine;
+#define MYBUFLEN 4096
+  char buf[MYBUFLEN];
+  int nread;
+  int any_written = 0;
+
+  if (!(flags & GPGME_AUDITLOG_DIAG))
+    {
+      return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+    }
+
+  if (!gpg || !output)
+    {
+      return gpg_error (GPG_ERR_INV_VALUE);
+    }
+
+  if (!gpg->diagnostics)
+    {
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  gpgme_data_rewind (gpg->diagnostics);
+
+  while ((nread = gpgme_data_read (gpg->diagnostics, buf, MYBUFLEN)) > 0)
+    {
+      any_written = 1;
+      if (gpgme_data_write (output, buf, nread) == -1)
+        return gpg_error_from_syserror ();
+    }
+  if (!any_written)
+    {
+      return gpg_error (GPG_ERR_NO_DATA);
+    }
+
+  if (nread == -1)
+    return gpg_error_from_syserror ();
+
+  gpgme_data_rewind (output);
+  return 0;
+#undef MYBUFLEN
+}
+
+
 \f
 struct engine_ops _gpgme_engine_ops_gpg =
   {
@@ -3316,7 +3372,7 @@ struct engine_ops _gpgme_engine_ops_gpg =
     gpg_sign,
     gpg_trustlist,
     gpg_verify,
-    NULL,              /* getauditlog */
+    gpg_getauditlog,
     NULL,               /* opassuan_transact */
     NULL,              /* conf_load */
     NULL,              /* conf_save */
index da7e524..3266e36 100644 (file)
@@ -37,7 +37,6 @@
 #include <locale.h>
 #endif
 #include <fcntl.h> /* FIXME */
-#include <errno.h>
 
 #include "gpgme.h"
 #include "util.h"
@@ -986,8 +985,7 @@ status_handler (void *opaque, int fd)
           while (linelen > 0)
             {
               nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen);
-              if (!nwritten || (nwritten < 0 && errno != EINTR)
-                  || nwritten > linelen)
+              if (nwritten <= 0 || nwritten > linelen)
                 {
                   err = gpg_error_from_syserror ();
                   break;
@@ -1013,8 +1011,17 @@ status_handler (void *opaque, int fd)
            *(rest++) = 0;
 
          r = _gpgme_parse_status (line + 2);
+          if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS)
+            {
+              /* Note that we call the monitor even if we do
+               * not know the status code (r < 0).  */
+              err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value,
+                                          line + 2, rest);
+            }
+          else
+            err = 0;
 
-         if (r >= 0)
+         if (r >= 0 && !err)
            {
              if (gpgsm->status.fnc)
                 {
@@ -2057,6 +2064,9 @@ gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags)
   if (!gpgsm || !output)
     return gpg_error (GPG_ERR_INV_VALUE);
 
+  if ((flags & GPGME_AUDITLOG_DIAG))
+    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
 #if USE_DESCRIPTOR_PASSING
   gpgsm->output_cb.data = output;
   err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
index b716ca2..b629bea 100644 (file)
@@ -596,8 +596,7 @@ _gpgme_engine_set_status_handler (engine_t engine,
 gpgme_error_t
 _gpgme_engine_set_command_handler (engine_t engine,
                                   engine_command_handler_t fnc,
-                                  void *fnc_value,
-                                  gpgme_data_t linked_data)
+                                  void *fnc_value)
 {
   if (!engine)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -605,8 +604,7 @@ _gpgme_engine_set_command_handler (engine_t engine,
   if (!engine->ops->set_command_handler)
     return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
 
-  return (*engine->ops->set_command_handler) (engine->engine,
-                                             fnc, fnc_value, linked_data);
+  return (*engine->ops->set_command_handler) (engine->engine, fnc, fnc_value);
 }
 
 gpgme_error_t
index 8b692f2..c512a25 100644 (file)
@@ -78,8 +78,7 @@ void _gpgme_engine_set_status_handler (engine_t engine,
                                       void *fnc_value);
 gpgme_error_t _gpgme_engine_set_command_handler (engine_t engine,
                                                 engine_command_handler_t fnc,
-                                                void *fnc_value,
-                                                gpgme_data_t data);
+                                                void *fnc_value);
 gpgme_error_t
 _gpgme_engine_set_colon_line_handler (engine_t engine,
                                      engine_colon_line_handler_t fnc,
index cd94050..f460e85 100644 (file)
@@ -123,6 +123,7 @@ export_start (gpgme_ctx_t ctx, int synchronous, const char *pattern,
                 |GPGME_EXPORT_MODE_MINIMAL
                 |GPGME_EXPORT_MODE_SECRET
                 |GPGME_EXPORT_MODE_RAW
+                |GPGME_EXPORT_MODE_NOUID
                 |GPGME_EXPORT_MODE_PKCS12)))
     return gpg_error (GPG_ERR_INV_VALUE); /* Invalid flags in MODE.  */
 
index 16484ec..ffca7e8 100644 (file)
@@ -259,7 +259,7 @@ genkey_start (gpgme_ctx_t ctx, int synchronous, const char *parms,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-        (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+        (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
         return err;
     }
@@ -345,7 +345,7 @@ createkey_start (gpgme_ctx_t ctx, int synchronous,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-        (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+        (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
         return err;
     }
@@ -433,7 +433,7 @@ createsubkey_start (gpgme_ctx_t ctx, int synchronous,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-        (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+        (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
         return err;
     }
@@ -519,7 +519,7 @@ addrevuid_start (gpgme_ctx_t ctx, int synchronous, int extraflags,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-        (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+        (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
         return err;
     }
index dbaf260..d70e66f 100644 (file)
@@ -47,9 +47,12 @@ getauditlog_start (gpgme_ctx_t ctx, int synchronous,
   if (!output)
     return gpg_error (GPG_ERR_INV_VALUE);
 
-  err = _gpgme_op_reset (ctx, ((synchronous&255) | 256) );
-  if (err)
-    return err;
+  if (!(flags & GPGME_AUDITLOG_DIAG))
+    {
+      err = _gpgme_op_reset (ctx, ((synchronous&255) | 256) );
+      if (err)
+        return err;
+    }
 
   _gpgme_engine_set_status_handler (ctx->engine,
                                     getauditlog_status_handler, ctx);
index f1e9f25..b10331b 100644 (file)
@@ -48,24 +48,25 @@ int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;}
 /* We don't allow a request with more than 64 MiB.  */
 #define MAX_REQUEST_SIZE (64 * 1024 * 1024)
 
-/* Minimal, default and maximum chunk size for returned data. The
- * first chunk is returned directly.  If the "more" flag is also
- * returned, a "getmore" command needs to be used to get the next
- * chunk.  Right now this value covers just the value of the "data"
- * element; so to cover for the other returned objects this values
- * needs to be lower than the maximum allowed size of the browser. */
-#define MIN_REPLY_CHUNK_SIZE  512
-#define DEF_REPLY_CHUNK_SIZE (512 * 1024)
+/* Minimal chunk size for returned data.*/
+#define MIN_REPLY_CHUNK_SIZE  30
+
+/* If no chunksize is provided we print everything.  Changing
+ * this to a positive value will result in all messages beeing
+ * chunked. */
+#define DEF_REPLY_CHUNK_SIZE  0
 #define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024)
 
 
 static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN;
 static cjson_t error_object_v (cjson_t json, const char *message,
-                              va_list arg_ptr) GPGRT_ATTR_PRINTF(2,0);
+                               va_list arg_ptr, gpg_error_t err)
+                               GPGRT_ATTR_PRINTF(2,0);
 static cjson_t error_object (cjson_t json, const char *message,
                             ...) GPGRT_ATTR_PRINTF(2,3);
 static char *error_object_string (const char *message,
                                   ...) GPGRT_ATTR_PRINTF(1,2);
+static char *process_request (const char *request);
 
 
 /* True if interactive mode is active.  */
@@ -79,8 +80,6 @@ static struct
   char  *buffer;   /* Malloced data or NULL if not used.  */
   size_t length;   /* Length of that data.  */
   size_t written;  /* # of already written bytes from BUFFER.  */
-  const char *type;/* The "type" of the data.  */
-  int base64;      /* The "base64" flag of the data.  */
 } pending_data;
 
 
@@ -88,13 +87,7 @@ static struct
  * Helper functions and macros
  */
 
-#define xtrymalloc(a)  gpgrt_malloc ((a))
 #define xtrystrdup(a)  gpgrt_strdup ((a))
-#define xmalloc(a) ({                           \
-      void *_r = gpgrt_malloc ((a));            \
-      if (!_r)                                  \
-        xoutofcore ("malloc");                  \
-      _r; })
 #define xcalloc(a,b) ({                         \
       void *_r = gpgrt_calloc ((a), (b));       \
       if (!_r)                                  \
@@ -112,6 +105,21 @@ static struct
       _r; })
 #define xfree(a) gpgrt_free ((a))
 
+/* Only use calloc. */
+#define CALLOC_ONLY 1
+
+#if CALLOC_ONLY
+#define xtrymalloc(a)  gpgrt_calloc (1, (a))
+#define xmalloc(a) xcalloc(1, (a))
+#else
+#define xtrymalloc(a)  gpgrt_malloc ((a))
+#define xmalloc(a) ({                           \
+      void *_r = gpgrt_malloc ((a));            \
+      if (!_r)                                  \
+        xoutofcore ("malloc");                  \
+      _r; })
+#endif
+
 #define spacep(p)   (*(p) == ' ' || *(p) == '\t')
 
 #ifndef HAVE_STPCPY
@@ -127,6 +135,19 @@ _my_stpcpy (char *a, const char *b)
 #endif /*!HAVE_STPCPY*/
 
 
+/* Free a NULL terminated array */
+static void
+xfree_array (char **array)
+{
+  if (array)
+    {
+      int idx;
+      for (idx = 0; array[idx]; idx++)
+        xfree (array[idx]);
+      xfree (array);
+    }
+}
+
 
 static void
 xoutofcore (const char *type)
@@ -147,6 +168,16 @@ xjson_CreateObject (void)
   return json;
 }
 
+/* Call cJSON_CreateArray but terminate in case of an error.  */
+static cjson_t
+xjson_CreateArray (void)
+{
+  cjson_t json = cJSON_CreateArray ();
+  if (!json)
+    xoutofcore ("cJSON_CreateArray");
+  return json;
+}
+
 
 /* Wrapper around cJSON_AddStringToObject which returns an gpg-error
  * code instead of the NULL or the new object.  */
@@ -169,6 +200,15 @@ xjson_AddStringToObject (cjson_t object, const char *name, const char *string)
 }
 
 
+/* Same as xjson_AddStringToObject but ignores NULL strings */
+static void
+xjson_AddStringToObject0 (cjson_t object, const char *name, const char *string)
+{
+  if (!string)
+    return;
+  xjson_AddStringToObject (object, name, string);
+}
+
 /* Wrapper around cJSON_AddBoolToObject which terminates the process
  * in case of an error.  */
 static void
@@ -179,6 +219,26 @@ xjson_AddBoolToObject (cjson_t object, const char *name, int abool)
   return ;
 }
 
+/* Wrapper around cJSON_AddNumberToObject which terminates the process
+ * in case of an error.  */
+static void
+xjson_AddNumberToObject (cjson_t object, const char *name, double dbl)
+{
+  if (!cJSON_AddNumberToObject (object, name, dbl))
+    xoutofcore ("cJSON_AddNumberToObject");
+  return ;
+}
+
+/* Wrapper around cJSON_AddItemToObject which terminates the process
+ * in case of an error.  */
+static void
+xjson_AddItemToObject (cjson_t object, const char *name, cjson_t item)
+{
+  if (!cJSON_AddItemToObject (object, name, item))
+    xoutofcore ("cJSON_AddItemToObject");
+  return ;
+}
+
 /* This is similar to cJSON_AddStringToObject but takes (DATA,
  * DATALEN) and adds it under NAME as a base 64 encoded string to
  * OBJECT.  */
@@ -256,7 +316,8 @@ add_base64_to_object (cjson_t object, const char *name,
 /* Create a JSON error object.  If JSON is not NULL the error message
  * is appended to that object.  An existing "type" item will be replaced. */
 static cjson_t
-error_object_v (cjson_t json, const char *message, va_list arg_ptr)
+error_object_v (cjson_t json, const char *message, va_list arg_ptr,
+                gpg_error_t err)
 {
   cjson_t response, j_tmp;
   char *msg;
@@ -277,8 +338,10 @@ error_object_v (cjson_t json, const char *message, va_list arg_ptr)
       cJSON_ReplaceItemInObject (response, "type", j_tmp);
      }
   xjson_AddStringToObject (response, "msg", msg);
-
   xfree (msg);
+
+  xjson_AddNumberToObject (response, "code", err);
+
   return response;
 }
 
@@ -302,7 +365,20 @@ error_object (cjson_t json, const char *message, ...)
   va_list arg_ptr;
 
   va_start (arg_ptr, message);
-  response = error_object_v (json, message, arg_ptr);
+  response = error_object_v (json, message, arg_ptr, 0);
+  va_end (arg_ptr);
+  return response;
+}
+
+
+static cjson_t
+gpg_error_object (cjson_t json, gpg_error_t err, const char *message, ...)
+{
+  cjson_t response;
+  va_list arg_ptr;
+
+  va_start (arg_ptr, message);
+  response = error_object_v (json, message, arg_ptr, err);
   va_end (arg_ptr);
   return response;
 }
@@ -316,7 +392,7 @@ error_object_string (const char *message, ...)
   char *msg;
 
   va_start (arg_ptr, message);
-  response = error_object_v (NULL, message, arg_ptr);
+  response = error_object_v (NULL, message, arg_ptr, 0);
   va_end (arg_ptr);
 
   msg = xjson_Print (response);
@@ -398,12 +474,13 @@ get_chunksize (cjson_t json, size_t *r_chunksize)
 }
 
 
-/* Extract the keys from the "keys" array in the JSON object.  On
- * success a string with the keys identifiers is stored at R_KEYS.
+/* Extract the keys from the array or string with the name "name"
+ * in the JSON object.  On success a string with the keys identifiers
+ * is stored at R_KEYS.
  * The keys in that string are LF delimited.  On failure an error code
  * is returned.  */
 static gpg_error_t
-get_keys (cjson_t json, char **r_keystring)
+get_keys (cjson_t json, const char *name, char **r_keystring)
 {
   cjson_t j_keys, j_item;
   int i, nkeys;
@@ -412,7 +489,7 @@ get_keys (cjson_t json, char **r_keystring)
 
   *r_keystring = NULL;
 
-  j_keys = cJSON_GetObjectItem (json, "keys");
+  j_keys = cJSON_GetObjectItem (json, name);
   if (!j_keys)
     return gpg_error (GPG_ERR_NO_KEY);
   if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys))
@@ -490,12 +567,12 @@ _create_new_context (gpgme_protocol_t proto)
 
 
 /* Return a context object for protocol PROTO.  This is currently a
- * statuically allocated context initialized for PROTO.  Termnates
+ * statically allocated context initialized for PROTO.  Terminates
  * process on failure.  */
 static gpgme_ctx_t
 get_context (gpgme_protocol_t proto)
 {
-  static gpgme_ctx_t ctx_openpgp, ctx_cms;
+  static gpgme_ctx_t ctx_openpgp, ctx_cms, ctx_conf;
 
   if (proto == GPGME_PROTOCOL_OpenPGP)
     {
@@ -509,12 +586,17 @@ get_context (gpgme_protocol_t proto)
         ctx_cms = _create_new_context (proto);
       return ctx_cms;
     }
+  else if (proto == GPGME_PROTOCOL_GPGCONF)
+    {
+      if (!ctx_conf)
+        ctx_conf = _create_new_context (proto);
+      return ctx_conf;
+    }
   else
     log_bug ("invalid protocol %d requested\n", proto);
 }
 
 
-
 /* Free context object retrieved by get_context.  */
 static void
 release_context (gpgme_ctx_t ctx)
@@ -524,6 +606,23 @@ release_context (gpgme_ctx_t ctx)
 }
 
 
+/* Create an addition context for short operations. */
+static gpgme_ctx_t
+create_onetime_context (gpgme_protocol_t proto)
+{
+  return _create_new_context (proto);
+
+}
+
+
+/* Release a one-time context.  */
+static void
+release_onetime_context (gpgme_ctx_t ctx)
+{
+  return gpgme_release (ctx);
+
+}
+
 
 /* Given a Base-64 encoded string object in JSON return a gpgme data
  * object at R_DATA.  */
@@ -590,369 +689,2374 @@ data_from_base64_string (gpgme_data_t *r_data, cjson_t json)
 }
 
 
-\f
-/*
- * Implementation of the commands.
- */
+/* Create a keylist pattern array from a json keys object
+ * in the request. Returns either a malloced NULL terminated
+ * string array which can be used as patterns for
+ * op_keylist_ext or NULL. */
+static char **
+create_keylist_patterns (cjson_t request, const char *name)
+{
+  char *keystring;
+  char *p;
+  char *tmp;
+  char **ret;
+  int cnt = 2; /* Last NULL and one is not newline delimited */
+  int i = 0;
 
+  if (get_keys (request, name, &keystring))
+    return NULL;
 
-/* Create a "data" object and the "type", "base64" and "more" flags
- * from DATA and append them to RESULT.  Ownership if DATA is
- * transferred to this function.  TYPE must be a fixed string.
- * CHUNKSIZE is the chunksize requested from the caller.  If BASE64 is
- * -1 the need for base64 encoding is determined by the content of
- * DATA, all other values are take as rtue or false.  Note that
- * op_getmore has similar code but works on PENDING_DATA which is set
- * here.  */
+  for (p = keystring; *p; p++)
+    if (*p == '\n')
+      cnt++;
+
+  ret = xcalloc (cnt, sizeof *ret);
+
+  for (p = keystring, tmp = keystring; *p; p++)
+    {
+      if (*p != '\n')
+        continue;
+      *p = '\0';
+      ret[i++] = xstrdup (tmp);
+      tmp = p + 1;
+    }
+  /* The last key is not newline delimted. */
+  ret[i] = *tmp ? xstrdup (tmp) : NULL;
+
+  xfree (keystring);
+  return ret;
+}
+
+
+/* Do a secret keylisting for protocol proto and add the fingerprints of
+   the secret keys for patterns to the result as "sec-fprs" array. */
 static gpg_error_t
-make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize,
-                  const char *type, int base64)
+add_secret_fprs (const char **patterns, gpgme_protocol_t protocol,
+                 cjson_t result)
 {
+  gpgme_ctx_t ctx;
   gpg_error_t err;
-  char *buffer;
-  size_t buflen;
-  int c;
+  gpgme_key_t key = NULL;
+  cjson_t j_fprs = xjson_CreateArray ();
 
-  if (!base64 || base64 == -1) /* Make sure that we really have a string.  */
-    gpgme_data_write (data, "", 1);
+  ctx = create_onetime_context (protocol);
 
-  buffer = gpgme_data_release_and_get_mem (data, &buflen);
-  data = NULL;
-  if (!buffer)
+  gpgme_set_keylist_mode (ctx, GPGME_KEYLIST_MODE_LOCAL |
+                               GPGME_KEYLIST_MODE_WITH_SECRET);
+
+  err = gpgme_op_keylist_ext_start (ctx, patterns, 1, 0);
+
+  if (err)
     {
-      err = gpg_error_from_syserror ();
+      gpg_error_object (result, err, "Error listing keys: %s",
+                        gpg_strerror (err));
       goto leave;
     }
 
-  if (base64 == -1)
+  while (!(err = gpgme_op_keylist_next (ctx, &key)))
     {
-      base64 = 0;
-      if (!buflen)
-        log_fatal ("Appended Nul byte got lost\n");
-      if (memchr (buffer, 0, buflen-1))
-        {
-          buflen--; /* Adjust for the extra nul byte.  */
-          base64 = 1;
-        }
-      /* Fixme: We might want to do more advanced heuristics than to
-       * only look for a Nul.  */
+      if (!key || !key->fpr)
+        continue;
+      cJSON_AddItemToArray (j_fprs, cJSON_CreateString (key->fpr));
+      gpgme_key_unref (key);
+      key = NULL;
     }
+  err = 0;
 
-  /* Adjust the chunksize if we need to do base64 conversion.  */
-  if (base64)
-    chunksize = (chunksize / 4) * 3;
+  release_onetime_context (ctx);
+  ctx = NULL;
 
-  xjson_AddStringToObject (result, "type", type);
-  xjson_AddBoolToObject (result, "base64", base64);
+  xjson_AddItemToObject (result, "sec-fprs", j_fprs);
+
+leave:
+  release_onetime_context (ctx);
+  gpgme_key_unref (key);
+
+  return err;
+}
+
+
+/* Create sigsum json array */
+static cjson_t
+sigsum_to_json (gpgme_sigsum_t summary)
+{
+  cjson_t result = xjson_CreateObject ();
+  cjson_t sigsum_array = xjson_CreateArray ();
+
+  if ( (summary & GPGME_SIGSUM_VALID      ))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("valid"));
+  if ( (summary & GPGME_SIGSUM_GREEN      ))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("green"));
+  if ( (summary & GPGME_SIGSUM_RED        ))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("red"));
+  if ( (summary & GPGME_SIGSUM_KEY_REVOKED))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("revoked"));
+  if ( (summary & GPGME_SIGSUM_KEY_EXPIRED))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("key-expired"));
+  if ( (summary & GPGME_SIGSUM_SIG_EXPIRED))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("sig-expired"));
+  if ( (summary & GPGME_SIGSUM_KEY_MISSING))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("key-missing"));
+  if ( (summary & GPGME_SIGSUM_CRL_MISSING))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("crl-missing"));
+  if ( (summary & GPGME_SIGSUM_CRL_TOO_OLD))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("crl-too-old"));
+  if ( (summary & GPGME_SIGSUM_BAD_POLICY ))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("bad-policy"));
+  if ( (summary & GPGME_SIGSUM_SYS_ERROR  ))
+    cJSON_AddItemToArray (sigsum_array,
+        cJSON_CreateString ("sys-error"));
+  /* The signature summary as string array. */
+  xjson_AddItemToObject (result, "sigsum", sigsum_array);
+
+  /* Bools for the same. */
+  xjson_AddBoolToObject (result, "valid",
+                         (summary & GPGME_SIGSUM_VALID      ));
+  xjson_AddBoolToObject (result, "green",
+                         (summary & GPGME_SIGSUM_GREEN      ));
+  xjson_AddBoolToObject (result, "red",
+                         (summary & GPGME_SIGSUM_RED        ));
+  xjson_AddBoolToObject (result, "revoked",
+                         (summary & GPGME_SIGSUM_KEY_REVOKED));
+  xjson_AddBoolToObject (result, "key-expired",
+                         (summary & GPGME_SIGSUM_KEY_EXPIRED));
+  xjson_AddBoolToObject (result, "sig-expired",
+                         (summary & GPGME_SIGSUM_SIG_EXPIRED));
+  xjson_AddBoolToObject (result, "key-missing",
+                         (summary & GPGME_SIGSUM_KEY_MISSING));
+  xjson_AddBoolToObject (result, "crl-missing",
+                         (summary & GPGME_SIGSUM_CRL_MISSING));
+  xjson_AddBoolToObject (result, "crl-too-old",
+                         (summary & GPGME_SIGSUM_CRL_TOO_OLD));
+  xjson_AddBoolToObject (result, "bad-policy",
+                         (summary & GPGME_SIGSUM_BAD_POLICY ));
+  xjson_AddBoolToObject (result, "sys-error",
+                         (summary & GPGME_SIGSUM_SYS_ERROR  ));
+
+  return result;
+}
+
+
+/* Helper for summary formatting */
+static const char *
+validity_to_string (gpgme_validity_t val)
+{
+  switch (val)
+    {
+    case GPGME_VALIDITY_UNDEFINED:return "undefined";
+    case GPGME_VALIDITY_NEVER:    return "never";
+    case GPGME_VALIDITY_MARGINAL: return "marginal";
+    case GPGME_VALIDITY_FULL:     return "full";
+    case GPGME_VALIDITY_ULTIMATE: return "ultimate";
+    case GPGME_VALIDITY_UNKNOWN:
+    default:                      return "unknown";
+    }
+}
 
-  if (buflen > chunksize)
+static const char *
+protocol_to_string (gpgme_protocol_t proto)
+{
+  switch (proto)
     {
-      xjson_AddBoolToObject (result, "more", 1);
+    case GPGME_PROTOCOL_OpenPGP: return "OpenPGP";
+    case GPGME_PROTOCOL_CMS:     return "CMS";
+    case GPGME_PROTOCOL_GPGCONF: return "gpgconf";
+    case GPGME_PROTOCOL_ASSUAN:  return "assuan";
+    case GPGME_PROTOCOL_G13:     return "g13";
+    case GPGME_PROTOCOL_UISERVER:return "uiserver";
+    case GPGME_PROTOCOL_SPAWN:   return "spawn";
+    default:
+                                 return "unknown";
+    }
+}
 
-      c = buffer[chunksize];
-      buffer[chunksize] = 0;
-      if (base64)
-        err = add_base64_to_object (result, "data", buffer, chunksize);
-      else
-        err = cjson_AddStringToObject (result, "data", buffer);
-      buffer[chunksize] = c;
-      if (err)
-        goto leave;
+/* Create a sig_notation json object */
+static cjson_t
+sig_notation_to_json (gpgme_sig_notation_t not)
+{
+  cjson_t result = xjson_CreateObject ();
+  xjson_AddBoolToObject (result, "human_readable", not->human_readable);
+  xjson_AddBoolToObject (result, "critical", not->critical);
+
+  xjson_AddStringToObject0 (result, "name", not->name);
+  xjson_AddStringToObject0 (result, "value", not->value);
+
+  xjson_AddNumberToObject (result, "flags", not->flags);
+
+  return result;
+}
 
-      pending_data.buffer = buffer;
-      buffer = NULL;
-      pending_data.length = buflen;
-      pending_data.written = chunksize;
-      pending_data.type = type;
-      pending_data.base64 = base64;
+/* Create a key_sig json object */
+static cjson_t
+key_sig_to_json (gpgme_key_sig_t sig)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddBoolToObject (result, "revoked", sig->revoked);
+  xjson_AddBoolToObject (result, "expired", sig->expired);
+  xjson_AddBoolToObject (result, "invalid", sig->invalid);
+  xjson_AddBoolToObject (result, "exportable", sig->exportable);
+
+  xjson_AddStringToObject0 (result, "pubkey_algo_name",
+                            gpgme_pubkey_algo_name (sig->pubkey_algo));
+  xjson_AddStringToObject0 (result, "keyid", sig->keyid);
+  xjson_AddStringToObject0 (result, "status", gpgme_strerror (sig->status));
+  xjson_AddStringToObject0 (result, "name", sig->name);
+  xjson_AddStringToObject0 (result, "email", sig->email);
+  xjson_AddStringToObject0 (result, "comment", sig->comment);
+
+  xjson_AddNumberToObject (result, "pubkey_algo", sig->pubkey_algo);
+  xjson_AddNumberToObject (result, "timestamp", sig->timestamp);
+  xjson_AddNumberToObject (result, "expires", sig->expires);
+  xjson_AddNumberToObject (result, "status_code", sig->status);
+  xjson_AddNumberToObject (result, "sig_class", sig->sig_class);
+
+  if (sig->notations)
+    {
+      gpgme_sig_notation_t not;
+      cjson_t array = xjson_CreateArray ();
+      for (not = sig->notations; not; not = not->next)
+        cJSON_AddItemToArray (array, sig_notation_to_json (not));
+      xjson_AddItemToObject (result, "notations", array);
     }
-  else
+
+  return result;
+}
+
+/* Create a tofu info object */
+static cjson_t
+tofu_to_json (gpgme_tofu_info_t tofu)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "description", tofu->description);
+
+  xjson_AddNumberToObject (result, "validity", tofu->validity);
+  xjson_AddNumberToObject (result, "policy", tofu->policy);
+  xjson_AddNumberToObject (result, "signcount", tofu->signcount);
+  xjson_AddNumberToObject (result, "encrcount", tofu->encrcount);
+  xjson_AddNumberToObject (result, "signfirst", tofu->signfirst);
+  xjson_AddNumberToObject (result, "signlast", tofu->signlast);
+  xjson_AddNumberToObject (result, "encrfirst", tofu->encrfirst);
+  xjson_AddNumberToObject (result, "encrlast", tofu->encrlast);
+
+  return result;
+}
+
+/* Create a userid json object */
+static cjson_t
+uid_to_json (gpgme_user_id_t uid)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddBoolToObject (result, "revoked", uid->revoked);
+  xjson_AddBoolToObject (result, "invalid", uid->invalid);
+
+  xjson_AddStringToObject0 (result, "validity",
+                            validity_to_string (uid->validity));
+  xjson_AddStringToObject0 (result, "uid", uid->uid);
+  xjson_AddStringToObject0 (result, "name", uid->name);
+  xjson_AddStringToObject0 (result, "email", uid->email);
+  xjson_AddStringToObject0 (result, "comment", uid->comment);
+  xjson_AddStringToObject0 (result, "address", uid->address);
+
+  xjson_AddNumberToObject (result, "origin", uid->origin);
+  xjson_AddNumberToObject (result, "last_update", uid->last_update);
+
+  /* Key sigs */
+  if (uid->signatures)
     {
-      if (base64)
-        err = add_base64_to_object (result, "data", buffer, buflen);
-      else
-        err = cjson_AddStringToObject (result, "data", buffer);
+      cjson_t sig_array = xjson_CreateArray ();
+      gpgme_key_sig_t sig;
+
+      for (sig = uid->signatures; sig; sig = sig->next)
+        cJSON_AddItemToArray (sig_array, key_sig_to_json (sig));
+
+      xjson_AddItemToObject (result, "signatures", sig_array);
     }
 
- leave:
-  gpgme_free (buffer);
-  return err;
+  /* TOFU info */
+  if (uid->tofu)
+    {
+      gpgme_tofu_info_t tofu;
+      cjson_t array = xjson_CreateArray ();
+      for (tofu = uid->tofu; tofu; tofu = tofu->next)
+        cJSON_AddItemToArray (array, tofu_to_json (tofu));
+      xjson_AddItemToObject (result, "tofu", array);
+    }
+
+  return result;
 }
 
+/* Create a subkey json object */
+static cjson_t
+subkey_to_json (gpgme_subkey_t sub)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddBoolToObject (result, "revoked", sub->revoked);
+  xjson_AddBoolToObject (result, "expired", sub->expired);
+  xjson_AddBoolToObject (result, "disabled", sub->disabled);
+  xjson_AddBoolToObject (result, "invalid", sub->invalid);
+  xjson_AddBoolToObject (result, "can_encrypt", sub->can_encrypt);
+  xjson_AddBoolToObject (result, "can_sign", sub->can_sign);
+  xjson_AddBoolToObject (result, "can_certify", sub->can_certify);
+  xjson_AddBoolToObject (result, "can_authenticate", sub->can_authenticate);
+  xjson_AddBoolToObject (result, "secret", sub->secret);
+  xjson_AddBoolToObject (result, "is_qualified", sub->is_qualified);
+  xjson_AddBoolToObject (result, "is_cardkey", sub->is_cardkey);
+  xjson_AddBoolToObject (result, "is_de_vs", sub->is_de_vs);
+
+  xjson_AddStringToObject0 (result, "pubkey_algo_name",
+                            gpgme_pubkey_algo_name (sub->pubkey_algo));
+  xjson_AddStringToObject0 (result, "pubkey_algo_string",
+                            gpgme_pubkey_algo_string (sub));
+  xjson_AddStringToObject0 (result, "keyid", sub->keyid);
+  xjson_AddStringToObject0 (result, "card_number", sub->card_number);
+  xjson_AddStringToObject0 (result, "curve", sub->curve);
+  xjson_AddStringToObject0 (result, "keygrip", sub->keygrip);
+
+  xjson_AddNumberToObject (result, "pubkey_algo", sub->pubkey_algo);
+  xjson_AddNumberToObject (result, "length", sub->length);
+  xjson_AddNumberToObject (result, "timestamp", sub->timestamp);
+  xjson_AddNumberToObject (result, "expires", sub->expires);
 
-\f
-static const char hlp_encrypt[] =
-  "op:     \"encrypt\"\n"
-  "keys:   Array of strings with the fingerprints or user-ids\n"
-  "        of the keys to encrypt the data.  For a single key\n"
-  "        a String may be used instead of an array.\n"
-  "data:   Input data. \n"
-  "\n"
-  "Optional parameters:\n"
-  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
-  "chunksize:     Max number of bytes in the resulting \"data\".\n"
-  "\n"
-  "Optional boolean flags (default is false):\n"
-  "base64:        Input data is base64 encoded.\n"
-  "mime:          Indicate that data is a MIME object.\n"
-  "armor:         Request output in armored format.\n"
-  "always-trust:  Request --always-trust option.\n"
-  "no-encrypt-to: Do not use a default recipient.\n"
-  "no-compress:   Do not compress the plaintext first.\n"
-  "throw-keyids:  Request the --throw-keyids option.\n"
-  "want-address:  Require that the keys include a mail address.\n"
-  "wrap:          Assume the input is an OpenPGP message.\n"
+  return result;
+}
+
+/* Create a key json object */
+static cjson_t
+key_to_json (gpgme_key_t key)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddBoolToObject (result, "revoked", key->revoked);
+  xjson_AddBoolToObject (result, "expired", key->expired);
+  xjson_AddBoolToObject (result, "disabled", key->disabled);
+  xjson_AddBoolToObject (result, "invalid", key->invalid);
+  xjson_AddBoolToObject (result, "can_encrypt", key->can_encrypt);
+  xjson_AddBoolToObject (result, "can_sign", key->can_sign);
+  xjson_AddBoolToObject (result, "can_certify", key->can_certify);
+  xjson_AddBoolToObject (result, "can_authenticate", key->can_authenticate);
+  xjson_AddBoolToObject (result, "secret", key->secret);
+  xjson_AddBoolToObject (result, "is_qualified", key->is_qualified);
+
+  xjson_AddStringToObject0 (result, "protocol",
+                            protocol_to_string (key->protocol));
+  xjson_AddStringToObject0 (result, "issuer_serial", key->issuer_serial);
+  xjson_AddStringToObject0 (result, "issuer_name", key->issuer_name);
+  xjson_AddStringToObject0 (result, "fingerprint", key->fpr);
+  xjson_AddStringToObject0 (result, "chain_id", key->chain_id);
+  xjson_AddStringToObject0 (result, "owner_trust",
+                            validity_to_string (key->owner_trust));
+
+  xjson_AddNumberToObject (result, "origin", key->origin);
+  xjson_AddNumberToObject (result, "last_update", key->last_update);
+
+  /* Add subkeys */
+  if (key->subkeys)
+    {
+      cjson_t subkey_array = xjson_CreateArray ();
+      gpgme_subkey_t sub;
+      for (sub = key->subkeys; sub; sub = sub->next)
+        cJSON_AddItemToArray (subkey_array, subkey_to_json (sub));
+
+      xjson_AddItemToObject (result, "subkeys", subkey_array);
+    }
+
+  /* User Ids */
+  if (key->uids)
+    {
+      cjson_t uid_array = xjson_CreateArray ();
+      gpgme_user_id_t uid;
+      for (uid = key->uids; uid; uid = uid->next)
+        cJSON_AddItemToArray (uid_array, uid_to_json (uid));
+
+      xjson_AddItemToObject (result, "userids", uid_array);
+    }
+
+  return result;
+}
+
+
+/* Create a signature json object */
+static cjson_t
+signature_to_json (gpgme_signature_t sig)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddItemToObject (result, "summary", sigsum_to_json (sig->summary));
+
+  xjson_AddBoolToObject (result, "wrong_key_usage", sig->wrong_key_usage);
+  xjson_AddBoolToObject (result, "chain_model", sig->chain_model);
+  xjson_AddBoolToObject (result, "is_de_vs", sig->is_de_vs);
+
+  xjson_AddStringToObject0 (result, "status_string",
+                            gpgme_strerror (sig->status));
+  xjson_AddStringToObject0 (result, "fingerprint", sig->fpr);
+  xjson_AddStringToObject0 (result, "validity_string",
+                            validity_to_string (sig->validity));
+  xjson_AddStringToObject0 (result, "pubkey_algo_name",
+                            gpgme_pubkey_algo_name (sig->pubkey_algo));
+  xjson_AddStringToObject0 (result, "hash_algo_name",
+                            gpgme_hash_algo_name (sig->hash_algo));
+  xjson_AddStringToObject0 (result, "pka_address", sig->pka_address);
+
+  xjson_AddNumberToObject (result, "status_code", sig->status);
+  xjson_AddNumberToObject (result, "timestamp", sig->timestamp);
+  xjson_AddNumberToObject (result, "exp_timestamp", sig->exp_timestamp);
+  xjson_AddNumberToObject (result, "pka_trust", sig->pka_trust);
+  xjson_AddNumberToObject (result, "validity", sig->validity);
+  xjson_AddNumberToObject (result, "validity_reason", sig->validity_reason);
+
+  if (sig->notations)
+    {
+      gpgme_sig_notation_t not;
+      cjson_t array = xjson_CreateArray ();
+      for (not = sig->notations; not; not = not->next)
+        cJSON_AddItemToArray (array, sig_notation_to_json (not));
+      xjson_AddItemToObject (result, "notations", array);
+    }
+
+  return result;
+}
+
+
+/* Create a JSON object from a gpgme_verify result */
+static cjson_t
+verify_result_to_json (gpgme_verify_result_t verify_result)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddBoolToObject (result, "is_mime", verify_result->is_mime);
+
+  if (verify_result->signatures)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_signature_t sig;
+
+      for (sig = verify_result->signatures; sig; sig = sig->next)
+        cJSON_AddItemToArray (array, signature_to_json (sig));
+      xjson_AddItemToObject (result, "signatures", array);
+    }
+
+  return result;
+}
+
+/* Create a recipient json object */
+static cjson_t
+recipient_to_json (gpgme_recipient_t recp)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "keyid", recp->keyid);
+  xjson_AddStringToObject0 (result, "pubkey_algo_name",
+                            gpgme_pubkey_algo_name (recp->pubkey_algo));
+  xjson_AddStringToObject0 (result, "status_string",
+                            gpgme_strerror (recp->status));
+
+  xjson_AddNumberToObject (result, "status_code", recp->status);
+
+  return result;
+}
+
+
+/* Create a JSON object from a gpgme_decrypt result */
+static cjson_t
+decrypt_result_to_json (gpgme_decrypt_result_t decrypt_result)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "file_name", decrypt_result->file_name);
+  xjson_AddStringToObject0 (result, "symkey_algo",
+                            decrypt_result->symkey_algo);
+
+  xjson_AddBoolToObject (result, "wrong_key_usage",
+                         decrypt_result->wrong_key_usage);
+  xjson_AddBoolToObject (result, "is_de_vs",
+                         decrypt_result->is_de_vs);
+  xjson_AddBoolToObject (result, "is_mime", decrypt_result->is_mime);
+  xjson_AddBoolToObject (result, "legacy_cipher_nomdc",
+                         decrypt_result->legacy_cipher_nomdc);
+
+  if (decrypt_result->recipients)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_recipient_t recp;
+
+      for (recp = decrypt_result->recipients; recp; recp = recp->next)
+        cJSON_AddItemToArray (array, recipient_to_json (recp));
+      xjson_AddItemToObject (result, "recipients", array);
+    }
+
+  return result;
+}
+
+
+/* Create a JSON object from an engine_info */
+static cjson_t
+engine_info_to_json (gpgme_engine_info_t info)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "protocol",
+                            protocol_to_string (info->protocol));
+  xjson_AddStringToObject0 (result, "fname", info->file_name);
+  xjson_AddStringToObject0 (result, "version", info->version);
+  xjson_AddStringToObject0 (result, "req_version", info->req_version);
+  xjson_AddStringToObject0 (result, "homedir", info->home_dir ?
+                                                info->home_dir :
+                                                "default");
+  return result;
+}
+
+
+/* Create a JSON object from an import_status */
+static cjson_t
+import_status_to_json (gpgme_import_status_t sts)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "fingerprint", sts->fpr);
+  xjson_AddStringToObject0 (result, "error_string",
+                            gpgme_strerror (sts->result));
+
+  xjson_AddNumberToObject (result, "status", sts->status);
+
+  return result;
+}
+
+/* Create a JSON object from an import result */
+static cjson_t
+import_result_to_json (gpgme_import_result_t imp)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddNumberToObject (result, "considered", imp->considered);
+  xjson_AddNumberToObject (result, "no_user_id", imp->no_user_id);
+  xjson_AddNumberToObject (result, "imported", imp->imported);
+  xjson_AddNumberToObject (result, "imported_rsa", imp->imported_rsa);
+  xjson_AddNumberToObject (result, "unchanged", imp->unchanged);
+  xjson_AddNumberToObject (result, "new_user_ids", imp->new_user_ids);
+  xjson_AddNumberToObject (result, "new_sub_keys", imp->new_sub_keys);
+  xjson_AddNumberToObject (result, "new_signatures", imp->new_signatures);
+  xjson_AddNumberToObject (result, "new_revocations", imp->new_revocations);
+  xjson_AddNumberToObject (result, "secret_read", imp->secret_read);
+  xjson_AddNumberToObject (result, "secret_imported", imp->secret_imported);
+  xjson_AddNumberToObject (result, "secret_unchanged", imp->secret_unchanged);
+  xjson_AddNumberToObject (result, "skipped_new_keys", imp->skipped_new_keys);
+  xjson_AddNumberToObject (result, "not_imported", imp->not_imported);
+  xjson_AddNumberToObject (result, "skipped_v3_keys", imp->skipped_v3_keys);
+
+
+  if (imp->imports)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_import_status_t status;
+
+      for (status = imp->imports; status; status = status->next)
+        cJSON_AddItemToArray (array, import_status_to_json (status));
+      xjson_AddItemToObject (result, "imports", array);
+    }
+
+  return result;
+}
+
+
+/* Create a JSON object from a gpgconf arg */
+static cjson_t
+conf_arg_to_json (gpgme_conf_arg_t arg, gpgme_conf_type_t type)
+{
+  cjson_t result = xjson_CreateObject ();
+  int is_none = 0;
+  switch (type)
+    {
+      case GPGME_CONF_STRING:
+      case GPGME_CONF_PATHNAME:
+      case GPGME_CONF_LDAP_SERVER:
+      case GPGME_CONF_KEY_FPR:
+      case GPGME_CONF_PUB_KEY:
+      case GPGME_CONF_SEC_KEY:
+      case GPGME_CONF_ALIAS_LIST:
+        xjson_AddStringToObject0 (result, "string", arg->value.string);
+        break;
+
+      case GPGME_CONF_UINT32:
+        xjson_AddNumberToObject (result, "number", arg->value.uint32);
+        break;
+
+      case GPGME_CONF_INT32:
+        xjson_AddNumberToObject (result, "number", arg->value.int32);
+        break;
+
+      case GPGME_CONF_NONE:
+      default:
+        is_none = 1;
+        break;
+    }
+  xjson_AddBoolToObject (result, "is_none", is_none);
+  return result;
+}
+
+
+/* Create a JSON object from a gpgconf option */
+static cjson_t
+conf_opt_to_json (gpgme_conf_opt_t opt)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "name", opt->name);
+  xjson_AddStringToObject0 (result, "description", opt->description);
+  xjson_AddStringToObject0 (result, "argname", opt->argname);
+  xjson_AddStringToObject0 (result, "default_description",
+                            opt->default_description);
+  xjson_AddStringToObject0 (result, "no_arg_description",
+                            opt->no_arg_description);
+
+  xjson_AddNumberToObject (result, "flags", opt->flags);
+  xjson_AddNumberToObject (result, "level", opt->level);
+  xjson_AddNumberToObject (result, "type", opt->type);
+  xjson_AddNumberToObject (result, "alt_type", opt->alt_type);
+
+  if (opt->default_value)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_conf_arg_t arg;
+
+      for (arg = opt->default_value; arg; arg = arg->next)
+        cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type));
+      xjson_AddItemToObject (result, "default_value", array);
+    }
+
+  if (opt->no_arg_value)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_conf_arg_t arg;
+
+      for (arg = opt->no_arg_value; arg; arg = arg->next)
+        cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type));
+      xjson_AddItemToObject (result, "no_arg_value", array);
+    }
+
+  if (opt->value)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_conf_arg_t arg;
+
+      for (arg = opt->value; arg; arg = arg->next)
+        cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type));
+      xjson_AddItemToObject (result, "value", array);
+    }
+  return result;
+}
+
+
+/* Create a JSON object from a gpgconf component*/
+static cjson_t
+conf_comp_to_json (gpgme_conf_comp_t cmp)
+{
+  cjson_t result = xjson_CreateObject ();
+
+  xjson_AddStringToObject0 (result, "name", cmp->name);
+  xjson_AddStringToObject0 (result, "description", cmp->description);
+  xjson_AddStringToObject0 (result, "program_name", cmp->program_name);
+
+
+  if (cmp->options)
+    {
+      cjson_t array = xjson_CreateArray ();
+      gpgme_conf_opt_t opt;
+
+      for (opt = cmp->options; opt; opt = opt->next)
+        cJSON_AddItemToArray (array, conf_opt_to_json (opt));
+      xjson_AddItemToObject (result, "options", array);
+    }
+
+  return result;
+}
+
+
+/* Create a gpgme_data from json string data named "name"
+ * in the request. Takes the base64 option into account.
+ *
+ * Adds an error to the "result" on error. */
+static gpg_error_t
+get_string_data (cjson_t request, cjson_t result, const char *name,
+                 gpgme_data_t *r_data)
+{
+  gpgme_error_t err;
+  int opt_base64;
+  cjson_t j_data;
+
+  if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
+    return err;
+
+  /* Get the data.  Note that INPUT is a shallow data object with the
+   * storage hold in REQUEST.  */
+  j_data = cJSON_GetObjectItem (request, name);
+  if (!j_data)
+    {
+      return gpg_error (GPG_ERR_NO_DATA);
+    }
+  if (!cjson_is_string (j_data))
+    {
+      return gpg_error (GPG_ERR_INV_VALUE);
+    }
+  if (opt_base64)
+    {
+      err = data_from_base64_string (r_data, j_data);
+      if (err)
+        {
+          gpg_error_object (result, err,
+                            "Error decoding Base-64 encoded '%s': %s",
+                            name, gpg_strerror (err));
+          return err;
+        }
+    }
+  else
+    {
+      err = gpgme_data_new_from_mem (r_data, j_data->valuestring,
+                                     strlen (j_data->valuestring), 0);
+      if (err)
+        {
+          gpg_error_object (result, err, "Error getting '%s': %s",
+                            name, gpg_strerror (err));
+          return err;
+        }
+    }
+  return 0;
+}
+
+
+/* Create a "data" object and the "type" and "base64" flags
+ * from DATA and append them to RESULT.  Ownership of DATA is
+ * transferred to this function.  TYPE must be a fixed string.
+ * If BASE64 is -1 the need for base64 encoding is determined
+ * by the content of DATA, all other values are taken as true
+ * or false. */
+static gpg_error_t
+make_data_object (cjson_t result, gpgme_data_t data,
+                  const char *type, int base64)
+{
+  gpg_error_t err;
+  char *buffer;
+  const char *s;
+  size_t buflen, n;
+
+  if (!base64 || base64 == -1) /* Make sure that we really have a string.  */
+    gpgme_data_write (data, "", 1);
+
+  buffer = gpgme_data_release_and_get_mem (data, &buflen);
+  data = NULL;
+  if (!buffer)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  if (base64 == -1)
+    {
+      base64 = 0;
+      if (!buflen)
+        log_fatal ("Appended Nul byte got lost\n");
+      /* Figure out if there is any Nul octet in the buffer.  In that
+       * case we need to Base-64 the buffer.  Due to problems with the
+       * browser's Javascript we use Base-64 also in case an UTF-8
+       * character is in the buffer.  This is because the chunking may
+       * split an UTF-8 characters and JS can't handle this.  */
+      for (s=buffer, n=0; n < buflen -1; s++, n++)
+        if (!*s || (*s & 0x80))
+          {
+            buflen--; /* Adjust for the extra nul byte.  */
+            base64 = 1;
+            break;
+          }
+    }
+
+  xjson_AddStringToObject (result, "type", type);
+  xjson_AddBoolToObject (result, "base64", base64);
+
+  if (base64)
+    err = add_base64_to_object (result, "data", buffer, buflen);
+  else
+    err = cjson_AddStringToObject (result, "data", buffer);
+
+ leave:
+  gpgme_free (buffer);
+  return err;
+}
+
+
+/* Encode and chunk response.
+ *
+ * If neccessary this base64 encodes and chunks the repsonse
+ * for getmore so that we always return valid json independent
+ * of the chunksize.
+ *
+ * A chunked repsonse contains the base64 encoded chunk
+ * as a string and a boolean if there is still more data
+ * available for getmore like:
+ * {
+ *   chunk: "SGVsbG8gV29ybGQK"
+ *   more: true
+ * }
+ *
+ * Chunking is only done if the response is larger then the
+ * chunksize.
+ *
+ * caller has to xfree the return value.
+ */
+static char *
+encode_and_chunk (cjson_t request, cjson_t response)
+{
+  char *data;
+  gpg_error_t err = 0;
+  size_t chunksize = 0;
+  char *getmore_request = NULL;
+
+  if (opt_interactive)
+    data = cJSON_Print (response);
+  else
+    data = cJSON_PrintUnformatted (response);
+
+  if (!data)
+    {
+      err = GPG_ERR_NO_DATA;
+      goto leave;
+    }
+
+  if (!request)
+    {
+      goto leave;
+    }
+
+  if ((err = get_chunksize (request, &chunksize)))
+    {
+      err = GPG_ERR_INV_VALUE;
+      goto leave;
+    }
+
+  if (!chunksize)
+    goto leave;
+
+  pending_data.buffer = data;
+  /* Data should already be encoded so that it does not
+     contain 0.*/
+  pending_data.length = strlen (data);
+  pending_data.written = 0;
+
+  if (gpgrt_asprintf (&getmore_request,
+                  "{ \"op\":\"getmore\", \"chunksize\": %i }",
+                  (int) chunksize) == -1)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  data = process_request (getmore_request);
+
+leave:
+  xfree (getmore_request);
+
+  if (!err && !data)
+    {
+      err = GPG_ERR_GENERAL;
+    }
+
+  if (err)
+    {
+      cjson_t err_obj = gpg_error_object (NULL, err,
+                                          "Encode and chunk failed: %s",
+                                          gpgme_strerror (err));
+      xfree (data);
+      if (opt_interactive)
+        data = cJSON_Print (err_obj);
+      data = cJSON_PrintUnformatted (err_obj);
+
+      cJSON_Delete (err_obj);
+    }
+
+  return data;
+}
+
+
+\f
+/*
+ * Implementation of the commands.
+ */
+static const char hlp_encrypt[] =
+  "op:     \"encrypt\"\n"
+  "keys:   Array of strings with the fingerprints or user-ids\n"
+  "        of the keys to encrypt the data.  For a single key\n"
+  "        a String may be used instead of an array.\n"
+  "data:   Input data. \n"
+  "\n"
+  "Optional parameters:\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "signing_keys:  Similar to the keys parameter for added signing.\n"
+  "               (openpgp only)"
+  "file_name:     The file name associated with the data.\n"
+  "sender:        Sender info to embed in a signature.\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "base64:        Input data is base64 encoded.\n"
+  "mime:          Indicate that data is a MIME object.\n"
+  "armor:         Request output in armored format.\n"
+  "always-trust:  Request --always-trust option.\n"
+  "no-encrypt-to: Do not use a default recipient.\n"
+  "no-compress:   Do not compress the plaintext first.\n"
+  "throw-keyids:  Request the --throw-keyids option.\n"
+  "want-address:  Require that the keys include a mail address.\n"
+  "wrap:          Assume the input is an OpenPGP message.\n"
+  "\n"
+  "Response on success:\n"
+  "type:   \"ciphertext\"\n"
+  "data:   Unless armor mode is used a Base64 encoded binary\n"
+  "        ciphertext.  In armor mode a string with an armored\n"
+  "        OpenPGP or a PEM message.\n"
+  "base64: Boolean indicating whether data is base64 encoded.";
+static gpg_error_t
+op_encrypt (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_protocol_t protocol;
+  char **signing_patterns = NULL;
+  int opt_mime;
+  char *keystring = NULL;
+  char *file_name = NULL;
+  gpgme_data_t input = NULL;
+  gpgme_data_t output = NULL;
+  int abool;
+  gpgme_encrypt_flags_t encrypt_flags = 0;
+  gpgme_ctx_t keylist_ctx = NULL;
+  gpgme_key_t key = NULL;
+  cjson_t j_tmp = NULL;
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  if ((err = get_boolean_flag (request, "mime", 0, &opt_mime)))
+    goto leave;
+
+  if ((err = get_boolean_flag (request, "armor", 0, &abool)))
+    goto leave;
+  gpgme_set_armor (ctx, abool);
+  if ((err = get_boolean_flag (request, "always-trust", 0, &abool)))
+    goto leave;
+  if (abool)
+    encrypt_flags |= GPGME_ENCRYPT_ALWAYS_TRUST;
+  if ((err = get_boolean_flag (request, "no-encrypt-to", 0,&abool)))
+    goto leave;
+  if (abool)
+    encrypt_flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO;
+  if ((err = get_boolean_flag (request, "no-compress", 0, &abool)))
+    goto leave;
+  if (abool)
+    encrypt_flags |= GPGME_ENCRYPT_NO_COMPRESS;
+  if ((err = get_boolean_flag (request, "throw-keyids", 0, &abool)))
+    goto leave;
+  if (abool)
+    encrypt_flags |= GPGME_ENCRYPT_THROW_KEYIDS;
+  if ((err = get_boolean_flag (request, "wrap", 0, &abool)))
+    goto leave;
+  if (abool)
+    encrypt_flags |= GPGME_ENCRYPT_WRAP;
+  if ((err = get_boolean_flag (request, "want-address", 0, &abool)))
+    goto leave;
+  if (abool)
+    encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS;
+
+  j_tmp = cJSON_GetObjectItem (request, "file_name");
+  if (j_tmp && cjson_is_string (j_tmp))
+    {
+      file_name = j_tmp->valuestring;
+    }
+
+  j_tmp = cJSON_GetObjectItem (request, "sender");
+  if (j_tmp && cjson_is_string (j_tmp))
+    {
+      gpgme_set_sender (ctx, j_tmp->valuestring);
+    }
+
+  /* Get the keys.  */
+  err = get_keys (request, "keys", &keystring);
+  if (err)
+    {
+      /* Provide a custom error response.  */
+      gpg_error_object (result, err, "Error getting keys: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Do we have signing keys ? */
+  signing_patterns = create_keylist_patterns (request, "signing_keys");
+  if (signing_patterns)
+    {
+      keylist_ctx = create_onetime_context (protocol);
+      gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL);
+
+      err = gpgme_op_keylist_ext_start (keylist_ctx,
+                                        (const char **) signing_patterns,
+                                        1, 0);
+      if (err)
+        {
+          gpg_error_object (result, err, "Error listing keys: %s",
+                            gpg_strerror (err));
+          goto leave;
+        }
+      while (!(err = gpgme_op_keylist_next (keylist_ctx, &key)))
+        {
+          if ((err = gpgme_signers_add (ctx, key)))
+            {
+              gpg_error_object (result, err, "Error adding signer: %s",
+                                gpg_strerror (err));
+              goto leave;
+            }
+          gpgme_key_unref (key);
+          key = NULL;
+        }
+      release_onetime_context (keylist_ctx);
+      keylist_ctx = NULL;
+    }
+
+  if ((err = get_string_data (request, result, "data", &input)))
+      goto leave;
+
+  if (opt_mime)
+    gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME);
+
+  if (file_name)
+    {
+      gpgme_data_set_file_name (input, file_name);
+    }
+
+  /* Create an output data object.  */
+  err = gpgme_data_new (&output);
+  if (err)
+    {
+      gpg_error_object (result, err, "Error creating output data object: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Encrypt.  */
+  if (!signing_patterns)
+    {
+      err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags,
+                                  input, output);
+    }
+  else
+    {
+      err = gpgme_op_encrypt_sign_ext (ctx, NULL, keystring, encrypt_flags,
+                                       input, output);
+
+    }
+  /* encrypt_result = gpgme_op_encrypt_result (ctx); */
+  if (err)
+    {
+      gpg_error_object (result, err, "Encryption failed: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+  gpgme_data_release (input);
+  input = NULL;
+
+  /* We need to base64 if armoring has not been requested.  */
+  err = make_data_object (result, output,
+                          "ciphertext", !gpgme_get_armor (ctx));
+  output = NULL;
+
+ leave:
+  xfree_array (signing_patterns);
+  xfree (keystring);
+  release_onetime_context (keylist_ctx);
+  /* Reset sender in case the context is reused */
+  gpgme_set_sender (ctx, NULL);
+  gpgme_key_unref (key);
+  gpgme_signers_clear (ctx);
+  release_context (ctx);
+  gpgme_data_release (input);
+  gpgme_data_release (output);
+  return err;
+}
+
+
+\f
+static const char hlp_decrypt[] =
+  "op:     \"decrypt\"\n"
+  "data:   The encrypted data.\n"
+  "\n"
+  "Optional parameters:\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "base64:        Input data is base64 encoded.\n"
+  "\n"
+  "Response on success:\n"
+  "type:     \"plaintext\"\n"
+  "data:     The decrypted data.  This may be base64 encoded.\n"
+  "base64:   Boolean indicating whether data is base64 encoded.\n"
+  "mime:     deprecated - use dec_info is_mime instead\n"
+  "dec_info: An object with decryption information. (gpgme_decrypt_result_t)\n"
+  " Boolean values:\n"
+  "  wrong_key_usage:     Key should not have been used for encryption.\n"
+  "  is_de_vs:            Message was encrypted in compliance to the de-vs\n"
+  "                       mode.\n"
+  "  is_mime:             Message claims that the content is a MIME Message.\n"
+  "  legacy_cipher_nomdc: The message was made by a legacy algorithm\n"
+  "                       without integrity protection.\n"
+  " String values:\n"
+  "  file_name:   The filename contained in the decrypt result.\n"
+  "  symkey_algo: A string with the symmetric encryption algorithm and\n"
+  "               mode using the format \"<algo>.<mode>\".\n"
+  " Array values:\n"
+  "  recipients:  The list of recipients (gpgme_recipient_t).\n"
+  "   String values:\n"
+  "    keyid:            The keyid of the recipient.\n"
+  "    pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n"
+  "    status_string:    The status code as localized gpg-error string\n"
+  "   Number values:\n"
+  "    status_code:      The status as a number. (gpg_error_t)\n"
+  "info:     Optional an object with verification information.\n"
+  "          (gpgme_verify_result_t)\n"
+  " file_name: The filename contained in the verify result.\n"
+  " is_mime:   The is_mime info contained in the verify result.\n"
+  " signatures: Array of signatures\n"
+  "  summary: Object containing summary information.\n"
+  "   Boolean values: (Check gpgme_sigsum_t doc for meaning)\n"
+  "    valid\n"
+  "    green\n"
+  "    red\n"
+  "    revoked\n"
+  "    key-expired\n"
+  "    sig-expired\n"
+  "    key-missing\n"
+  "    crl-missing\n"
+  "    crl-too-old\n"
+  "    bad-policy\n"
+  "    sys-error\n"
+  "   sigsum: Array of strings representing the sigsum.\n"
+  "  Boolean values:\n"
+  "   wrong_key_usage: Key should not have been used for signing.\n"
+  "   chain_model:     Validity has been verified using the chain model.\n"
+  "   is_de_vs:        signature is in compliance to the de-vs mode.\n"
+  "  String values:\n"
+  "   status_string:      The status code as localized gpg-error string\n"
+  "   fingerprint:        The fingerprint of the signing key.\n"
+  "   validity_string:    The validity as string.\n"
+  "   pubkey_algo_name:   gpgme_pubkey_algo_name of used algo.\n"
+  "   hash_algo_name:     gpgme_hash_algo_name of used hash algo\n"
+  "   pka_address:        The mailbox from the PKA information.\n"
+  "  Number values:\n"
+  "   status_code:     The status as a number. (gpg_error_t)\n"
+  "   timestamp:       Signature creation time. (secs since epoch)\n"
+  "   exp_timestamp:   Signature expiration or 0. (secs since epoch)\n"
+  "   pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n"
+  "   validity: validity as number (gpgme_validity_t)\n"
+  "   validity_reason: (gpg_error_t)\n"
+  "  Array values:\n"
+  "   notations: Notation data and policy urls (gpgme_sig_notation_t)\n"
+  "    Boolean values:\n"
+  "     human_readable\n"
+  "     critical\n"
+  "    String values:\n"
+  "     name\n"
+  "     value\n"
+  "    Number values:\n"
+  "     flags\n";
+static gpg_error_t
+op_decrypt (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_protocol_t protocol;
+  gpgme_data_t input = NULL;
+  gpgme_data_t output = NULL;
+  gpgme_decrypt_result_t decrypt_result;
+  gpgme_verify_result_t verify_result;
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  if ((err = get_string_data (request, result, "data", &input)))
+      goto leave;
+
+  /* Create an output data object.  */
+  err = gpgme_data_new (&output);
+  if (err)
+    {
+      gpg_error_object (result, err,
+                        "Error creating output data object: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Decrypt.  */
+  err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY,
+                              input, output);
+  decrypt_result = gpgme_op_decrypt_result (ctx);
+  if (err)
+    {
+      gpg_error_object (result, err, "Decryption failed: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+  gpgme_data_release (input);
+  input = NULL;
+
+  if (decrypt_result->is_mime)
+    xjson_AddBoolToObject (result, "mime", 1);
+
+  xjson_AddItemToObject (result, "dec_info",
+                         decrypt_result_to_json (decrypt_result));
+
+  verify_result = gpgme_op_verify_result (ctx);
+  if (verify_result && verify_result->signatures)
+    {
+      xjson_AddItemToObject (result, "info",
+                             verify_result_to_json (verify_result));
+    }
+
+  err = make_data_object (result, output, "plaintext", -1);
+  output = NULL;
+
+  if (err)
+    {
+      gpg_error_object (result, err, "Plaintext output failed: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+ leave:
+  release_context (ctx);
+  gpgme_data_release (input);
+  gpgme_data_release (output);
+  return err;
+}
+
+
+\f
+static const char hlp_sign[] =
+  "op:     \"sign\"\n"
+  "keys:   Array of strings with the fingerprints of the signing key.\n"
+  "        For a single key a String may be used instead of an array.\n"
+  "data:   Input data. \n"
+  "\n"
+  "Optional parameters:\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "sender:        The mail address of the sender.\n"
+  "mode:          A string with the signing mode can be:\n"
+  "               detached (default)\n"
+  "               opaque\n"
+  "               clearsign\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "base64:        Input data is base64 encoded.\n"
+  "armor:         Request output in armored format.\n"
+  "\n"
+  "Response on success:\n"
+  "type:   \"signature\"\n"
+  "data:   Unless armor mode is used a Base64 encoded binary\n"
+  "        signature.  In armor mode a string with an armored\n"
+  "        OpenPGP or a PEM message.\n"
+  "base64: Boolean indicating whether data is base64 encoded.\n";
+static gpg_error_t
+op_sign (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_protocol_t protocol;
+  char **patterns = NULL;
+  gpgme_data_t input = NULL;
+  gpgme_data_t output = NULL;
+  int abool;
+  cjson_t j_tmp;
+  gpgme_sig_mode_t mode = GPGME_SIG_MODE_DETACH;
+  gpgme_ctx_t keylist_ctx = NULL;
+  gpgme_key_t key = NULL;
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  if ((err = get_boolean_flag (request, "armor", 0, &abool)))
+    goto leave;
+  gpgme_set_armor (ctx, abool);
+
+  j_tmp = cJSON_GetObjectItem (request, "mode");
+  if (j_tmp && cjson_is_string (j_tmp))
+    {
+      if (!strcmp (j_tmp->valuestring, "opaque"))
+        {
+          mode = GPGME_SIG_MODE_NORMAL;
+        }
+      else if (!strcmp (j_tmp->valuestring, "clearsign"))
+        {
+          mode = GPGME_SIG_MODE_CLEAR;
+        }
+    }
+
+  j_tmp = cJSON_GetObjectItem (request, "sender");
+  if (j_tmp && cjson_is_string (j_tmp))
+    {
+      gpgme_set_sender (ctx, j_tmp->valuestring);
+    }
+
+  patterns = create_keylist_patterns (request, "keys");
+  if (!patterns)
+    {
+      gpg_error_object (result, err, "Error getting keys: %s",
+                        gpg_strerror (gpg_error (GPG_ERR_NO_KEY)));
+      goto leave;
+    }
+
+  /* Do a keylisting and add the keys */
+  keylist_ctx = create_onetime_context (protocol);
+  gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL);
+
+  err = gpgme_op_keylist_ext_start (keylist_ctx,
+                                    (const char **) patterns, 1, 0);
+  if (err)
+    {
+      gpg_error_object (result, err, "Error listing keys: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+  while (!(err = gpgme_op_keylist_next (keylist_ctx, &key)))
+    {
+      if ((err = gpgme_signers_add (ctx, key)))
+        {
+          gpg_error_object (result, err, "Error adding signer: %s",
+                            gpg_strerror (err));
+          goto leave;
+        }
+      gpgme_key_unref (key);
+      key = NULL;
+    }
+
+  if ((err = get_string_data (request, result, "data", &input)))
+    goto leave;
+
+  /* Create an output data object.  */
+  err = gpgme_data_new (&output);
+  if (err)
+    {
+      gpg_error_object (result, err, "Error creating output data object: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Sign. */
+  err = gpgme_op_sign (ctx, input, output, mode);
+  if (err)
+    {
+      gpg_error_object (result, err, "Signing failed: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  gpgme_data_release (input);
+  input = NULL;
+
+  /* We need to base64 if armoring has not been requested.  */
+  err = make_data_object (result, output,
+                          "signature", !gpgme_get_armor (ctx));
+  output = NULL;
+
+ leave:
+  xfree_array (patterns);
+  gpgme_signers_clear (ctx);
+  gpgme_key_unref (key);
+  release_onetime_context (keylist_ctx);
+  release_context (ctx);
+  gpgme_data_release (input);
+  gpgme_data_release (output);
+  return err;
+}
+
+
+\f
+static const char hlp_verify[] =
+  "op:     \"verify\"\n"
+  "data:   The data to verify.\n"
+  "\n"
+  "Optional parameters:\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "signature:     A detached signature. If missing opaque is assumed.\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "base64:        Input data is base64 encoded.\n"
+  "\n"
+  "Response on success:\n"
+  "type:   \"plaintext\"\n"
+  "data:   The verified data.  This may be base64 encoded.\n"
+  "base64: Boolean indicating whether data is base64 encoded.\n"
+  "info:   An object with verification information (gpgme_verify_result_t).\n"
+  " is_mime:    Boolean that is true if the messages claims it is MIME.\n"
+  "             Note that this flag is not covered by the signature.)\n"
+  " signatures: Array of signatures\n"
+  "  summary: Object containing summary information.\n"
+  "   Boolean values: (Check gpgme_sigsum_t doc for meaning)\n"
+  "    valid\n"
+  "    green\n"
+  "    red\n"
+  "    revoked\n"
+  "    key-expired\n"
+  "    sig-expired\n"
+  "    key-missing\n"
+  "    crl-missing\n"
+  "    crl-too-old\n"
+  "    bad-policy\n"
+  "    sys-error\n"
+  "   sigsum: Array of strings representing the sigsum.\n"
+  "  Boolean values:\n"
+  "   wrong_key_usage: Key should not have been used for signing.\n"
+  "   chain_model:     Validity has been verified using the chain model.\n"
+  "   is_de_vs:        signature is in compliance to the de-vs mode.\n"
+  "  String values:\n"
+  "   status_string:      The status code as localized gpg-error string\n"
+  "   fingerprint:        The fingerprint of the signing key.\n"
+  "   validity_string:    The validity as string.\n"
+  "   pubkey_algo_name:   gpgme_pubkey_algo_name of used algo.\n"
+  "   hash_algo_name:     gpgme_hash_algo_name of used hash algo\n"
+  "   pka_address:        The mailbox from the PKA information.\n"
+  "  Number values:\n"
+  "   status_code:     The status as a number. (gpg_error_t)\n"
+  "   timestamp:       Signature creation time. (secs since epoch)\n"
+  "   exp_timestamp:   Signature expiration or 0. (secs since epoch)\n"
+  "   pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n"
+  "   validity: validity as number (gpgme_validity_t)\n"
+  "   validity_reason: (gpg_error_t)\n"
+  "  Array values:\n"
+  "   notations: Notation data and policy urls (gpgme_sig_notation_t)\n"
+  "    Boolean values:\n"
+  "     human_readable\n"
+  "     critical\n"
+  "    String values:\n"
+  "     name\n"
+  "     value\n"
+  "    Number values:\n"
+  "     flags\n";
+static gpg_error_t
+op_verify (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_protocol_t protocol;
+  gpgme_data_t input = NULL;
+  gpgme_data_t signature = NULL;
+  gpgme_data_t output = NULL;
+  gpgme_verify_result_t verify_result;
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  if ((err = get_string_data (request, result, "data", &input)))
+    goto leave;
+
+  err = get_string_data (request, result, "signature", &signature);
+  /* Signature data is optional otherwise we expect opaque or clearsigned. */
+  if (err && err != gpg_error (GPG_ERR_NO_DATA))
+    goto leave;
+
+  if (!signature)
+    {
+      /* Verify opaque or clearsigned we need an output data object.  */
+      err = gpgme_data_new (&output);
+      if (err)
+        {
+          gpg_error_object (result, err,
+                            "Error creating output data object: %s",
+                            gpg_strerror (err));
+          goto leave;
+        }
+      err = gpgme_op_verify (ctx, input, 0, output);
+    }
+  else
+    {
+      err = gpgme_op_verify (ctx, signature, input, NULL);
+    }
+
+  if (err)
+    {
+      gpg_error_object (result, err, "Verify failed: %s", gpg_strerror (err));
+      goto leave;
+    }
+  gpgme_data_release (input);
+  input = NULL;
+  gpgme_data_release (signature);
+  signature = NULL;
+
+  verify_result = gpgme_op_verify_result (ctx);
+  if (verify_result && verify_result->signatures)
+    {
+      xjson_AddItemToObject (result, "info",
+                             verify_result_to_json (verify_result));
+    }
+
+  if (output)
+    {
+      err = make_data_object (result, output, "plaintext", -1);
+      output = NULL;
+
+      if (err)
+        {
+          gpg_error_object (result, err, "Plaintext output failed: %s",
+                            gpg_strerror (err));
+          goto leave;
+        }
+    }
+
+ leave:
+  release_context (ctx);
+  gpgme_data_release (input);
+  gpgme_data_release (output);
+  gpgme_data_release (signature);
+  return err;
+}
+
+
+\f
+static const char hlp_version[] =
+  "op:     \"version\"\n"
+  "\n"
+  "Response on success:\n"
+  "gpgme:  The GPGME Version.\n"
+  "info:   dump of engine info. containing:\n"
+  "        protocol: The protocol.\n"
+  "        fname:    The file name.\n"
+  "        version:  The version.\n"
+  "        req_ver:  The required version.\n"
+  "        homedir:  The homedir of the engine or \"default\".\n";
+static gpg_error_t
+op_version (cjson_t request, cjson_t result)
+{
+  gpg_error_t err = 0;
+  gpgme_engine_info_t ei = NULL;
+  cjson_t infos = xjson_CreateArray ();
+
+  (void)request;
+
+  if (!cJSON_AddStringToObject (result, "gpgme", gpgme_check_version (NULL)))
+    {
+      cJSON_Delete (infos);
+      return gpg_error_from_syserror ();
+    }
+
+  if ((err = gpgme_get_engine_info (&ei)))
+    {
+      cJSON_Delete (infos);
+      return err;
+    }
+
+  for (; ei; ei = ei->next)
+    cJSON_AddItemToArray (infos, engine_info_to_json (ei));
+
+  if (!cJSON_AddItemToObject (result, "info", infos))
+    {
+      err = gpg_error_from_syserror ();
+      cJSON_Delete (infos);
+      return err;
+    }
+
+  return 0;
+}
+
+
+\f
+static const char hlp_keylist[] =
+  "op:     \"keylist\"\n"
+  "\n"
+  "Optional parameters:\n"
+  "keys:          Array of strings or fingerprints to lookup\n"
+  "               For a single key a String may be used instead of an array.\n"
+  "               default lists all keys.\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "secret:        List only secret keys.\n"
+  "with-secret:   Add KEYLIST_MODE_WITH_SECRET.\n"
+  "extern:        Add KEYLIST_MODE_EXTERN.\n"
+  "local:         Add KEYLIST_MODE_LOCAL. (default mode).\n"
+  "sigs:          Add KEYLIST_MODE_SIGS.\n"
+  "notations:     Add KEYLIST_MODE_SIG_NOTATIONS.\n"
+  "tofu:          Add KEYLIST_MODE_WITH_TOFU.\n"
+  "ephemeral:     Add KEYLIST_MODE_EPHEMERAL.\n"
+  "validate:      Add KEYLIST_MODE_VALIDATE.\n"
+  "locate:        Add KEYLIST_MODE_LOCATE.\n"
+  "\n"
+  "Response on success:\n"
+  "keys:   Array of keys.\n"
+  "  Boolean values:\n"
+  "   revoked\n"
+  "   expired\n"
+  "   disabled\n"
+  "   invalid\n"
+  "   can_encrypt\n"
+  "   can_sign\n"
+  "   can_certify\n"
+  "   can_authenticate\n"
+  "   secret\n"
+  "   is_qualified\n"
+  "  String values:\n"
+  "   protocol\n"
+  "   issuer_serial (CMS Only)\n"
+  "   issuer_name (CMS Only)\n"
+  "   chain_id (CMS Only)\n"
+  "   owner_trust (OpenPGP only)\n"
+  "   fingerprint\n"
+  "  Number values:\n"
+  "   last_update\n"
+  "   origin\n"
+  "  Array values:\n"
+  "   subkeys\n"
+  "    Boolean values:\n"
+  "     revoked\n"
+  "     expired\n"
+  "     disabled\n"
+  "     invalid\n"
+  "     can_encrypt\n"
+  "     can_sign\n"
+  "     can_certify\n"
+  "     can_authenticate\n"
+  "     secret\n"
+  "     is_qualified\n"
+  "     is_cardkey\n"
+  "     is_de_vs\n"
+  "    String values:\n"
+  "     pubkey_algo_name\n"
+  "     pubkey_algo_string\n"
+  "     keyid\n"
+  "     card_number\n"
+  "     curve\n"
+  "     keygrip\n"
+  "    Number values:\n"
+  "     pubkey_algo\n"
+  "     length\n"
+  "     timestamp\n"
+  "     expires\n"
+  "   userids\n"
+  "    Boolean values:\n"
+  "     revoked\n"
+  "     invalid\n"
+  "    String values:\n"
+  "     validity\n"
+  "     uid\n"
+  "     name\n"
+  "     email\n"
+  "     comment\n"
+  "     address\n"
+  "    Number values:\n"
+  "     origin\n"
+  "     last_update\n"
+  "    Array values:\n"
+  "     signatures\n"
+  "      Boolean values:\n"
+  "       revoked\n"
+  "       expired\n"
+  "       invalid\n"
+  "       exportable\n"
+  "      String values:\n"
+  "       pubkey_algo_name\n"
+  "       keyid\n"
+  "       status\n"
+  "       uid\n"
+  "       name\n"
+  "       email\n"
+  "       comment\n"
+  "      Number values:\n"
+  "       pubkey_algo\n"
+  "       timestamp\n"
+  "       expires\n"
+  "       status_code\n"
+  "       sig_class\n"
+  "      Array values:\n"
+  "       notations\n"
+  "        Boolean values:\n"
+  "         human_readable\n"
+  "         critical\n"
+  "        String values:\n"
+  "         name\n"
+  "         value\n"
+  "        Number values:\n"
+  "         flags\n"
+  "     tofu\n"
+  "      String values:\n"
+  "       description\n"
+  "      Number values:\n"
+  "       validity\n"
+  "       policy\n"
+  "       signcount\n"
+  "       encrcount\n"
+  "       signfirst\n"
+  "       signlast\n"
+  "       encrfirst\n"
+  "       encrlast\n";
+static gpg_error_t
+op_keylist (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_protocol_t protocol;
+  char **patterns = NULL;
+  int abool;
+  int secret_only = 0;
+  gpgme_keylist_mode_t mode = 0;
+  gpgme_key_t key = NULL;
+  cjson_t keyarray = xjson_CreateArray ();
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  /* Handle the various keylist mode bools. */
+  if ((err = get_boolean_flag (request, "secret", 0, &abool)))
+    goto leave;
+  if (abool)
+    {
+      mode |= GPGME_KEYLIST_MODE_WITH_SECRET;
+      secret_only = 1;
+    }
+  if ((err = get_boolean_flag (request, "with-secret", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_WITH_SECRET;
+  if ((err = get_boolean_flag (request, "extern", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_EXTERN;
+
+  if ((err = get_boolean_flag (request, "local", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_LOCAL;
+
+  if ((err = get_boolean_flag (request, "sigs", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_SIGS;
+
+  if ((err = get_boolean_flag (request, "notations", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS;
+
+  if ((err = get_boolean_flag (request, "tofu", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_WITH_TOFU;
+
+  if ((err = get_boolean_flag (request, "ephemeral", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_EPHEMERAL;
+
+  if ((err = get_boolean_flag (request, "validate", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_VALIDATE;
+
+  if ((err = get_boolean_flag (request, "locate", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_KEYLIST_MODE_LOCATE;
+
+  if (!mode)
+    {
+      /* default to local */
+      mode = GPGME_KEYLIST_MODE_LOCAL;
+    }
+
+  /* Get the keys.  */
+  patterns = create_keylist_patterns (request, "keys");
+
+  /* Do a keylisting and add the keys */
+  gpgme_set_keylist_mode (ctx, mode);
+
+  err = gpgme_op_keylist_ext_start (ctx, (const char **) patterns,
+                                    secret_only, 0);
+  if (err)
+    {
+      gpg_error_object (result, err, "Error listing keys: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  while (!(err = gpgme_op_keylist_next (ctx, &key)))
+    {
+      cJSON_AddItemToArray (keyarray, key_to_json (key));
+      gpgme_key_unref (key);
+    }
+  err = 0;
+
+  if (!cJSON_AddItemToObject (result, "keys", keyarray))
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+ leave:
+  xfree_array (patterns);
+  if (err)
+    {
+      cJSON_Delete (keyarray);
+    }
+  return err;
+}
+
+
+\f
+static const char hlp_import[] =
+  "op:     \"import\"\n"
+  "data:   The data to import.\n"
+  "\n"
+  "Optional parameters:\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "base64:        Input data is base64 encoded.\n"
+  "\n"
+  "Response on success:\n"
+  "result: The import result.\n"
+  "  Number values:\n"
+  "   considered\n"
+  "   no_user_id\n"
+  "   imported\n"
+  "   imported_rsa\n"
+  "   unchanged\n"
+  "   new_user_ids\n"
+  "   new_sub_keys\n"
+  "   new_signatures\n"
+  "   new_revocations\n"
+  "   secret_read\n"
+  "   secret_imported\n"
+  "   secret_unchanged\n"
+  "   skipped_new_keys\n"
+  "   not_imported\n"
+  "   skipped_v3_keys\n"
+  "  Array values:\n"
+  "   imports: List of keys for which an import was attempted\n"
+  "    String values:\n"
+  "     fingerprint\n"
+  "     error_string\n"
+  "    Number values:\n"
+  "     error_code\n"
+  "     status\n";
+static gpg_error_t
+op_import (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_data_t input = NULL;
+  gpgme_import_result_t import_result;
+  gpgme_protocol_t protocol;
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  if ((err = get_string_data (request, result, "data", &input)))
+      goto leave;
+
+  /* Import.  */
+  err = gpgme_op_import (ctx, input);
+  import_result = gpgme_op_import_result (ctx);
+  if (err)
+    {
+      gpg_error_object (result, err, "Import failed: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+  gpgme_data_release (input);
+  input = NULL;
+
+  xjson_AddItemToObject (result, "result",
+                         import_result_to_json (import_result));
+
+ leave:
+  release_context (ctx);
+  gpgme_data_release (input);
+  return err;
+}
+
+
+static const char hlp_export[] =
+  "op:     \"export\"\n"
+  "\n"
+  "Optional parameters:\n"
+  "keys:          Array of strings or fingerprints to lookup\n"
+  "               For a single key a String may be used instead of an array.\n"
+  "               default exports all keys.\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
+  "\n"
+  "Optional boolean flags (default is false):\n"
+  "armor:         Request output in armored format.\n"
+  "extern:        Add EXPORT_MODE_EXTERN.\n"
+  "minimal:       Add EXPORT_MODE_MINIMAL.\n"
+  "raw:           Add EXPORT_MODE_RAW.\n"
+  "pkcs12:        Add EXPORT_MODE_PKCS12.\n"
+  "with-sec-fprs: Add the sec-fprs array to the result.\n"
+  "\n"
+  "Response on success:\n"
+  "type:     \"keys\"\n"
+  "data:     Unless armor mode is used a Base64 encoded binary.\n"
+  "          In armor mode a string with an armored\n"
+  "          OpenPGP or a PEM / PKCS12 key.\n"
+  "base64:   Boolean indicating whether data is base64 encoded.\n"
+  "sec-fprs: Optional, only if with-secret is set. An array containing\n"
+  "          the fingerprints of the keys in the export for which a secret\n"
+  "          key is available";
+static gpg_error_t
+op_export (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_protocol_t protocol;
+  char **patterns = NULL;
+  int abool;
+  int with_secret = 0;
+  gpgme_export_mode_t mode = 0;
+  gpgme_data_t output = NULL;
+
+  if ((err = get_protocol (request, &protocol)))
+    goto leave;
+  ctx = get_context (protocol);
+
+  if ((err = get_boolean_flag (request, "armor", 0, &abool)))
+    goto leave;
+  gpgme_set_armor (ctx, abool);
+
+  /* Handle the various export mode bools. */
+  if ((err = get_boolean_flag (request, "secret", 0, &abool)))
+    goto leave;
+  if (abool)
+    {
+      err = gpg_error (GPG_ERR_FORBIDDEN);
+      goto leave;
+    }
+
+  if ((err = get_boolean_flag (request, "extern", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_EXPORT_MODE_EXTERN;
+
+  if ((err = get_boolean_flag (request, "minimal", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_EXPORT_MODE_MINIMAL;
+
+  if ((err = get_boolean_flag (request, "raw", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_EXPORT_MODE_RAW;
+
+  if ((err = get_boolean_flag (request, "pkcs12", 0, &abool)))
+    goto leave;
+  if (abool)
+    mode |= GPGME_EXPORT_MODE_PKCS12;
+
+  if ((err = get_boolean_flag (request, "with-sec-fprs", 0, &abool)))
+    goto leave;
+  if (abool)
+    with_secret = 1;
+
+  /* Get the export patterns.  */
+  patterns = create_keylist_patterns (request, "keys");
+
+  /* Create an output data object.  */
+  err = gpgme_data_new (&output);
+  if (err)
+    {
+      gpg_error_object (result, err, "Error creating output data object: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  err = gpgme_op_export_ext (ctx, (const char **) patterns,
+                             mode, output);
+  if (err)
+    {
+      gpg_error_object (result, err, "Error exporting keys: %s",
+                        gpg_strerror (err));
+      goto leave;
+    }
+
+  /* We need to base64 if armoring has not been requested.  */
+  err = make_data_object (result, output,
+                          "keys", !gpgme_get_armor (ctx));
+  output = NULL;
+
+  if (!err && with_secret)
+    {
+      err = add_secret_fprs ((const char **) patterns, protocol, result);
+    }
+
+leave:
+  xfree_array (patterns);
+  release_context (ctx);
+  gpgme_data_release (output);
+
+  return err;
+}
+
+
+static const char hlp_delete[] =
+  "op:     \"delete\"\n"
+  "key:    Fingerprint of the key to delete.\n"
+  "\n"
+  "Optional parameters:\n"
+  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
   "\n"
   "Response on success:\n"
-  "type:   \"ciphertext\"\n"
-  "data:   Unless armor mode is used a Base64 encoded binary\n"
-  "        ciphertext.  In armor mode a string with an armored\n"
-  "        OpenPGP or a PEM message.\n"
-  "base64: Boolean indicating whether data is base64 encoded.\n"
-  "more:   Optional boolean indicating that \"getmore\" is required.";
+  "success:   Boolean true.\n";
 static gpg_error_t
-op_encrypt (cjson_t request, cjson_t result)
+op_delete (cjson_t request, cjson_t result)
 {
   gpg_error_t err;
   gpgme_ctx_t ctx = NULL;
+  gpgme_ctx_t keylist_ctx = NULL;
   gpgme_protocol_t protocol;
-  size_t chunksize;
-  int opt_base64;
-  int opt_mime;
-  char *keystring = NULL;
-  cjson_t j_input;
-  gpgme_data_t input = NULL;
-  gpgme_data_t output = NULL;
-  int abool;
-  gpgme_encrypt_flags_t encrypt_flags = 0;
+  gpgme_key_t key = NULL;
+  int secret = 0;
+  cjson_t j_key = NULL;
 
   if ((err = get_protocol (request, &protocol)))
     goto leave;
   ctx = get_context (protocol);
-  if ((err = get_chunksize (request, &chunksize)))
-    goto leave;
-
-  if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
-    goto leave;
-  if ((err = get_boolean_flag (request, "mime", 0, &opt_mime)))
-    goto leave;
+  keylist_ctx = get_context (protocol);
 
-  if ((err = get_boolean_flag (request, "armor", 0, &abool)))
-    goto leave;
-  gpgme_set_armor (ctx, abool);
-  if ((err = get_boolean_flag (request, "always-trust", 0, &abool)))
-    goto leave;
-  if (abool)
-    encrypt_flags |= GPGME_ENCRYPT_ALWAYS_TRUST;
-  if ((err = get_boolean_flag (request, "no-encrypt-to", 0,&abool)))
+  if ((err = get_boolean_flag (request, "secret", 0, &secret)))
     goto leave;
-  if (abool)
-    encrypt_flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO;
-  if ((err = get_boolean_flag (request, "no-compress", 0, &abool)))
-    goto leave;
-  if (abool)
-    encrypt_flags |= GPGME_ENCRYPT_NO_COMPRESS;
-  if ((err = get_boolean_flag (request, "throw-keyids", 0, &abool)))
-    goto leave;
-  if (abool)
-    encrypt_flags |= GPGME_ENCRYPT_THROW_KEYIDS;
-  if ((err = get_boolean_flag (request, "wrap", 0, &abool)))
-    goto leave;
-  if (abool)
-    encrypt_flags |= GPGME_ENCRYPT_WRAP;
-  if ((err = get_boolean_flag (request, "want-address", 0, &abool)))
-    goto leave;
-  if (abool)
-    encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS;
-
-
-  /* Get the keys.  */
-  err = get_keys (request, &keystring);
-  if (err)
+  if (secret)
     {
-      /* Provide a custom error response.  */
-      error_object (result, "Error getting keys: %s", gpg_strerror (err));
+      err = gpg_error (GPG_ERR_FORBIDDEN);
       goto leave;
     }
 
-  /* Get the data.  Note that INPUT is a shallow data object with the
-   * storage hold in REQUEST.  */
-  j_input = cJSON_GetObjectItem (request, "data");
-  if (!j_input)
+  j_key = cJSON_GetObjectItem (request, "key");
+  if (!j_key)
     {
-      err = gpg_error (GPG_ERR_NO_DATA);
+      err = gpg_error (GPG_ERR_NO_KEY);
       goto leave;
     }
-  if (!cjson_is_string (j_input))
+  if (!cjson_is_string (j_key))
     {
       err = gpg_error (GPG_ERR_INV_VALUE);
       goto leave;
     }
-  if (opt_base64)
+
+  /* Get the key */
+  if ((err = gpgme_get_key (keylist_ctx, j_key->valuestring, &key, 0)))
     {
-      err = data_from_base64_string (&input, j_input);
-      if (err)
-        {
-          error_object (result, "Error decoding Base-64 encoded 'data': %s",
+      gpg_error_object (result, err, "Error fetching key for delete: %s",
                         gpg_strerror (err));
-          goto leave;
-        }
+      goto leave;
     }
-  else
+
+  err = gpgme_op_delete (ctx, key, 0);
+  if (err)
     {
-      err = gpgme_data_new_from_mem (&input, j_input->valuestring,
-                                     strlen (j_input->valuestring), 0);
-      if (err)
-        {
-          error_object (result, "Error getting 'data': %s", gpg_strerror (err));
-          goto leave;
-        }
+      gpg_error_object (result, err, "Error deleting key: %s",
+                        gpg_strerror (err));
+      goto leave;
     }
-  if (opt_mime)
-    gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME);
 
+  xjson_AddBoolToObject (result, "success", 1);
 
-  /* Create an output data object.  */
-  err = gpgme_data_new (&output);
-  if (err)
+leave:
+  gpgme_key_unref (key);
+  release_context (ctx);
+  release_context (keylist_ctx);
+
+  return err;
+}
+
+
+static const char hlp_config_opt[] =
+  "op:       \"config_opt\"\n"
+  "component: The component of the option.\n"
+  "option:    The name of the option.\n"
+  "\n"
+  "Response on success:\n"
+  "\n"
+  "option: Information about the option.\n"
+  " String values:\n"
+  "  name: The name of the option\n"
+  "  description: Localized description of the opt.\n"
+  "  argname: Thhe argument name e.g. --verbose\n"
+  "  default_description\n"
+  "  no_arg_description\n"
+  " Number values:\n"
+  "  flags: Flags for this option.\n"
+  "  level: the level of the description. See gpgme_conf_level_t.\n"
+  "  type: The type of the option. See gpgme_conf_type_t.\n"
+  "  alt_type: Alternate type of the option. See gpgme_conf_type_t\n"
+  " Arg type values: (see desc. below)\n"
+  "  default_value: Array of the default value.\n"
+  "  no_arg_value: Array of the value if it is not set.\n"
+  "  value: Array for the current value if the option is set.\n"
+  "\n"
+  "If the response is empty the option was not found\n"
+  "";
+static gpg_error_t
+op_config_opt (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  gpgme_conf_comp_t conf = NULL;
+  gpgme_conf_comp_t comp = NULL;
+  cjson_t j_tmp;
+  char *comp_name = NULL;
+  char *opt_name = NULL;
+
+  ctx = get_context (GPGME_PROTOCOL_GPGCONF);
+
+  j_tmp = cJSON_GetObjectItem (request, "component");
+  if (!j_tmp || !cjson_is_string (j_tmp))
     {
-      error_object (result, "Error creating output data object: %s",
-                    gpg_strerror (err));
+      err = gpg_error (GPG_ERR_INV_VALUE);
       goto leave;
     }
+  comp_name = j_tmp->valuestring;
 
-  /* Encrypt.  */
-  err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags,
-                              input, output);
-  /* encrypt_result = gpgme_op_encrypt_result (ctx); */
+
+  j_tmp = cJSON_GetObjectItem (request, "option");
+  if (!j_tmp || !cjson_is_string (j_tmp))
+    {
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+  opt_name = j_tmp->valuestring;
+
+  /* Load the config */
+  err = gpgme_op_conf_load (ctx, &conf);
   if (err)
     {
-      error_object (result, "Encryption failed: %s", gpg_strerror (err));
       goto leave;
     }
-  gpgme_data_release (input);
-  input = NULL;
 
-  /* We need to base64 if armoring has not been requested.  */
-  err = make_data_object (result, output, chunksize,
-                          "ciphertext", !gpgme_get_armor (ctx));
-  output = NULL;
+  comp = conf;
+  for (comp = conf; comp; comp = comp->next)
+    {
+      gpgme_conf_opt_t opt = NULL;
+      int found = 0;
+      if (!comp->name || strcmp (comp->name, comp_name))
+        {
+          /* Skip components if a single one is specified */
+          continue;
+        }
+      for (opt = comp->options; opt; opt = opt->next)
+        {
+          if (!opt->name || strcmp (opt->name, opt_name))
+            {
+              /* Skip components if a single one is specified */
+              continue;
+            }
+          xjson_AddItemToObject (result, "option", conf_opt_to_json (opt));
+          found = 1;
+          break;
+        }
+      if (found)
+        break;
+    }
 
- leave:
-  xfree (keystring);
+leave:
+  gpgme_conf_release (conf);
   release_context (ctx);
-  gpgme_data_release (input);
-  gpgme_data_release (output);
+
   return err;
 }
 
 
-\f
-static const char hlp_decrypt[] =
-  "op:     \"decrypt\"\n"
-  "data:   The encrypted data.\n"
+static const char hlp_config[] =
+  "op:     \"config\"\n"
   "\n"
   "Optional parameters:\n"
-  "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
-  "chunksize:     Max number of bytes in the resulting \"data\".\n"
-  "\n"
-  "Optional boolean flags (default is false):\n"
-  "base64:        Input data is base64 encoded.\n"
+  "component:    Component of entries to list.\n"
+  "              Default: all\n"
   "\n"
   "Response on success:\n"
-  "type:   \"plaintext\"\n"
-  "data:   The decrypted data.  This may be base64 encoded.\n"
-  "base64: Boolean indicating whether data is base64 encoded.\n"
-  "mime:   A Boolean indicating whether the data is a MIME object.\n"
-  "info:   An optional object with extra information.\n"
-  "more:   Optional boolean indicating that \"getmore\" is required.";
+  "   components: Array of the component program configs.\n"
+  "     name:         The component name.\n"
+  "     description:  Description of the component.\n"
+  "     program_name: The absolute path to the program.\n"
+  "     options: Array of config options\n"
+  "      String values:\n"
+  "       name: The name of the option\n"
+  "       description: Localized description of the opt.\n"
+  "       argname: Thhe argument name e.g. --verbose\n"
+  "       default_description\n"
+  "       no_arg_description\n"
+  "      Number values:\n"
+  "       flags: Flags for this option.\n"
+  "       level: the level of the description. See gpgme_conf_level_t.\n"
+  "       type: The type of the option. See gpgme_conf_type_t.\n"
+  "       alt_type: Alternate type of the option. See gpgme_conf_type_t\n"
+  "      Arg type values: (see desc. below)\n"
+  "       default_value: Array of the default value.\n"
+  "       no_arg_value: Array of the value if it is not set.\n"
+  "       value: Array for the current value if the option is set.\n"
+  "\n"
+  "Conf type values are an array of values that are either\n"
+  "of type number named \"number\" or of type string,\n"
+  "named \"string\".\n"
+  "If the type is none the bool value is_none is true.\n"
+  "";
 static gpg_error_t
-op_decrypt (cjson_t request, cjson_t result)
+op_config (cjson_t request, cjson_t result)
 {
   gpg_error_t err;
   gpgme_ctx_t ctx = NULL;
-  gpgme_protocol_t protocol;
-  size_t chunksize;
-  int opt_base64;
-  cjson_t j_input;
-  gpgme_data_t input = NULL;
-  gpgme_data_t output = NULL;
-  gpgme_decrypt_result_t decrypt_result;
-
-  if ((err = get_protocol (request, &protocol)))
-    goto leave;
-  ctx = get_context (protocol);
-  if ((err = get_chunksize (request, &chunksize)))
-    goto leave;
+  gpgme_conf_comp_t conf = NULL;
+  gpgme_conf_comp_t comp = NULL;
+  cjson_t j_tmp;
+  char *comp_name = NULL;
+  cjson_t j_comps = xjson_CreateArray ();
 
-  if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
-    goto leave;
+  ctx = get_context (GPGME_PROTOCOL_GPGCONF);
 
-  /* Get the data.  Note that INPUT is a shallow data object with the
-   * storage hold in REQUEST.  */
-  j_input = cJSON_GetObjectItem (request, "data");
-  if (!j_input)
+  j_tmp = cJSON_GetObjectItem (request, "component");
+  if (j_tmp && cjson_is_string (j_tmp))
     {
-      err = gpg_error (GPG_ERR_NO_DATA);
-      goto leave;
+      comp_name = j_tmp->valuestring;
     }
-  if (!cjson_is_string (j_input))
+  else if (j_tmp && !cjson_is_string (j_tmp))
     {
       err = gpg_error (GPG_ERR_INV_VALUE);
       goto leave;
     }
-  if (opt_base64)
+
+  /* Load the config */
+  err = gpgme_op_conf_load (ctx, &conf);
+  if (err)
     {
-      err = data_from_base64_string (&input, j_input);
-      if (err)
-        {
-          error_object (result, "Error decoding Base-64 encoded 'data': %s",
-                        gpg_strerror (err));
-          goto leave;
-        }
+      goto leave;
     }
-  else
+
+  comp = conf;
+  for (comp = conf; comp; comp = comp->next)
     {
-      err = gpgme_data_new_from_mem (&input, j_input->valuestring,
-                                     strlen (j_input->valuestring), 0);
-      if (err)
+      if (comp_name && comp->name && strcmp (comp->name, comp_name))
         {
-          error_object (result, "Error getting 'data': %s", gpg_strerror (err));
-          goto leave;
+          /* Skip components if a single one is specified */
+          continue;
         }
+      cJSON_AddItemToArray (j_comps, conf_comp_to_json (comp));
     }
+  xjson_AddItemToObject (result, "components", j_comps);
 
-  /* Create an output data object.  */
-  err = gpgme_data_new (&output);
+leave:
+  gpgme_conf_release (conf);
+  release_context (ctx);
+
+  return err;
+}
+
+
+\f
+static const char hlp_createkey[] =
+  "op:      \"createkey\"\n"
+  "userid:  The user id. E.g. \"Foo Bar <foo@bar.baz>\"\n"
+  "\n"
+  "Optional parameters:\n"
+  "algo:        Algo of the key as string.  See doc for gpg --quick-gen-key.\n"
+  "             Supported values are \"default\" and \"future-default\".\n"
+  "expires:     Seconds from now to expiry as Number.  0 means no expiry.\n"
+  "             The default is to use a standard expiration interval.\n"
+  "\n"
+  "Response on success:\n"
+  "fingerprint:   The fingerprint of the created key.\n"
+  "\n"
+  "Note: This interface does not allow key generation if the userid\n"
+  "of the new key already exists in the keyring.\n";
+static gpg_error_t
+op_createkey (cjson_t request, cjson_t result)
+{
+  gpg_error_t err;
+  gpgme_ctx_t ctx = NULL;
+  unsigned int flags = GPGME_CREATE_FORCE; /* Always force as the GUI should
+                                              handle checks, if required. */
+  unsigned long expires = 0;
+  cjson_t j_tmp;
+  const char *algo = "default";
+  const char *userid;
+  gpgme_genkey_result_t res;
+
+#ifdef GPG_AGENT_ALLOWS_KEYGEN_THROUGH_BROWSER
+  /* GnuPG forbids keygen through the browser socket so for
+     this we create an unrestricted context.
+     See GnuPG-Bug-Id: T4010 for more info */
+  ctx = get_context (GPGME_PROTOCOL_OpenPGP);
+#else
+    err = gpgme_new (&ctx);
   if (err)
+    log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err));
+  gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP);
+#endif
+
+  j_tmp = cJSON_GetObjectItem (request, "algo");
+  if (j_tmp && cjson_is_string (j_tmp))
     {
-      error_object (result, "Error creating output data object: %s",
-                    gpg_strerror (err));
-      goto leave;
+      algo = j_tmp->valuestring;
     }
 
-  /* Decrypt.  */
-  err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY,
-                              input, output);
-  decrypt_result = gpgme_op_decrypt_result (ctx);
-  if (err)
+  j_tmp = cJSON_GetObjectItem (request, "userid");
+  if (!j_tmp || !cjson_is_string (j_tmp))
     {
-      error_object (result, "Decryption failed: %s", gpg_strerror (err));
+      err = gpg_error (GPG_ERR_INV_VALUE);
       goto leave;
     }
-  gpgme_data_release (input);
-  input = NULL;
 
-  if (decrypt_result->is_mime)
-    xjson_AddBoolToObject (result, "mime", 1);
+  userid = j_tmp->valuestring;
 
-  err = make_data_object (result, output, chunksize, "plaintext", -1);
-  output = NULL;
+  j_tmp = cJSON_GetObjectItem (request, "expires");
+  if (j_tmp)
+    {
+      if (!cjson_is_number (j_tmp))
+        {
+          err = gpg_error (GPG_ERR_INV_VALUE);
+          goto leave;
+        }
+      expires = j_tmp->valueint;
 
- leave:
+      if (!expires)
+        flags |= GPGME_CREATE_NOEXPIRE;
+    }
+
+
+  if ((err = gpgme_op_createkey (ctx, userid, algo, 0, expires, NULL, flags)))
+    goto leave;
+
+  res = gpgme_op_genkey_result (ctx);
+  if (!res)
+    {
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+
+  xjson_AddStringToObject0 (result, "fingerprint", res->fpr);
+
+leave:
+#ifdef GPG_AGENT_ALLOWS_KEYGEN_THROUGH_BROWSER
   release_context (ctx);
-  gpgme_data_release (input);
-  gpgme_data_release (output);
+#else
+  gpgme_release (ctx);
+#endif
+
   return err;
 }
 
@@ -961,14 +3065,10 @@ op_decrypt (cjson_t request, cjson_t result)
 static const char hlp_getmore[] =
   "op:     \"getmore\"\n"
   "\n"
-  "Optional parameters:\n"
-  "chunksize:  Max number of bytes in the \"data\" object.\n"
-  "\n"
   "Response on success:\n"
-  "type:       Type of the pending data\n"
-  "data:       The next chunk of data\n"
-  "base64:     Boolean indicating whether data is base64 encoded\n"
-  "more:       Optional boolean requesting another \"getmore\".";
+  "response:       base64 encoded json response.\n"
+  "more:           Another getmore is required.\n"
+  "base64:         boolean if the response is base64 encoded.\n";
 static gpg_error_t
 op_getmore (cjson_t request, cjson_t result)
 {
@@ -980,20 +3080,24 @@ op_getmore (cjson_t request, cjson_t result)
   if ((err = get_chunksize (request, &chunksize)))
     goto leave;
 
-  /* Adjust the chunksize if we need to do base64 conversion.  */
-  if (pending_data.base64)
-    chunksize = (chunksize / 4) * 3;
+  /* For the meta data we need 41 bytes:
+     {"more":true,"base64":true,"response":""} */
+  chunksize -= 41;
+
+  /* Adjust the chunksize for the base64 conversion.  */
+  chunksize = (chunksize / 4) * 3;
 
   /* Do we have anything pending?  */
   if (!pending_data.buffer)
     {
       err = gpg_error (GPG_ERR_NO_DATA);
-      error_object (result, "Operation not possible: %s", gpg_strerror (err));
+      gpg_error_object (result, err, "Operation not possible: %s",
+                        gpg_strerror (err));
       goto leave;
     }
 
-  xjson_AddStringToObject (result, "type", pending_data.type);
-  xjson_AddBoolToObject (result, "base64", pending_data.base64);
+  /* We currently always use base64 encoding for simplicity. */
+  xjson_AddBoolToObject (result, "base64", 1);
 
   if (pending_data.written >= pending_data.length)
     {
@@ -1002,7 +3106,7 @@ op_getmore (cjson_t request, cjson_t result)
       gpgme_free (pending_data.buffer);
       pending_data.buffer = NULL;
       xjson_AddBoolToObject (result, "more", 0);
-      err = cjson_AddStringToObject (result, "data", "");
+      err = cjson_AddStringToObject (result, "response", "");
     }
   else
     {
@@ -1017,21 +3121,16 @@ op_getmore (cjson_t request, cjson_t result)
 
       c = pending_data.buffer[pending_data.written + n];
       pending_data.buffer[pending_data.written + n] = 0;
-      if (pending_data.base64)
-        err = add_base64_to_object (result, "data",
-                                    (pending_data.buffer
-                                     + pending_data.written), n);
-      else
-        err = cjson_AddStringToObject (result, "data",
-                                       (pending_data.buffer
-                                        + pending_data.written));
+      err = add_base64_to_object (result, "response",
+                                  (pending_data.buffer
+                                   + pending_data.written), n);
       pending_data.buffer[pending_data.written + n] = c;
       if (!err)
         {
           pending_data.written += n;
           if (pending_data.written >= pending_data.length)
             {
-              gpgme_free (pending_data.buffer);
+              xfree (pending_data.buffer);
               pending_data.buffer = NULL;
             }
         }
@@ -1051,9 +3150,27 @@ static const char hlp_help[] =
   "operation is not performned but a string with the documentation\n"
   "returned.  To list all operations it is allowed to leave out \"op\" in\n"
   "help mode.  Supported values for \"op\" are:\n\n"
+  "  config      Read configuration values.\n"
+  "  config_opt  Read a single configuration value.\n"
+  "  decrypt     Decrypt data.\n"
+  "  delete      Delete a key.\n"
   "  encrypt     Encrypt data.\n"
-  "  getmore     Retrieve remaining data.\n"
-  "  help        Help overview.";
+  "  export      Export keys.\n"
+  "  createkey   Generate a keypair (OpenPGP only).\n"
+  "  import      Import data.\n"
+  "  keylist     List keys.\n"
+  "  sign        Sign data.\n"
+  "  verify      Verify data.\n"
+  "  version     Get engine information.\n"
+  "  getmore     Retrieve remaining data if chunksize was used.\n"
+  "  help        Help overview.\n"
+  "\n"
+  "If the data needs to be transferred in smaller chunks the\n"
+  "property \"chunksize\" with an integer value can be added.\n"
+  "When \"chunksize\" is set the response (including json) will\n"
+  "not be larger then \"chunksize\" but might be smaller.\n"
+  "The chunked result will be transferred in base64 encoded chunks\n"
+  "using the \"getmore\" operation. See help getmore for more info.";
 static gpg_error_t
 op_help (cjson_t request, cjson_t result)
 {
@@ -1090,10 +3207,20 @@ process_request (const char *request)
     gpg_error_t (*handler)(cjson_t request, cjson_t result);
     const char * const helpstr;
   } optbl[] = {
-    { "encrypt", op_encrypt, hlp_encrypt },
-    { "decrypt", op_decrypt, hlp_decrypt },
-    { "getmore", op_getmore, hlp_getmore },
-    { "help",    op_help,    hlp_help },
+    { "config",     op_config,     hlp_config },
+    { "config_opt", op_config_opt, hlp_config_opt },
+    { "encrypt",    op_encrypt,    hlp_encrypt },
+    { "export",     op_export,     hlp_export },
+    { "decrypt",    op_decrypt,    hlp_decrypt },
+    { "delete",     op_delete,     hlp_delete },
+    { "createkey",  op_createkey,  hlp_createkey },
+    { "keylist",    op_keylist,    hlp_keylist },
+    { "import",     op_import,     hlp_import },
+    { "sign",       op_sign,       hlp_sign },
+    { "verify",     op_verify,     hlp_verify },
+    { "version",    op_version,    hlp_version },
+    { "getmore",    op_getmore,    hlp_getmore },
+    { "help",       op_help,       hlp_help },
     { NULL }
   };
   size_t erroff;
@@ -1101,8 +3228,9 @@ process_request (const char *request)
   cjson_t j_tmp, j_op;
   cjson_t response;
   int helpmode;
+  int is_getmore = 0;
   const char *op;
-  char *res;
+  char *res = NULL;
   int idx;
 
   response = xjson_CreateObject ();
@@ -1146,7 +3274,7 @@ process_request (const char *request)
       else
         {
           gpg_error_t err;
-
+          is_getmore = optbl[idx].handler == op_getmore;
           /* If this is not the "getmore" command and we have any
            * pending data release that data.  */
           if (pending_data.buffer && optbl[idx].handler != op_getmore)
@@ -1163,8 +3291,8 @@ process_request (const char *request)
                   || strcmp (j_tmp->valuestring, "error"))
                 {
                   /* No error type response - provide a generic one.  */
-                  error_object (response, "Operation failed: %s",
-                                gpg_strerror (err));
+                  gpg_error_object (response, err, "Operation failed: %s",
+                                    gpg_strerror (err));
                 }
 
               xjson_AddStringToObject (response, "op", op);
@@ -1178,14 +3306,37 @@ process_request (const char *request)
     }
 
  leave:
-  cJSON_Delete (json);
-  if (opt_interactive)
-    res = cJSON_Print (response);
+  if (is_getmore)
+    {
+      /* For getmore we bypass the encode_and_chunk. */
+      if (opt_interactive)
+        res = cJSON_Print (response);
+      else
+        res = cJSON_PrintUnformatted (response);
+    }
   else
-    res = cJSON_PrintUnformatted (response);
+    res = encode_and_chunk (json, response);
   if (!res)
-    log_error ("Printing JSON data failed\n");
+    {
+      cjson_t err_obj;
+
+      log_error ("printing JSON data failed\n");
+
+      err_obj = error_object (NULL, "Printing JSON data failed");
+      if (opt_interactive)
+        res = cJSON_Print (err_obj);
+      res = cJSON_PrintUnformatted (err_obj);
+      cJSON_Delete (err_obj);
+    }
+
+  cJSON_Delete (json);
   cJSON_Delete (response);
+
+  if (!res)
+    {
+      /* Can't happen unless we created a broken error_object above */
+      return xtrystrdup ("Bug: Fatal error in process request\n");
+    }
   return res;
 }
 
@@ -1557,7 +3708,7 @@ native_messaging_repl (void)
         }
 
       /* Read request.  */
-      request = xtrymalloc (nrequest);
+      request = xtrymalloc (nrequest + 1);
       if (!request)
         {
           err = gpg_error_from_syserror ();
@@ -1582,6 +3733,7 @@ native_messaging_repl (void)
         }
       else /* Process request  */
         {
+          request[n] = '\0'; /* Ensure that request has an end */
           if (opt_debug)
             log_debug ("request='%s'\n", request);
           xfree (response);
@@ -1620,6 +3772,10 @@ native_messaging_repl (void)
           log_error ("error writing request: %s\n", gpg_strerror (err));
           break;
         }
+      xfree (response);
+      response = NULL;
+      xfree (request);
+      request = NULL;
     }
 
   xfree (response);
@@ -1684,6 +3840,8 @@ main (int argc, char *argv[])
   };
   gpgrt_argparse_t pargs = { &argc, &argv};
 
+  int log_file_set = 0;
+
   gpgrt_set_strusage (my_strusage);
 
 #ifdef HAVE_SETLOCALE
@@ -1720,12 +3878,24 @@ main (int argc, char *argv[])
 
   if (!opt_debug)
     {
+      /* Handling is similar to GPGME_DEBUG */
       const char *s = getenv ("GPGME_JSON_DEBUG");
+      const char *s1;
+
       if (s && atoi (s) > 0)
-        opt_debug = 1;
+        {
+          opt_debug = 1;
+          s1 = strchr (s, PATHSEP_C);
+          if (s1 && strlen (s1) > 2)
+            {
+              s1++;
+              log_set_file (s1);
+              log_file_set = 1;
+            }
+        }
     }
 
-  if (opt_debug)
+  if (opt_debug && !log_file_set)
     {
       const char *home = getenv ("HOME");
       char *file = xstrconcat ("socket://",
index 82d6747..2d829d9 100644 (file)
@@ -249,6 +249,7 @@ gpgme_release (gpgme_ctx_t ctx)
   free (ctx->lc_messages);
   free (ctx->override_session_key);
   free (ctx->request_origin);
+  free (ctx->auto_key_locate);
   _gpgme_engine_info_release (ctx->engine_info);
   ctx->engine_info = NULL;
   DESTROY_LOCK (ctx->lock);
@@ -542,6 +543,17 @@ gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value)
     {
       ctx->no_symkey_cache = abool;
     }
+  else if (!strcmp (name, "ignore-mdc-error"))
+    {
+      ctx->ignore_mdc_error = abool;
+    }
+  else if (!strcmp (name, "auto-key-locate"))
+    {
+      free (ctx->auto_key_locate);
+      ctx->auto_key_locate = strdup (value);
+      if (!ctx->auto_key_locate)
+        err = gpg_error_from_syserror ();
+    }
   else
     err = gpg_error (GPG_ERR_UNKNOWN_NAME);
 
@@ -591,6 +603,14 @@ gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name)
     {
       return ctx->no_symkey_cache? "1":"";
     }
+  else if (!strcmp (name, "ignore-mdc-error"))
+    {
+      return ctx->ignore_mdc_error? "1":"";
+    }
+  else if (!strcmp (name, "auto-key-locate"))
+    {
+      return ctx->auto_key_locate? ctx->auto_key_locate : "";
+    }
   else
     return NULL;
 }
index a01d89a..c690220 100644 (file)
@@ -272,5 +272,7 @@ EXPORTS
     gpgme_op_encrypt_sign_ext             @202
     gpgme_op_encrypt_sign_ext_start       @203
 
+    gpgme_data_new_from_estream           @204
+
 ; END
 
index 49fafb9..3bf968e 100644 (file)
@@ -95,6 +95,12 @@ extern "C" {
 #define _GPGME_DEPRECATED_OUTSIDE_GPGME(a,b) _GPGME_DEPRECATED(a,b)
 #endif
 
+/* We used to use some symbols which clash with keywords in some
+ * languages.  This macro is used to obsolete them.  */
+#if defined(__cplusplus) || defined(SWIGPYTHON)
+# define _GPGME_OBSOLETE_SOME_SYMBOLS 1
+#endif
+
 
 /* Check for a matching _FILE_OFFSET_BITS definition.  */
 #if @NEED__FILE_OFFSET_BITS@
@@ -399,12 +405,15 @@ gpgme_pinentry_mode_t;
 #define GPGME_EXPORT_MODE_SECRET               16
 #define GPGME_EXPORT_MODE_RAW                  32
 #define GPGME_EXPORT_MODE_PKCS12               64
+#define GPGME_EXPORT_MODE_NOUID               128  /* Experimental(!)*/
 
 typedef unsigned int gpgme_export_mode_t;
 
 
 /* Flags for the audit log functions.  */
+#define GPGME_AUDITLOG_DEFAULT   0
 #define GPGME_AUDITLOG_HTML      1
+#define GPGME_AUDITLOG_DIAG      2
 #define GPGME_AUDITLOG_WITH_HELP 128
 
 
@@ -638,7 +647,7 @@ struct _gpgme_key_sig
   gpgme_error_t status;
 
   /* Deprecated; use SIG_CLASS instead.  */
-#ifdef __cplusplus
+#ifdef _GPGME_OBSOLETE_SOME_SYMBOLS
   unsigned int _obsolete_class _GPGME_DEPRECATED(0,4);
 #else
   unsigned int class _GPGME_DEPRECATED_OUTSIDE_GPGME(0,4);
@@ -1178,6 +1187,8 @@ gpgme_error_t gpgme_data_new_from_cbs (gpgme_data_t *dh,
 gpgme_error_t gpgme_data_new_from_fd (gpgme_data_t *dh, int fd);
 
 gpgme_error_t gpgme_data_new_from_stream (gpgme_data_t *dh, FILE *stream);
+gpgme_error_t gpgme_data_new_from_estream (gpgme_data_t *r_dh,
+                                           gpgrt_stream_t stream);
 
 /* Return the encoding attribute of the data buffer DH */
 gpgme_data_encoding_t gpgme_data_get_encoding (gpgme_data_t dh);
@@ -1365,8 +1376,12 @@ struct _gpgme_op_decrypt_result
   /* The message claims that the content is a MIME object.  */
   unsigned int is_mime : 1;
 
+  /* The message was made by a legacy algorithm without any integrity
+   * protection.  This might be an old but legitimate message. */
+  unsigned int legacy_cipher_nomdc : 1;
+
   /* Internal to GPGME, do not use.  */
-  int _unused : 29;
+  int _unused : 28;
 
   gpgme_recipient_t recipients;
 
@@ -1458,7 +1473,7 @@ struct _gpgme_new_signature
   char *fpr;
 
   /* Deprecated; use SIG_CLASS instead.  */
-#ifdef __cplusplus
+#ifdef _GPGME_OBSOLETE_SOME_SYMBOLS
   unsigned int _obsolete_class_2;
 #else
   unsigned int class _GPGME_DEPRECATED_OUTSIDE_GPGME(0,4);
@@ -1583,11 +1598,12 @@ struct _gpgme_op_verify_result
 {
   gpgme_signature_t signatures;
 
-  /* The original file name of the plaintext message, if
-     available.  */
+  /* The original file name of the plaintext message, if available.
+   * Warning: This information is not covered by the signature.  */
   char *file_name;
 
   /* The message claims that the content is a MIME object.  */
+  /* Warning: This flag is not covered by the signature.  */
   unsigned int is_mime : 1;
 
   /* Internal to GPGME; do not use.  */
index c2fcabb..5e49793 100644 (file)
@@ -171,7 +171,7 @@ keysign_start (gpgme_ctx_t ctx, int synchronous,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-        (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+        (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
         return err;
     }
index b49c86d..7531f54 100644 (file)
@@ -133,6 +133,9 @@ GPGME_1.1 {
 
     gpgme_op_decrypt_ext;
     gpgme_op_decrypt_ext_start;
+
+    gpgme_data_new_from_estream;
+
 };
 
 
index 03f274c..9414e61 100644 (file)
@@ -414,6 +414,9 @@ _gpgme_parse_failure (char *args)
 {
   char *where, *which;
 
+  if (!strncmp (args, "gpg-exit", 8))
+    return 0;
+
   where = strchr (args, ' ');
   if (!where)
     return trace_gpg_error (GPG_ERR_INV_ENGINE);
@@ -425,9 +428,5 @@ _gpgme_parse_failure (char *args)
   if (where)
     *where = '\0';
 
-  where = args;
-  if (!strcmp (where, "gpg-exit"))
-    return 0;
-
   return atoi (which);
 }
index 5955454..3b9728d 100644 (file)
--- a/src/ops.h
+++ b/src/ops.h
@@ -85,7 +85,8 @@ gpgme_error_t _gpgme_verify_status_handler (void *priv,
 
 \f
 /* From decrypt.c.  */
-gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx);
+gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx,
+                                             gpgme_data_t plaintext);
 gpgme_error_t _gpgme_decrypt_status_handler (void *priv,
                                             gpgme_status_code_t code,
                                             char *args);
index 5bd67a5..6c03002 100644 (file)
@@ -151,7 +151,7 @@ passwd_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-        (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+        (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
         return err;
     }
index bfd9ad1..ab4109e 100644 (file)
@@ -449,7 +449,7 @@ sign_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t plain,
   if (ctx->passphrase_cb)
     {
       err = _gpgme_engine_set_command_handler
-       (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL);
+       (ctx->engine, _gpgme_passphrase_command_handler, ctx);
       if (err)
        return err;
     }
index 5b02c7e..30dd081 100644 (file)
 # define F_OK 0
 #endif
 
+/* The Registry key used by GNUPG.  */
+#ifdef _WIN64
+# define GNUPG_REGKEY_2  "Software\\Wow6432Node\\GNU\\GnuPG"
+#else
+# define GNUPG_REGKEY_2  "Software\\GNU\\GnuPG"
+#endif
+#ifdef _WIN64
+# define GNUPG_REGKEY_3  "Software\\Wow6432Node\\GnuPG"
+#else
+# define GNUPG_REGKEY_3  "Software\\GnuPG"
+#endif
 
 DEFINE_STATIC_LOCK (get_path_lock);
 
@@ -513,7 +524,7 @@ _gpgme_get_gpg_path (void)
       char *dir;
 
       dir = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
-                                      "Software\\GNU\\GnuPG",
+                                      GNUPG_REGKEY_2,
                                       "Install Directory");
       if (dir)
         {
@@ -568,12 +579,12 @@ _gpgme_get_gpgconf_path (void)
       char *dir;
 
       dir = read_w32_registry_string (NULL,
-                                      "Software\\GNU\\GnuPG",
+                                      GNUPG_REGKEY_2,
                                       "Install Directory");
       if (!dir)
         {
           char *tmp = read_w32_registry_string (NULL,
-                                                "Software\\GnuPG",
+                                                GNUPG_REGKEY_3,
                                                 "Install Directory");
           if (tmp)
             {
@@ -596,6 +607,14 @@ _gpgme_get_gpgconf_path (void)
       gpgconf = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe");
     }
 
+  /* 5. Try to find gpgconf.exe relative to us.  */
+  if (!gpgconf && inst_dir)
+    {
+      char *dir = _gpgme_strconcat (inst_dir, "\\..\\..\\GnuPG\\bin");
+      gpgconf = find_program_in_dir (dir, name);
+      free (dir);
+    }
+
   /* 5. Print a debug message if not found.  */
   if (!gpgconf)
     _gpgme_debug (DEBUG_ENGINE, "_gpgme_get_gpgconf_path: '%s' not found",name);
index 30c35f0..b5825d2 100644 (file)
@@ -19,7 +19,8 @@
 
 ## Process this file with automake to produce Makefile.in
 
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir)
+GNUPGHOME=$(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME)
 
 TESTS = t-version t-data t-engine-info
 
index 7f6727c..311f8d3 100644 (file)
@@ -123,7 +123,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 am__EXEEXT_1 = t-version$(EXEEXT) t-data$(EXEEXT) \
@@ -205,7 +205,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
@@ -519,7 +519,8 @@ target_alias = @target_alias@
 top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir)
+GNUPGHOME = $(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME)
 EXTRA_DIST = start-stop-agent t-data-1.txt t-data-2.txt ChangeLog-2011
 AM_CPPFLAGS = -I$(top_builddir)/src @GPG_ERROR_CFLAGS@
 AM_LDFLAGS = -no-install
index b50f4b0..ba2d1f3 100644 (file)
@@ -22,7 +22,8 @@
 GPG = gpg
 GPG_AGENT = gpg-agent
 
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) LC_ALL=C GPG_AGENT_INFO= \
+GNUPGHOME=$(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) LC_ALL=C GPG_AGENT_INFO= \
                     top_srcdir=$(top_srcdir)
 
 # The keylist tests must come after the import and the edit test.
@@ -104,5 +105,11 @@ gpg.conf:
        echo no-force-v3-sigs > ./gpg.conf
 
 gpg-agent.conf:
-# This is required for gpg2, which does not support command fd.
+# This is required for gpg2, which does not support command fd for the
+# passphrase.  disable-scdaemon is required so that we don't try using
+# a key from a smartcard reader (error might be: Unusable secret key)
        echo pinentry-program $(abs_srcdir)/pinentry > ./gpg-agent.conf
+       echo disable-scdaemon >> ./gpg-agent.conf
+
+
+# end-of-file
index f09f94a..f8cf735 100644 (file)
@@ -120,7 +120,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 @HAVE_W32_SYSTEM_FALSE@am__EXEEXT_1 = t-eventloop$(EXEEXT) \
@@ -254,7 +254,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
@@ -534,7 +534,8 @@ top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 GPG = gpg
 GPG_AGENT = gpg-agent
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) LC_ALL=C GPG_AGENT_INFO= \
+GNUPGHOME = $(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) LC_ALL=C GPG_AGENT_INFO= \
                     top_srcdir=$(top_srcdir)
 
 
@@ -1114,8 +1115,13 @@ gpg.conf:
        echo no-force-v3-sigs > ./gpg.conf
 
 gpg-agent.conf:
-# This is required for gpg2, which does not support command fd.
+# This is required for gpg2, which does not support command fd for the
+# passphrase.  disable-scdaemon is required so that we don't try using
+# a key from a smartcard reader (error might be: Unusable secret key)
        echo pinentry-program $(abs_srcdir)/pinentry > ./gpg-agent.conf
+       echo disable-scdaemon >> ./gpg-agent.conf
+
+# end-of-file
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
index d2acd05..c259920 100644 (file)
@@ -22,7 +22,8 @@
 GPGSM = gpgsm
 GPG_AGENT = gpg-agent
 
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) LC_ALL=C GPG_AGENT_INFO= \
+GNUPGHOME=$(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) LC_ALL=C GPG_AGENT_INFO= \
                     top_srcdir=$(top_srcdir)
 
 noinst_HEADERS = t-support.h
index 307b3f1..afa0f15 100644 (file)
@@ -120,7 +120,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 am__EXEEXT_1 = t-import$(EXEEXT) t-keylist$(EXEEXT) t-encrypt$(EXEEXT) \
@@ -183,7 +183,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
@@ -455,7 +455,8 @@ top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 GPGSM = gpgsm
 GPG_AGENT = gpg-agent
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) LC_ALL=C GPG_AGENT_INFO= \
+GNUPGHOME = $(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) LC_ALL=C GPG_AGENT_INFO= \
                     top_srcdir=$(top_srcdir)
 
 noinst_HEADERS = t-support.h
index 31d26ed..1dba3e8 100644 (file)
@@ -17,7 +17,8 @@
 
 ## Process this file with automake to produce Makefile.in
 
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO=
+GNUPGHOME=$(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) GPG_AGENT_INFO=
 
 noinst_HEADERS =
 TESTS =
index 5afbeca..37531ce 100644 (file)
@@ -117,7 +117,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
        $(ACLOCAL_M4)
 mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs
-CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_HEADER = $(top_builddir)/conf/config.h
 CONFIG_CLEAN_FILES =
 CONFIG_CLEAN_VPATH_FILES =
 am__EXEEXT_1 =
@@ -142,7 +142,7 @@ AM_V_at = $(am__v_at_@AM_V@)
 am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
 am__v_at_0 = @
 am__v_at_1 = 
-DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)/conf
 depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
 am__depfiles_maybe = depfiles
 am__mv = mv -f
@@ -408,7 +408,8 @@ target_alias = @target_alias@
 top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
-TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO=
+GNUPGHOME = $(abs_builddir)
+TESTS_ENVIRONMENT = GNUPGHOME=$(GNUPGHOME) GPG_AGENT_INFO=
 noinst_HEADERS = 
 EXTRA_DIST = 
 AM_CPPFLAGS = -I$(top_builddir)/src @GPG_ERROR_CFLAGS@
index 69de139..c9d9e72 100644 (file)
@@ -55,6 +55,7 @@ print_result (gpgme_decrypt_result_t result)
 
   printf ("Original file name .: %s\n", nonnull(result->file_name));
   printf ("Wrong key usage ....: %s\n", result->wrong_key_usage? "yes":"no");
+  printf ("Legacy w/o MDC ... .: %s\n", result->legacy_cipher_nomdc?"yes":"no");
   printf ("Compliance de-vs ...: %s\n", result->is_de_vs? "yes":"no");
   printf ("MIME flag ..........: %s\n", result->is_mime? "yes":"no");
   printf ("Unsupported algo ...: %s\n", nonnull(result->unsupported_algorithm));
@@ -85,7 +86,9 @@ show_usage (int ex)
          "  --override-session-key STRING   use STRING as session key\n"
          "  --request-origin STRING         use STRING as request origin\n"
          "  --no-symkey-cache               disable the use of that cache\n"
+         "  --ignore-mdc-error              allow decryption of legacy data\n"
          "  --unwrap         remove only the encryption layer\n"
+         "  --diagnostics    print diagnostics\n"
          , stderr);
   exit (ex);
 }
@@ -108,7 +111,9 @@ main (int argc, char **argv)
   const char *override_session_key = NULL;
   const char *request_origin = NULL;
   int no_symkey_cache = 0;
+  int ignore_mdc_error = 0;
   int raw_output = 0;
+  int diagnostics = 0;
 
   if (argc)
     { argc--; argv++; }
@@ -169,6 +174,16 @@ main (int argc, char **argv)
           no_symkey_cache = 1;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--ignore-mdc-error"))
+        {
+          ignore_mdc_error = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--diagnostics"))
+        {
+          diagnostics = 1;
+          argc--; argv++;
+        }
       else if (!strcmp (*argv, "--unwrap"))
         {
           flags |= GPGME_DECRYPT_UNWRAP;
@@ -240,7 +255,18 @@ main (int argc, char **argv)
       err = gpgme_set_ctx_flag (ctx, "no-symkey-cache", "1");
       if (err)
         {
-          fprintf (stderr, PGM ": error setting no-symkey-cache:  %s\n",
+          fprintf (stderr, PGM ": error setting no-symkey-cache: %s\n",
+                   gpgme_strerror (err));
+          exit (1);
+        }
+    }
+
+  if (ignore_mdc_error)
+    {
+      err = gpgme_set_ctx_flag (ctx, "ignore-mdc-error", "1");
+      if (err)
+        {
+          fprintf (stderr, PGM ": error setting ignore-mdc-error: %s\n",
                    gpgme_strerror (err));
           exit (1);
         }
@@ -264,9 +290,33 @@ main (int argc, char **argv)
 
   err = gpgme_op_decrypt_ext (ctx, flags, in, out);
   result = gpgme_op_decrypt_result (ctx);
+
+  if (diagnostics)
+    {
+      gpgme_data_t diag;
+      gpgme_error_t diag_err;
+
+      gpgme_data_new (&diag);
+      diag_err = gpgme_op_getauditlog (ctx, diag, GPGME_AUDITLOG_DIAG);
+      if (diag_err)
+        {
+          fprintf (stderr, PGM ": getting diagnostics failed: %s\n",
+                   gpgme_strerror (diag_err));
+        }
+      else
+        {
+          fputs ("Begin Diagnostics:\n", stdout);
+          print_data (diag);
+          fputs ("End Diagnostics.\n", stdout);
+        }
+      gpgme_data_release (diag);
+    }
+
   if (err)
     {
       fprintf (stderr, PGM ": decrypt failed: %s\n", gpgme_strerror (err));
+      if (result)
+        print_result (result);
       exit (1);
     }
   if (result)
index 295251a..9206b50 100644 (file)
@@ -47,6 +47,7 @@ show_usage (int ex)
          "  --openpgp        use the OpenPGP protocol (default)\n"
          "  --cms            use the CMS protocol\n"
          "  --secret         list only secret keys\n"
+         "  --with-secret    list pubkeys with secret info filled\n"
          "  --local          use GPGME_KEYLIST_MODE_LOCAL\n"
          "  --extern         use GPGME_KEYLIST_MODE_EXTERN\n"
          "  --sigs           use GPGME_KEYLIST_MODE_SIGS\n"
@@ -57,6 +58,7 @@ show_usage (int ex)
          "  --import         import all keys\n"
          "  --offline        use offline mode\n"
          "  --from-file      list all keys in the given file\n"
+         "  --from-wkd       list key from a web key directory\n"
          "  --require-gnupg  required at least the given GnuPG version\n"
          , stderr);
   exit (ex);
@@ -100,6 +102,7 @@ main (int argc, char **argv)
   int only_secret = 0;
   int offline = 0;
   int from_file = 0;
+  int from_wkd = 0;
   gpgme_data_t data = NULL;
 
 
@@ -171,6 +174,11 @@ main (int argc, char **argv)
           mode |= GPGME_KEYLIST_MODE_VALIDATE;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--with-secret"))
+        {
+          mode |= GPGME_KEYLIST_MODE_WITH_SECRET;
+          argc--; argv++;
+        }
       else if (!strcmp (*argv, "--import"))
         {
           import = 1;
@@ -194,6 +202,12 @@ main (int argc, char **argv)
           gpgme_set_global_flag ("require-gnupg", *argv);
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--from-wkd"))
+        {
+          argc--; argv++;
+          mode |= GPGME_KEYLIST_MODE_LOCATE;
+          from_wkd = 1;
+        }
       else if (!strncmp (*argv, "--", 2))
         show_usage (1);
     }
@@ -213,6 +227,13 @@ main (int argc, char **argv)
 
   gpgme_set_offline (ctx, offline);
 
+  if (from_wkd)
+    {
+      err = gpgme_set_ctx_flag (ctx, "auto-key-locate",
+                                "clear,nodefault,wkd");
+      fail_if_err (err);
+    }
+
   if (from_file)
     {
       err = gpgme_data_new_from_file (&data, *argv, 1);