summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Nemec <bnemec@redhat.com>2019-08-28 15:07:53 +0000
committerBen Nemec <bnemec@redhat.com>2019-08-28 15:46:03 +0000
commitf8fa059c46fed84514004ece7dccacf6380cf263 (patch)
tree14a9f8cd16c29966ab2d011f5def57343a6fe117
parent91b39bb60de37b061c7da032cf196014bd0d67b1 (diff)
downloadoslo-i18n-f8fa059c46fed84514004ece7dccacf6380cf263.tar.gz
Allow Message.translate to handle unhashable inputs
We made a mistake with the API of the Message class and shadowed the built-in function 'translate' from the unicode class that it inherits from. This means that our translate may be called with things like dicts and lists that are not hashable, which is problematic because we use that value as a dict key. Unhashable types are not allowed as dict keys so this fails. While ideally we would address this shadowing by renaming our version of translate, it is part of the public API of the library and thus we can't just change it arbitrarily. As a partial fix, this change adds a type check on the input parameter, and if it finds a type other than a string it calls the superclass's implementation of translate instead. This should handle most common uses of the stdlib translate, but since technically it is possible to use a string as input to that as well it doesn't fully address the problem. Change-Id: Ie68cba6af19d11de8968ee80ac10107f488acb92 Partial-Bug: 1841796
-rw-r--r--oslo_i18n/_message.py20
-rw-r--r--oslo_i18n/tests/test_message.py15
2 files changed, 35 insertions, 0 deletions
diff --git a/oslo_i18n/_message.py b/oslo_i18n/_message.py
index 0bd83c5..6ea94bc 100644
--- a/oslo_i18n/_message.py
+++ b/oslo_i18n/_message.py
@@ -78,6 +78,26 @@ class Message(six.text_type):
:returns: the translated message in unicode
"""
+ # We did a bad thing here. We shadowed the unicode built-in translate,
+ # which means there are circumstances where this function may be called
+ # with a desired_locale that is a non-string sequence or mapping type.
+ # This will not only result in incorrect behavior, it also fails
+ # because things like lists are not hashable, and we use the value in
+ # desired_locale as part of a dict key. If we see a non-string
+ # desired_locale, we know that the caller did not intend to call this
+ # form of translate and we should instead pass that along to the
+ # unicode implementation of translate.
+ #
+ # Unfortunately this doesn't entirely solve the problem as it would be
+ # possible for a caller to use a string as the mapping type and in that
+ # case we can't tell which version of translate they intended to call.
+ # That doesn't seem to be a common thing to do though. str.maketrans
+ # returns a dict, and that is probably the way most callers will create
+ # their mapping.
+ if (desired_locale is not None and
+ not isinstance(desired_locale, six.string_types)):
+ return super(Message, self).translate(desired_locale)
+
translated_message = Message._translate_msgid(self.msgid,
self.domain,
desired_locale,
diff --git a/oslo_i18n/tests/test_message.py b/oslo_i18n/tests/test_message.py
index 559e5d0..629238a 100644
--- a/oslo_i18n/tests/test_message.py
+++ b/oslo_i18n/tests/test_message.py
@@ -616,6 +616,21 @@ class MessageTestCase(test_base.BaseTestCase):
self.assertEqual(zh_translation, msg.translate('zh'))
self.assertEqual(fr_translation, msg.translate('fr'))
+ def test_translate_with_dict(self):
+ msg = _message.Message('abc')
+ # This dict is what you get back from str.maketrans('abc', 'xyz')
+ # We can't actually call that here because it doesn't exist on py2
+ # and the string.maketrans that does behaves differently.
+ self.assertEqual('xyz', msg.translate({97: 120, 98: 121, 99: 122}))
+
+ def test_translate_with_list(self):
+ msg = _message.Message('abc')
+ table = [six.unichr(x) for x in range(128)]
+ table[ord('a')] = 'b'
+ table[ord('b')] = 'c'
+ table[ord('c')] = 'd'
+ self.assertEqual('bcd', msg.translate(table))
+
class TranslateMsgidTest(test_base.BaseTestCase):