[Python-modules-commits] [python-gnupg] 01/05: Import python-gnupg_0.3.8.orig.tar.gz

Elena Grandi valhalla-guest at moszumanska.debian.org
Sun Nov 29 13:49:38 UTC 2015


This is an automated email from the git hooks/post-receive script.

valhalla-guest pushed a commit to branch master
in repository python-gnupg.

commit 16065427a552e386da63551fe79e7836133dfac5
Author: Elena Grandi <valhalla-d at trueelena.org>
Date:   Tue Oct 13 10:26:13 2015 +0200

    Import python-gnupg_0.3.8.orig.tar.gz
---
 PKG-INFO         |   6 ++--
 README.rst       |  51 ++++++++++++++++++++++++++-
 gnupg.py         | 105 ++++++++++++++++++++++++++++++++++++++++++++-----------
 setup.py         |   2 ++
 test_gnupg.py    |  52 ++++++++++++++++++++++++++-
 test_pubring.gpg | Bin 0 -> 4399 bytes
 test_secring.gpg | Bin 0 -> 6724 bytes
 7 files changed, 191 insertions(+), 25 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 1f04c7c..09fab1f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.0
 Name: python-gnupg
-Version: 0.3.7
+Version: 0.3.8
 Summary: A wrapper for the Gnu Privacy Guard (GPG or GnuPG)
 Home-page: http://packages.python.org/python-gnupg/index.html
 Author: Vinay Sajip
 Author-email: vinay_sajip at red-dove.com
 License: Copyright (C) 2008-2014 by Vinay Sajip. All Rights Reserved. See LICENSE.txt for license.
-Download-URL: https://pypi.python.org/packages/source/p/python-gnupg/python-gnupg-0.3.7.tar.gz
+Download-URL: https://pypi.python.org/packages/source/p/python-gnupg/python-gnupg-0.3.8.tar.gz
 Description: This module allows easy access to GnuPG's key management, encryption and signature functionality from Python programs. It is intended for use with Python 2.4 or greater.
 Platform: No particular restrictions
 Classifier: Development Status :: 5 - Production/Stable
@@ -22,5 +22,7 @@ Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Classifier: Operating System :: OS Independent
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README.rst b/README.rst
index 21e7b6f..8e0c387 100644
--- a/README.rst
+++ b/README.rst
@@ -1,3 +1,10 @@
+.. image:: https://travis-ci.org/vsajip/python-gnupg.svg
+   :target: https://travis-ci.org/vsajip/python-gnupg
+
+.. image:: https://coveralls.io/repos/vsajip/python-gnupg/badge.svg
+   :target: https://coveralls.io/github/vsajip/python-gnupg
+
+
 What is it?
 ===========
 
@@ -47,16 +54,58 @@ Change log
 
 N.B: GCnn refers to an issue nn on Google Code.
 
-0.3.8 (future)
+0.3.9 (future)
 --------------
 
 Released: Not yet
 
+0.3.8
+-----
+
+Released: 2015-09-24
+
+* Fixed #22: handled ``PROGRESS`` messages during verification and signing.
+
+* Fixed #26: handled ``PINENTRY_LAUNCHED`` messages during verification,
+  decryption and key generation.
+
+* Fixed #28: Allowed a default Name-Email to be computed even when neither of
+  ``LOGNAME`` and ``USERNAME`` are in the environment.
+
+* Fixed #29: Included test files missing from the tarball in previous versions.
+
+* Fixed #39: On Python 3.x, passing a text instead of a binary stream caused
+  file decryption to hang due to a ``UnicodeDecodeError``. This has now been
+  correctly handled: The decryption fails with a "no data" status.
+
+* Fixed #41: Handled Unicode filenames correctly by encoding them on 2.x using
+  the file system encoding.
+
+* Fixed #43: handled ``PINENTRY_LAUNCHED`` messages during key export. Thanks
+  to Ian Denhardt for looking into this.
+
+* Hide the console window which appears on Windows when gpg is spawned.
+  Thanks to Kévin Bernard-Allies for the patch.
+
+* Subkey fingerprints are now captured.
+
+* The returned value from the ``list_keys`` method now has a new attribute,
+  ``key_map``, which is a dictionary mapping key and subkey fingerprints to
+  the corresponding key's dictionary. With this change, you don't need to
+  iterate over the (potentially large) returned list to search for a key with
+  a given fingerprint - the ``key_map`` dict will take you straight to the key
+  info, whether the fingerprint you have is for a key or a subkey. Thanks to
+  Nick Daly for the initial suggestion.
+
 0.3.7
 -----
 
 Released: 2014-12-07
 
+Signed with PGP key: Vinay Sajip (CODE SIGNING KEY) <vinay_sajip at yahoo.co.uk>
+
+Key Fingerprint    : CA74 9061 914E AC13 8E66 EADB 9147 B477 339A 9B86
+
 * Added an ``output`` keyword parameter to the ``sign`` and
   ``sign_file`` methods, to allow writing the signature to a file.
   Thanks to Jannis Leidel for the patch.
diff --git a/gnupg.py b/gnupg.py
index 353f234..cfc6cb9 100644
--- a/gnupg.py
+++ b/gnupg.py
@@ -32,9 +32,9 @@ Modifications Copyright (C) 2008-2014 Vinay Sajip. All rights reserved.
 A unittest harness (test_gnupg.py) has also been added.
 """
 
-__version__ = "0.3.7"
+__version__ = "0.3.8"
 __author__ = "Vinay Sajip"
-__date__  = "$07-Dec-2014 18:46:17$"
+__date__  = "$24-Sep-2015 18:03:55$"
 
 try:
     from io import StringIO
@@ -52,6 +52,13 @@ from subprocess import PIPE
 import sys
 import threading
 
+STARTUPINFO = None
+if os.name == 'nt':
+    try:
+        from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW, SW_HIDE
+    except ImportError:
+        STARTUPINFO = None
+
 try:
     import logging.NullHandler as NullHandler
 except ImportError:
@@ -112,8 +119,18 @@ else:
 
 # Now that we use shell=False, we shouldn't need to quote arguments.
 # Use no_quote instead of shell_quote to remind us of where quoting
-# was needed.
+# was needed. However, note that we still need, on 2.x, to encode any
+# Unicode argument with the file system encoding - see Issue #41 and
+# Python issue #1759845 ("subprocess.call fails with unicode strings in
+# command line").
+
+# Allows the encoding used to be overridden in special cases by setting
+# this module attribute appropriately.
+fsencoding = sys.getfilesystemencoding()
+
 def no_quote(s):
+    if not _py3k and isinstance(s, text_type):
+        s = s.encode(fsencoding)
     return s
 
 def _copy_data(instream, outstream):
@@ -124,7 +141,13 @@ def _copy_data(instream, outstream):
     else:
         enc = 'ascii'
     while True:
-        data = instream.read(1024)
+        # See issue #39: read can fail when e.g. a text stream is provided
+        # for what is actually a binary file
+        try:
+            data = instream.read(1024)
+        except UnicodeError:
+            logger.warning('Exception occurred while reading', exc_info=1)
+            break
         if not data:
             break
         sent += len(data)
@@ -221,7 +244,8 @@ class Verify(object):
                      "PLAINTEXT_LENGTH", "POLICY_URL", "DECRYPTION_INFO",
                      "DECRYPTION_OKAY", "INV_SGNR", "FILE_START", "FILE_ERROR",
                      "FILE_DONE", "PKA_TRUST_GOOD", "PKA_TRUST_BAD", "BADMDC",
-                     "GOODMDC", "NO_SGNR", "NOTATION_NAME", "NOTATION_DATA"):
+                     "GOODMDC", "NO_SGNR", "NOTATION_NAME", "NOTATION_DATA",
+                     "PROGRESS", "PINENTRY_LAUNCHED", "NEWSIG"):
             pass
         elif key == "BADSIG":
             self.valid = False
@@ -443,6 +467,11 @@ class ListKeys(SearchKeys):
     UID_INDEX = 9
     FIELDS = 'type trust length algo keyid date expires dummy ownertrust uid'.split()
 
+    def __init__(self, gpg):
+        super(ListKeys, self).__init__(gpg)
+        self.in_subkey = False
+        self.key_map = {}
+
     def key(self, args):
         self.curkey = curkey = self.get_fields(args)
         if curkey['uid']:
@@ -450,16 +479,26 @@ class ListKeys(SearchKeys):
         del curkey['uid']
         curkey['subkeys'] = []
         self.append(curkey)
+        self.in_subkey = False
 
     pub = sec = key
 
     def fpr(self, args):
-        self.curkey['fingerprint'] = args[9]
-        self.fingerprints.append(args[9])
+        fp = args[9]
+        if fp in self.key_map:
+            raise ValueError('Unexpected fingerprint collision: %s' % fp)
+        if not self.in_subkey:
+            self.curkey['fingerprint'] = fp
+            self.fingerprints.append(fp)
+            self.key_map[fp] = self.curkey
+        else:
+            self.curkey['subkeys'][-1].append(fp)
+            self.key_map[fp] = self.curkey
 
     def sub(self, args):
-        subkey = [args[4], args[11]]
+        subkey = [args[4], args[11]]    # keyid, type
         self.curkey['subkeys'].append(subkey)
+        self.in_subkey = True
 
 
 class ScanKeys(ListKeys):
@@ -470,6 +509,7 @@ class ScanKeys(ListKeys):
         # use the last value args[-1] instead of args[11]
         subkey = [args[4], args[-1]]
         self.curkey['subkeys'].append(subkey)
+        self.in_subkey = True
 
 class TextHandler(object):
     def _as_text(self):
@@ -501,10 +541,12 @@ class Crypt(Verify, TextHandler):
     def handle_status(self, key, value):
         if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
                    "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA", "PROGRESS",
-                   "CARDCTRL", "BADMDC", "SC_OP_FAILURE", "SC_OP_SUCCESS"):
+                   "CARDCTRL", "BADMDC", "SC_OP_FAILURE", "SC_OP_SUCCESS",
+                   "PINENTRY_LAUNCHED"):
             # in the case of ERROR, this is because a more specific error
             # message will have come first
-            pass
+            if key == "NODATA":
+                self.status = "no data was provided"
         elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
                      "MISSING_PASSPHRASE", "DECRYPTION_FAILED",
                      "KEY_NOT_CREATED", "NEED_PASSPHRASE_PIN"):
@@ -549,13 +591,22 @@ class GenKey(object):
         return self.fingerprint or ''
 
     def handle_status(self, key, value):
-        if key in ("PROGRESS", "GOOD_PASSPHRASE", "NODATA", "KEY_NOT_CREATED"):
+        if key in ("PROGRESS", "GOOD_PASSPHRASE", "NODATA", "KEY_NOT_CREATED",
+                   "PINENTRY_LAUNCHED"):
             pass
         elif key == "KEY_CREATED":
             (self.type,self.fingerprint) = value.split()
         else:
             raise ValueError("Unknown status message: %r" % key)
 
+class ExportResult(GenKey):
+    """Handle status messages for --export[-secret-key].
+
+    For now, just use an existing class to base it on - if needed, we
+    can override handle_status for more specific message handling.
+    """
+    pass
+
 class DeleteResult(object):
     "Handle status messages for --delete-key and --delete-secret-key"
     def __init__(self, gpg):
@@ -601,7 +652,8 @@ class Sign(TextHandler):
         if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
                    "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL", "INV_SGNR",
                    "NO_SGNR", "MISSING_PASSPHRASE", "NEED_PASSPHRASE_PIN",
-                   "SC_OP_FAILURE", "SC_OP_SUCCESS"):
+                   "SC_OP_FAILURE", "SC_OP_SUCCESS", "PROGRESS",
+                   "PINENTRY_LAUNCHED"):
             pass
         elif key in ("KEYEXPIRED", "SIGEXPIRED"):
             self.status = 'key expired'
@@ -633,6 +685,7 @@ class GPG(object):
         'search': SearchKeys,
         'sign': Sign,
         'verify': Verify,
+        'export': ExportResult,
     }
 
     "Encapsulate access to the gpg executable"
@@ -723,7 +776,14 @@ class GPG(object):
             pcmd = ' '.join(cmd)
             print(pcmd)
         logger.debug("%s", cmd)
-        return Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE)
+        if not STARTUPINFO:
+            si = None
+        else:
+            si = STARTUPINFO()
+            si.dwFlags = STARTF_USESHOWWINDOW
+            si.wShowWindow = SW_HIDE
+        return Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE,
+                     startupinfo=si)
 
     def _read_response(self, stream, result):
         # Internal method: reads all the stderr output from GPG, taking notice
@@ -1050,7 +1110,7 @@ class GPG(object):
         # gpg --export produces no status-fd output; stdout will be
         # empty in case of failure
         #stdout, stderr = p.communicate()
-        result = self.result_map['delete'](self) # any result will do
+        result = self.result_map['export'](self)
         self._collect_output(p, result, stdin=p.stdin)
         logger.debug('export_keys result: %r', result.data)
         return result.data.decode(self.encoding, self.decode_errors)
@@ -1076,7 +1136,7 @@ class GPG(object):
                 getattr(result, keyword)(L)
         return result
 
-    def list_keys(self, secret=False):
+    def list_keys(self, secret=False, keys=None):
         """ list the keys currently in the keyring
 
         >>> import shutil
@@ -1096,8 +1156,13 @@ class GPG(object):
         which='keys'
         if secret:
             which='secret-keys'
-        args = ["--list-%s" % which, "--fixed-list-mode", "--fingerprint",
-                "--with-colons"]
+        args = ['--list-%s' % which, '--fixed-list-mode',
+                '--fingerprint', '--fingerprint',   # get subkey FPs, too
+                '--with-colons']
+        if keys:
+            if isinstance(keys, string_types):
+                keys = [keys]
+            args.extend(keys)
         p = self._open_subprocess(args)
         return self._get_list_output(p, 'list')
 
@@ -1187,10 +1252,8 @@ class GPG(object):
         parms.setdefault('Key-Type','RSA')
         parms.setdefault('Key-Length',2048)
         parms.setdefault('Name-Real', "Autogenerated Key")
-        try:
-            logname = os.environ['LOGNAME']
-        except KeyError:
-            logname = os.environ['USERNAME']
+        logname = (os.environ.get('LOGNAME') or os.environ.get('USERNAME') or
+                   'unspecified')
         hostname = socket.gethostname()
         parms.setdefault('Name-Email', "%s@%s" % (logname.replace(' ', '_'),
                                                   hostname))
diff --git a/setup.py b/setup.py
index cc07c6e..936f1bb 100644
--- a/setup.py
+++ b/setup.py
@@ -31,6 +31,8 @@ It is intended for use with Python 2.4 or greater.",
         "Programming Language :: Python :: 3.2",
         "Programming Language :: Python :: 3.3",
         "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
         "Operating System :: OS Independent",
         "Topic :: Software Development :: Libraries :: Python Modules"
     ]
diff --git a/test_gnupg.py b/test_gnupg.py
index 2a33d08..f7133b6 100644
--- a/test_gnupg.py
+++ b/test_gnupg.py
@@ -17,7 +17,7 @@ import unittest
 import gnupg
 
 __author__ = "Vinay Sajip"
-__date__  = "$07-Dec-2014 18:46:40$"
+__date__  = "$24-Sep-2015 18:05:15$"
 
 ALL_TESTS = True
 
@@ -230,6 +230,13 @@ class GPGTestCase(unittest.TestCase):
         public_keys = self.gpg.list_keys()
         self.assertTrue(is_list_with_len(public_keys, 1),
                         "1-element list expected")
+        key_info = public_keys[0]
+        fp = key_info['fingerprint']
+        self.assertTrue(fp in public_keys.key_map)
+        self.assertTrue(public_keys.key_map[fp] is key_info)
+        for _, _, sfp in key_info['subkeys']:
+            self.assertTrue(sfp in public_keys.key_map)
+            self.assertTrue(public_keys.key_map[sfp] is key_info)
         private_keys = self.gpg.list_keys(secret=True)
         self.assertTrue(is_list_with_len(private_keys, 1),
                         "1-element list expected")
@@ -243,6 +250,39 @@ class GPGTestCase(unittest.TestCase):
         private_keys_2 = gpg.list_keys(secret=True)
         self.assertEqual(private_keys_2, private_keys)
 
