diff --git a/apptools/preferences/preference_binding.py b/apptools/preferences/preference_binding.py index 0bb68981..7d8f5761 100644 --- a/apptools/preferences/preference_binding.py +++ b/apptools/preferences/preference_binding.py @@ -9,6 +9,7 @@ # Thanks for using Enthought open source! """ A binding between a trait on an object and a preference value. """ +from ast import literal_eval # Enthought library imports. from traits.api import Any, HasTraits, Instance, Str, Undefined @@ -101,10 +102,11 @@ def _get_value(self, trait_name, value): if type(handler) is Str: pass - # Otherwise, we eval it! + # Otherwise, we literal_eval it! This is safe against arbitrary code + # execution, but it does limit values to core Python data types. else: try: - value = eval(value) + value = literal_eval(value) # If the eval fails then there is probably a syntax error, but # we will let the handler validation throw the exception. diff --git a/apptools/preferences/preferences_helper.py b/apptools/preferences/preferences_helper.py index cc9201c8..b5afe0bd 100644 --- a/apptools/preferences/preferences_helper.py +++ b/apptools/preferences/preferences_helper.py @@ -11,6 +11,7 @@ # Standard library imports. +from ast import literal_eval import logging # Enthought library imports. @@ -154,10 +155,11 @@ def _get_value(self, trait_name, value): if isinstance(handler, Str) or trait.is_str: pass - # Otherwise, we eval it! + # Otherwise, we literal_eval it! This is safe against arbitrary code + # execution, but it does limit values to core Python data types. else: try: - value = eval(value) + value = literal_eval(value) # If the eval fails then there is probably a syntax error, but # we will let the handler validation throw the exception. diff --git a/apptools/preferences/tests/example.ini b/apptools/preferences/tests/example.ini index 100dc6e1..4e9a8240 100644 --- a/apptools/preferences/tests/example.ini +++ b/apptools/preferences/tests/example.ini @@ -6,6 +6,7 @@ visible = True description = 'acme ui' offsets = "[1, 2, 3, 4]" names = "['joe', 'fred', 'jane']" +invalid = "sum(range(100))" [acme.ui.splash_screen] image = splash diff --git a/apptools/preferences/tests/test_preference_binding.py b/apptools/preferences/tests/test_preference_binding.py index 4720b05b..2b67d090 100644 --- a/apptools/preferences/tests/test_preference_binding.py +++ b/apptools/preferences/tests/test_preference_binding.py @@ -26,7 +26,7 @@ from apptools.preferences.api import Preferences from apptools.preferences.api import bind_preference from apptools.preferences.api import set_default_preferences -from traits.api import Bool, HasTraits, Int, Float, Str +from traits.api import Bool, HasTraits, Int, Float, Str, TraitError from traits.observation.api import match @@ -321,3 +321,20 @@ class AcmeUI(HasTraits): self.assertEqual("color", listener.trait_name) self.assertEqual("blue", listener.old) self.assertEqual("red", listener.new) + + def test_invalid_preference(self): + + p = self.preferences + p.load(self.example) + + class AcmeUI(HasTraits): + """ The Acme UI class! """ + + # The traits that we want to initialize from preferences. + invalid = Int + + acme_ui = AcmeUI() + + # Make a binding with an invalid value. + with self.assertRaises(TraitError): + bind_preference(acme_ui, "invalid", "acme.ui.invalid") diff --git a/apptools/preferences/tests/test_preferences_helper.py b/apptools/preferences/tests/test_preferences_helper.py index 207024b3..6ee616e3 100644 --- a/apptools/preferences/tests/test_preferences_helper.py +++ b/apptools/preferences/tests/test_preferences_helper.py @@ -27,7 +27,7 @@ from apptools.preferences.api import ScopedPreferences from apptools.preferences.api import set_default_preferences from traits.api import ( - Any, Bool, HasTraits, Int, Float, List, Str, + Any, Bool, HasTraits, Int, Float, List, Str, TraitError, push_exception_handler, pop_exception_handler, ) from traits.observation.api import match @@ -587,3 +587,18 @@ class AcmeUIPreferencesHelper(PreferencesHelper): helper = AcmeUIPreferencesHelper(preferences_path="acme.ui") self.assertEqual("50", helper.width) + + def test_invalid_preference(self): + + p = self.preferences + p.load(self.example) + + class AcmeUIPreferencesHelper(PreferencesHelper): + """ The Acme UI class! """ + + # The traits that we want to initialize from preferences. + invalid = Int + + # attempt to create instance from invalid value + with self.assertRaises(TraitError): + AcmeUIPreferencesHelper(preferences_path="acme.ui") diff --git a/docs/releases/upcoming/299.bugfix.rst b/docs/releases/upcoming/299.bugfix.rst new file mode 100644 index 00000000..d2b7229e --- /dev/null +++ b/docs/releases/upcoming/299.bugfix.rst @@ -0,0 +1 @@ +Replace eval with ast.literal_eval in apptools.preferences (#299) \ No newline at end of file