Merge lp:~jelmer/bzr/prompt-unicode into lp:bzr

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5925
Proposed branch: lp:~jelmer/bzr/prompt-unicode
Merge into: lp:bzr
Diff against target: 244 lines (+37/-31)
10 files modified
bzrlib/builtins.py (+1/-1)
bzrlib/lockdir.py (+1/-1)
bzrlib/msgeditor.py (+1/-1)
bzrlib/tests/per_uifactory/__init__.py (+2/-3)
bzrlib/tests/test_script.py (+1/-1)
bzrlib/tests/test_ui.py (+19/-19)
bzrlib/transport/ssh.py (+2/-1)
bzrlib/ui/__init__.py (+3/-3)
bzrlib/ui/text.py (+3/-1)
doc/en/release-notes/bzr-2.4.txt (+4/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/prompt-unicode
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Review via email: mp+61123@code.launchpad.net

Commit message

Require prompts to always be unicode.

Description of the change

Require prompts to always be unicode, as they're going to be re-encoded
using the terminal encoding.

This should prevent problems with prompts that happen to contain non-ascii
characters and are being encoded using the terminal encoding.
(e.g. bug 592083)

We already had a test to make sure that UIFactory.prompt() did the right thing in case
it was passed a unicode prompt with non-ascii characters.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 05/16/2011 03:57 PM, Jelmer Vernooij wrote:
> Jelmer Vernooij has proposed merging lp:~jelmer/bzr/prompt-unicode into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> Bug #592083 in Bazaar: "UnicodeDecodeError in get_password if user name is unicode"
> https://bugs.launchpad.net/bzr/+bug/592083
>
> For more details, see:
> https://code.launchpad.net/~jelmer/bzr/prompt-unicode/+merge/61123
>
> Require prompts to always be unicode, as they're going to be re-encoded
> using the terminal encoding.
>
> This should prevent problems with prompts that happen to contain non-ascii
> characters and are being encoded using the terminal encoding.
> (e.g. bug 592083)
>
> We already had a test to make sure that UIFactory.prompt() did the right thing in case
> it was passed a unicode prompt with non-ascii characters.

This doesn't actually fix the bug, it just moves it. So I'm not really
sure what the benefit is. To *get* the error, we are reading an 8-bit
username and leaving it as an 8-bit string, rather than decoding it to
Unicode.

I don't think requiring u"" format strings actually helps. (Maybe it
gets us closer to python3 compatibility?) As we would have still passed

 u'Are you %(user)?' % ('\xe5\xb5',)

Which would have still failed with a Unicode error.

So I'm happy to fix the bug, but I don't think requiring Unicode objects
actually helps prevent bugs like this.

John
=:->
 review:needsinfo

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk3RL+IACgkQJdeBCYSNAAPrOQCfdgIdz3AVVtbXfWMXtcl9o8BA
gNMAn1qhhCan3T2CAM3JD7OpoNfzBCaK
=Jqc/
-----END PGP SIGNATURE-----

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 05/16/2011 03:57 PM, Jelmer Vernooij wrote:
> > Jelmer Vernooij has proposed merging lp:~jelmer/bzr/prompt-unicode into
> lp:bzr.
> >
> > Requested reviews:
> > bzr-core (bzr-core)
> > Related bugs:
> > Bug #592083 in Bazaar: "UnicodeDecodeError in get_password if user name is
> unicode"
> > https://bugs.launchpad.net/bzr/+bug/592083
> >
> > For more details, see:
> > https://code.launchpad.net/~jelmer/bzr/prompt-unicode/+merge/61123
> >
> > Require prompts to always be unicode, as they're going to be re-encoded
> > using the terminal encoding.
> >
> > This should prevent problems with prompts that happen to contain non-ascii
> > characters and are being encoded using the terminal encoding.
> > (e.g. bug 592083)
> >
> > We already had a test to make sure that UIFactory.prompt() did the right
> thing in case
> > it was passed a unicode prompt with non-ascii characters.
>
> This doesn't actually fix the bug, it just moves it. So I'm not really
> sure what the benefit is. To *get* the error, we are reading an 8-bit
> username and leaving it as an 8-bit string, rather than decoding it to
> Unicode.
in bzrlib/transport/ssh.py, this now does a decode(osutils._fs_enc) on filename, which is what was causing the problem for the user in the associated bug report.

I guess the unicode checks indeed don't really help if one of the format arguments can still be a regular string. Perhaps we should be checking for that in prompt() ?

Cheers,

Jelmer

Revision history for this message
Vincent Ladeuil (vila) wrote :

This looks like it's fixing the bug and is an improvement in any case, let's land it.

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/builtins.py'
2--- bzrlib/builtins.py 2011-05-03 13:53:46 +0000
3+++ bzrlib/builtins.py 2011-05-16 14:05:24 +0000
4@@ -4938,7 +4938,7 @@
5
6 if not force:
7 if not ui.ui_factory.confirm_action(
8- 'Uncommit these revisions',
9+ u'Uncommit these revisions',
10 'bzrlib.builtins.uncommit',
11 {}):
12 self.outf.write('Canceled\n')
13
14=== modified file 'bzrlib/lockdir.py'
15--- bzrlib/lockdir.py 2011-03-31 09:01:27 +0000
16+++ bzrlib/lockdir.py 2011-05-16 14:05:24 +0000
17@@ -355,7 +355,7 @@
18 holder_info = self.peek()
19 except LockCorrupt, e:
20 # The lock info is corrupt.
21- if bzrlib.ui.ui_factory.get_boolean("Break (corrupt %r)" % (self,)):
22+ if bzrlib.ui.ui_factory.get_boolean(u"Break (corrupt %r)" % (self,)):
23 self.force_break_corrupt(e.file_data)
24 return
25 if holder_info is not None:
26
27=== modified file 'bzrlib/msgeditor.py'
28--- bzrlib/msgeditor.py 2011-03-30 11:45:54 +0000
29+++ bzrlib/msgeditor.py 2011-05-16 14:05:24 +0000
30@@ -151,7 +151,7 @@
31 edited_content = msg_transport.get_bytes(basename)
32 if edited_content == reference_content:
33 if not ui.ui_factory.confirm_action(
34- "Commit message was not edited, use anyway",
35+ u"Commit message was not edited, use anyway",
36 "bzrlib.msgeditor.unchanged",
37 {}):
38 # Returning "" makes cmd_commit raise 'empty commit message
39
40=== modified file 'bzrlib/tests/per_uifactory/__init__.py'
41--- bzrlib/tests/per_uifactory/__init__.py 2011-01-10 22:20:12 +0000
42+++ bzrlib/tests/per_uifactory/__init__.py 2011-05-16 14:05:24 +0000
43@@ -66,9 +66,8 @@
44 # confirm_action should be answered by every ui factory; even
45 # noninteractive ones should have a reasonable default
46 self._load_responses([True])
47- result = self.factory.confirm_action(
48- 'Break a lock?',
49- 'bzr.lock.break.confirm',
50+ result = self.factory.confirm_action(u'Break a lock?',
51+ 'bzr.lock.break.confirm',
52 {})
53 # will be true either because we read it from the input or because
54 # that's the default
55
56=== modified file 'bzrlib/tests/test_script.py'
57--- bzrlib/tests/test_script.py 2011-05-13 12:51:05 +0000
58+++ bzrlib/tests/test_script.py 2011-05-16 14:05:24 +0000
59@@ -558,7 +558,7 @@
60
61 def run(self):
62 if ui.ui_factory.get_boolean(
63- 'Really do it',
64+ u'Really do it',
65 # 'bzrlib.tests.test_script.confirm',
66 # {}
67 ):
68
69=== modified file 'bzrlib/tests/test_ui.py'
70--- bzrlib/tests/test_ui.py 2011-04-05 17:37:53 +0000
71+++ bzrlib/tests/test_ui.py 2011-05-16 14:05:24 +0000
72@@ -63,7 +63,7 @@
73 def test_text_factory_confirm(self):
74 # turns into reading a regular boolean
75 ui = self.make_test_ui_factory('n\n')
76- self.assertEquals(ui.confirm_action('Should %(thing)s pass?',
77+ self.assertEquals(ui.confirm_action(u'Should %(thing)s pass?',
78 'bzrlib.tests.test_ui.confirmation',
79 {'thing': 'this'},),
80 False)
81@@ -119,12 +119,12 @@
82 stdout = tests.StringIOWrapper()
83 stderr = tests.StringIOWrapper()
84 factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
85- self.assertEqual(True, factory.get_boolean(""))
86- self.assertEqual(False, factory.get_boolean(""))
87- self.assertEqual(True, factory.get_boolean(""))
88- self.assertEqual(False, factory.get_boolean(""))
89- self.assertEqual(True, factory.get_boolean(""))
90- self.assertEqual(False, factory.get_boolean(""))
91+ self.assertEqual(True, factory.get_boolean(u""))
92+ self.assertEqual(False, factory.get_boolean(u""))
93+ self.assertEqual(True, factory.get_boolean(u""))
94+ self.assertEqual(False, factory.get_boolean(u""))
95+ self.assertEqual(True, factory.get_boolean(u""))
96+ self.assertEqual(False, factory.get_boolean(u""))
97 self.assertEqual("foo\n", factory.stdin.read())
98 # stdin should be empty
99 self.assertEqual('', factory.stdin.readline())
100@@ -137,15 +137,15 @@
101 stdout = tests.StringIOWrapper()
102 stderr = tests.StringIOWrapper()
103 factory = _mod_ui_text.TextUIFactory(stdin, stdout, stderr)
104- self.assertEqual(1, factory.get_integer(""))
105- self.assertEqual(-2, factory.get_integer(""))
106- self.assertEqual(42, factory.get_integer(""))
107+ self.assertEqual(1, factory.get_integer(u""))
108+ self.assertEqual(-2, factory.get_integer(u""))
109+ self.assertEqual(42, factory.get_integer(u""))
110
111 def test_text_factory_prompt(self):
112 # see <https://launchpad.net/bugs/365891>
113 StringIO = tests.StringIOWrapper
114 factory = _mod_ui_text.TextUIFactory(StringIO(), StringIO(), StringIO())
115- factory.prompt('foo %2e')
116+ factory.prompt(u'foo %2e')
117 self.assertEqual('', factory.stdout.getvalue())
118 self.assertEqual('foo %2e', factory.stderr.getvalue())
119
120@@ -166,7 +166,7 @@
121 self.apply_redirected(None, factory.stdout,
122 factory.stdout,
123 factory.get_boolean,
124- "what do you want"))
125+ u"what do you want"))
126 output = out.getvalue()
127 self.assertContainsRe(output,
128 "| foo *\r\r *\r*")
129@@ -195,10 +195,10 @@
130 factory.stdout.encoding = "utf8"
131 # there is no output from the base factory
132 self.assertEqual("someuser",
133- factory.get_username('Hello %(host)s', host='some'))
134+ factory.get_username(u'Hello %(host)s', host='some'))
135 self.assertEquals("Hello some: ", factory.stderr.getvalue())
136 self.assertEquals('', factory.stdout.getvalue())
137- self.assertEqual("", factory.get_username("Gebruiker"))
138+ self.assertEqual("", factory.get_username(u"Gebruiker"))
139 # stdin should be empty
140 self.assertEqual('', factory.stdin.readline())
141
142@@ -352,7 +352,7 @@
143 self.assertRaises(
144 NotImplementedError,
145 self.apply_redirected,
146- None, stdout, stdout, factory.get_boolean, "foo")
147+ None, stdout, stdout, factory.get_boolean, u"foo")
148
149
150 class TestUIFactoryTests(tests.TestCase):
151@@ -371,12 +371,12 @@
152
153 def test_canned_input_get_input(self):
154 uif = _mod_ui.CannedInputUIFactory([True, 'mbp', 'password', 42])
155- self.assertEqual(True, uif.get_boolean('Extra cheese?'))
156- self.assertEqual('mbp', uif.get_username('Enter your user name'))
157+ self.assertEqual(True, uif.get_boolean(u'Extra cheese?'))
158+ self.assertEqual('mbp', uif.get_username(u'Enter your user name'))
159 self.assertEqual('password',
160- uif.get_password('Password for %(host)s',
161+ uif.get_password(u'Password for %(host)s',
162 host='example.com'))
163- self.assertEqual(42, uif.get_integer('And all that jazz ?'))
164+ self.assertEqual(42, uif.get_integer(u'And all that jazz ?'))
165
166
167 class TestBoolFromString(tests.TestCase):
168
169=== modified file 'bzrlib/transport/ssh.py'
170--- bzrlib/transport/ssh.py 2011-01-11 20:20:13 +0000
171+++ bzrlib/transport/ssh.py 2011-05-16 14:05:24 +0000
172@@ -573,7 +573,8 @@
173 return True
174 except paramiko.PasswordRequiredException:
175 password = ui.ui_factory.get_password(
176- prompt='SSH %(filename)s password', filename=filename)
177+ prompt=u'SSH %(filename)s password',
178+ filename=filename.decode(osutils._fs_enc))
179 try:
180 key = pkey_class.from_private_key_file(filename, password)
181 paramiko_transport.auth_publickey(username, key)
182
183=== modified file 'bzrlib/ui/__init__.py'
184--- bzrlib/ui/__init__.py 2011-04-11 01:23:58 +0000
185+++ bzrlib/ui/__init__.py 2011-05-16 14:05:24 +0000
186@@ -204,10 +204,10 @@
187 """
188 return self.get_boolean(prompt % prompt_kwargs)
189
190- def get_password(self, prompt='', **kwargs):
191+ def get_password(self, prompt=u'', **kwargs):
192 """Prompt the user for a password.
193
194- :param prompt: The prompt to present the user
195+ :param prompt: The prompt to present the user (must be unicode)
196 :param kwargs: Arguments which will be expanded into the prompt.
197 This lets front ends display different things if
198 they so choose.
199@@ -483,7 +483,7 @@
200 def get_integer(self, prompt):
201 return self.responses.pop(0)
202
203- def get_password(self, prompt='', **kwargs):
204+ def get_password(self, prompt=u'', **kwargs):
205 return self.responses.pop(0)
206
207 def get_username(self, prompt, **kwargs):
208
209=== modified file 'bzrlib/ui/text.py'
210--- bzrlib/ui/text.py 2011-04-07 10:36:24 +0000
211+++ bzrlib/ui/text.py 2011-05-16 14:05:24 +0000
212@@ -114,7 +114,7 @@
213 password = password[:-1]
214 return password
215
216- def get_password(self, prompt='', **kwargs):
217+ def get_password(self, prompt=u'', **kwargs):
218 """Prompt the user for a password.
219
220 :param prompt: The prompt to present the user
221@@ -198,6 +198,8 @@
222 :param kwargs: Dictionary of arguments to insert into the prompt,
223 to allow UIs to reformat the prompt.
224 """
225+ if type(prompt) != unicode:
226+ raise ValueError("prompt %r not a unicode string" % prompt)
227 if kwargs:
228 # See <https://launchpad.net/bugs/365891>
229 prompt = prompt % kwargs
230
231=== modified file 'doc/en/release-notes/bzr-2.4.txt'
232--- doc/en/release-notes/bzr-2.4.txt 2011-05-16 11:17:40 +0000
233+++ doc/en/release-notes/bzr-2.4.txt 2011-05-16 14:05:24 +0000
234@@ -64,6 +64,10 @@
235 * Correct parent is now set when using 'switch -b' with bound branches.
236 (A. S. Budden, #513709)
237
238+* ``UIFactory.prompt``, ``UIFactory.get_username``,
239+ ``UIFactory.get_password`` and ``UIFactory.get_boolean`` now require a
240+ unicode prompt to be passed in. (Jelmer Vernooij, #592083)
241+
242 * ``WT.inventory`` and ``WT.iter_entries_by_dir()`` was not correctly
243 reporting subdirectories that were tree references (in formats that
244 supported them). (John Arbash Meinel, #764677)