+        # generate additional keys so that we can test listing a subset of
+        # keys
+        def get_names(key_map):
+            result = set()
+            for info in key_map.values():
+                for uid in info['uids']:
+                    uid = uid.replace(' (A test user (insecure!))', '')
+                    result.add(uid)
+            return result
+
+        result = self.generate_key("Charlie", "Clark", "gamma.com")
+        self.assertNotEqual(None, result, "Non-null result")
+        result = self.generate_key("Donna", "Davis", "delta.com")
+        self.assertNotEqual(None, result, "Non-null result")
+        public_keys = gpg.list_keys()
+        self.assertEqual(len(public_keys), 3)
+        actual = get_names(public_keys.key_map)
+        expected = set(['Barbara Brown <barbara.brown at beta.com>',
+                        'Charlie Clark <charlie.clark at gamma.com>',
+                        'Donna Davis <donna.davis at delta.com>'])
+        self.assertEqual(actual, expected)
+        # specify a single key as a string
+        public_keys = gpg.list_keys(keys='Donna Davis')
+        actual = get_names(public_keys.key_map)
+        expected = set(['Donna Davis <donna.davis at delta.com>'])
+        self.assertEqual(actual, expected)
+        # specify multiple keys
+        public_keys = gpg.list_keys(keys=['Donna', 'Barbara'])
+        actual = get_names(public_keys.key_map)
+        expected = set(['Barbara Brown <barbara.brown at beta.com>',
+                        'Donna Davis <donna.davis at delta.com>'])
+        self.assertEqual(actual, expected)
+
     def test_scan_keys(self):
         "Test that external key files can be scanned"
         expected = set([
@@ -515,6 +555,16 @@ class GPGTestCase(unittest.TestCase):
                 logger.debug("was: %r", data)
                 logger.debug("new: %r", ddata)
             self.assertEqual(data, ddata, "Round-trip must work")
+
+            # Try opening the encrypted file in text mode (Issue #39)
+            # this doesn't fail in 2.x
+            if gnupg._py3k:
+                efile = open(encfname, 'r')
+                ddata = self.gpg.decrypt_file(efile, passphrase="bbrown",
+                                              output=decfname)
+                self.assertFalse(ddata)
+                self.assertEqual(ddata.status, "no data was provided")
+                efile.close()
         finally:
             for fn in (encfname, decfname):
                 if os.name == 'posix' and mode is not None:
diff --git a/test_pubring.gpg b/test_pubring.gpg
new file mode 100644
index 0000000..e127ffb
Binary files /dev/null and b/test_pubring.gpg differ
diff --git a/test_secring.gpg b/test_secring.gpg
new file mode 100644
index 0000000..8ea3ec3
Binary files /dev/null and b/test_secring.gpg differ

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-gnupg.git



More information about the Python-modules-commits mailing list