Merge lp:~mterry/duplicity/gpg-encode into lp:duplicity/0.6

Proposed by Michael Terry
Status: Merged
Merged at revision: 961
Proposed branch: lp:~mterry/duplicity/gpg-encode
Merge into: lp:duplicity/0.6
Diff against target: 215 lines (+66/-22)
5 files modified
bin/duplicity (+12/-8)
bin/rdiffdir (+0/-3)
testing/overrides/gettext.py (+34/-0)
testing/run-tests (+2/-2)
testing/tests/finaltest.py (+18/-9)
To merge this branch: bzr merge lp:~mterry/duplicity/gpg-encode
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+204808@code.launchpad.net

Description of the change

getpass.getpass(prompt) eventually calls str(prompt). Which is a no go, if the prompt contains unicode. Here's a patch to always pass getpass() a byte string.

Our tests didn't catch this because they always set PASSPHRASE. I've added a test that passes the passphrase via stdin.

In order to actually test the bug condition (_() returning unicode strings that can't convert to ascii), I added a gettext.py override that always adds an invisible unicode character to returned strings when testing. This way, we should be able to catch future ascii translation problems in any of the code under test.

Oh, and while I was at it, I removed the useless gettext.install() lines in bin/duplicity and bin/rdiffdir (the gettext.install() call in duplicity/__init__.py is what matters).

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

Er, I'll just add one with this branch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/duplicity'
--- bin/duplicity 2014-01-21 21:04:27 +0000
+++ bin/duplicity 2014-02-05 02:57:13 +0000
@@ -27,7 +27,7 @@
27# Please send mail to me or the mailing list if you find bugs or have27# Please send mail to me or the mailing list if you find bugs or have
28# any suggestions.28# any suggestions.
2929
30import getpass, gzip, os, sys, time, types30import gzip, os, sys, time, types
31import traceback, platform, statvfs, resource, re31import traceback, platform, statvfs, resource, re
32import threading32import threading
33from datetime import datetime33from datetime import datetime
@@ -37,9 +37,6 @@
37if os.path.exists(os.path.join(pwd, "../duplicity")):37if os.path.exists(os.path.join(pwd, "../duplicity")):
38 sys.path.insert(0, os.path.abspath(os.path.join(pwd, "../.")))38 sys.path.insert(0, os.path.abspath(os.path.join(pwd, "../.")))
3939
40import gettext
41gettext.install('duplicity', codeset='utf8')
42
43from duplicity import log40from duplicity import log
44log.setup()41log.setup()
4542
@@ -65,6 +62,13 @@
65# If exit_val is not None, exit with given value at end.62# If exit_val is not None, exit with given value at end.
66exit_val = None63exit_val = None
6764
65def getpass_safe(message):
66 # getpass() in Python 2.x will call str() on our prompt. So we can't pass
67 # in non-ascii characters.
68 import getpass, locale
69 message = message.encode(locale.getpreferredencoding(), 'replace')
70 return getpass.getpass(message)
71
68def get_passphrase(n, action, for_signing = False):72def get_passphrase(n, action, for_signing = False):
69 """73 """
70 Check to make sure passphrase is indeed needed, then get74 Check to make sure passphrase is indeed needed, then get
@@ -160,19 +164,19 @@
160 if use_cache and globals.gpg_profile.signing_passphrase:164 if use_cache and globals.gpg_profile.signing_passphrase:
161 pass1 = globals.gpg_profile.signing_passphrase165 pass1 = globals.gpg_profile.signing_passphrase
162 else:166 else:
163 pass1 = getpass.getpass(_("GnuPG passphrase for signing key:")+" ")167 pass1 = getpass_safe(_("GnuPG passphrase for signing key:")+" ")
164 else:168 else:
165 if use_cache and globals.gpg_profile.passphrase:169 if use_cache and globals.gpg_profile.passphrase:
166 pass1 = globals.gpg_profile.passphrase170 pass1 = globals.gpg_profile.passphrase
167 else:171 else:
168 pass1 = getpass.getpass(_("GnuPG passphrase:")+" ")172 pass1 = getpass_safe(_("GnuPG passphrase:")+" ")
169173
170 if n == 1:174 if n == 1:
171 pass2 = pass1175 pass2 = pass1
172 elif for_signing:176 elif for_signing:
173 pass2 = getpass.getpass(_("Retype passphrase for signing key to confirm: "))177 pass2 = getpass_safe(_("Retype passphrase for signing key to confirm: "))
174 else:178 else:
175 pass2 = getpass.getpass(_("Retype passphrase to confirm: "))179 pass2 = getpass_safe(_("Retype passphrase to confirm: "))
176180
177 if not pass1 == pass2:181 if not pass1 == pass2:
178 print _("First and second passphrases do not match! Please try again.")182 print _("First and second passphrases do not match! Please try again.")
179183
=== modified file 'bin/rdiffdir'
--- bin/rdiffdir 2013-12-27 06:39:00 +0000
+++ bin/rdiffdir 2014-02-05 02:57:13 +0000
@@ -27,9 +27,6 @@
2727
28import sys, getopt, gzip, os28import sys, getopt, gzip, os
2929
30import gettext
31gettext.install('duplicity', codeset='utf8')
32
33from duplicity import diffdir30from duplicity import diffdir
34from duplicity import patchdir31from duplicity import patchdir
35from duplicity import log32from duplicity import log
3633
=== added directory 'testing/overrides'
=== added file 'testing/overrides/gettext.py'
--- testing/overrides/gettext.py 1970-01-01 00:00:00 +0000
+++ testing/overrides/gettext.py 2014-02-05 02:57:13 +0000
@@ -0,0 +1,34 @@
1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf8 -*-
2#
3# Copyright 2014 Michael Terry <mike@mterry.name>
4#
5# This file is part of duplicity.
6#
7# Duplicity is free software; you can redistribute it and/or modify it
8# under the terms of the GNU General Public License as published by the
9# Free Software Foundation; either version 2 of the License, or (at your
10# option) any later version.
11#
12# Duplicity is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with duplicity; if not, write to the Free Software Foundation,
19# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21# This is just a small override to the system gettext.py which allows us to
22# always return a string with fancy unicode characters, which will notify us
23# if we ever get a unicode->ascii translation by accident.
24
25def translation(*args, **kwargs):
26 class Translation:
27 ZWSP = u"​" # ZERO WIDTH SPACE, basically an invisible space separator
28 def install(self, **kwargs):
29 import __builtin__
30 __builtin__.__dict__['_'] = lambda x: x + self.ZWSP
31 def ungettext(self, one, more, n):
32 if n == 1: return one + self.ZWSP
33 else: return more + self.ZWSP
34 return Translation()
035
=== modified file 'testing/run-tests'
--- testing/run-tests 2011-11-24 01:49:53 +0000
+++ testing/run-tests 2014-02-05 02:57:13 +0000
@@ -25,9 +25,9 @@
2525
26THISDIR=$(pwd)26THISDIR=$(pwd)
27export TZ=US/Central27export TZ=US/Central
28export LANG=28export LANG=en_US.UTF-8
29# up for 'duplicity' module and here for 'helper.py'29# up for 'duplicity' module and here for 'helper.py'
30export PYTHONPATH="$(dirname $THISDIR):$THISDIR/helpers"30export PYTHONPATH="$THISDIR/overrides:$(dirname $THISDIR):$THISDIR/helpers"
31export GNUPGHOME="$THISDIR/gnupg"31export GNUPGHOME="$THISDIR/gnupg"
32export PATH="$(dirname $THISDIR)/bin:$PATH"32export PATH="$(dirname $THISDIR)/bin:$PATH"
3333
3434
=== modified file 'testing/tests/finaltest.py'
--- testing/tests/finaltest.py 2012-11-24 19:45:09 +0000
+++ testing/tests/finaltest.py 2014-02-05 02:57:13 +0000
@@ -20,6 +20,7 @@
20# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA20# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2121
22import helper22import helper
23import pexpect
23import sys, os, unittest24import sys, os, unittest
2425
25import duplicity.backend26import duplicity.backend
@@ -50,7 +51,8 @@
50 """51 """
51 Test backup/restore using duplicity binary52 Test backup/restore using duplicity binary
52 """53 """
53 def run_duplicity(self, arglist, options = [], current_time = None):54 def run_duplicity(self, arglist, options = [], current_time = None,
55 passphrase_input = None):
54 """Run duplicity binary with given arguments and options"""56 """Run duplicity binary with given arguments and options"""
55 options.append("--archive-dir testfiles/cache")57 options.append("--archive-dir testfiles/cache")
56 cmd_list = ["duplicity"]58 cmd_list = ["duplicity"]
@@ -62,22 +64,23 @@
62 cmd_list.extend(arglist)64 cmd_list.extend(arglist)
63 cmdline = " ".join(cmd_list)65 cmdline = " ".join(cmd_list)
64 #print "Running '%s'." % cmdline66 #print "Running '%s'." % cmdline
65 if not os.environ.has_key('PASSPHRASE'):67 if passphrase_input is None and not os.environ.has_key('PASSPHRASE'):
66 os.environ['PASSPHRASE'] = 'foobar'68 os.environ['PASSPHRASE'] = 'foobar'
67 return_val = os.system(cmdline)69 (output, return_val) = pexpect.run(cmdline, withexitstatus=True,
70 events={'passphrase.*:': passphrase_input})
68 if return_val:71 if return_val:
69 raise CmdError(return_val)72 raise CmdError(return_val)
7073
71 def backup(self, type, input_dir, options = [], current_time = None):74 def backup(self, type, input_dir, options = [], **kwargs):
72 """Run duplicity backup to default directory"""75 """Run duplicity backup to default directory"""
73 options = options[:]76 options = options[:]
74 if type == "full":77 if type == "full":
75 options.insert(0, 'full')78 options.insert(0, 'full')
76 args = [input_dir, "'%s'" % backend_url]79 args = [input_dir, "'%s'" % backend_url]
77 self.run_duplicity(args, options, current_time)80 self.run_duplicity(args, options, **kwargs)
7881
79 def restore(self, file_to_restore = None, time = None, options = [],82 def restore(self, file_to_restore = None, time = None, options = [],
80 current_time = None):83 **kwargs):
81 options = options[:] # just nip any mutability problems in bud84 options = options[:] # just nip any mutability problems in bud
82 assert not os.system("rm -rf testfiles/restore_out")85 assert not os.system("rm -rf testfiles/restore_out")
83 args = ["'%s'" % backend_url, "testfiles/restore_out"]86 args = ["'%s'" % backend_url, "testfiles/restore_out"]
@@ -85,17 +88,17 @@
85 options.extend(['--file-to-restore', file_to_restore])88 options.extend(['--file-to-restore', file_to_restore])
86 if time:89 if time:
87 options.extend(['--restore-time', str(time)])90 options.extend(['--restore-time', str(time)])
88 self.run_duplicity(args, options, current_time)91 self.run_duplicity(args, options, **kwargs)
8992
90 def verify(self, dirname, file_to_verify = None, time = None, options = [],93 def verify(self, dirname, file_to_verify = None, time = None, options = [],
91 current_time = None):94 **kwargs):
92 options = ["verify"] + options[:]95 options = ["verify"] + options[:]
93 args = ["'%s'" % backend_url, dirname]96 args = ["'%s'" % backend_url, dirname]
94 if file_to_verify:97 if file_to_verify:
95 options.extend(['--file-to-restore', file_to_verify])98 options.extend(['--file-to-restore', file_to_verify])
96 if time:99 if time:
97 options.extend(['--restore-time', str(time)])100 options.extend(['--restore-time', str(time)])
98 self.run_duplicity(args, options, current_time)101 self.run_duplicity(args, options, **kwargs)
99102
100 def deltmp(self):103 def deltmp(self):
101 """Delete temporary directories"""104 """Delete temporary directories"""
@@ -255,6 +258,12 @@
255 assert chain.start_time == 30000, chain.start_time258 assert chain.start_time == 30000, chain.start_time
256 assert chain.end_time == 40000, chain.end_time259 assert chain.end_time == 40000, chain.end_time
257260
261 def test_piped_password(self):
262 """Make sure that prompting for a password works"""
263 self.backup("full", "testfiles/empty_dir",
264 passphrase_input="foobar\nfoobar\n")
265 self.restore(passphrase_input="foobar\n")
266
258class FinalTest1(FinalTest, unittest.TestCase):267class FinalTest1(FinalTest, unittest.TestCase):
259 def setUp(self):268 def setUp(self):
260 assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")269 assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")

Subscribers

People subscribed via source and target branches

to all changes: