From 8efb49b218df77e0dcedf3789dcaac46ca09d02f Mon Sep 17 00:00:00 2001 From: Sergey Mazur Date: Thu, 12 Oct 2017 13:39:59 -0600 Subject: [PATCH] Adding detection for infinite embedding cycles. If it happens then CyclicReferenceError is raised. --- gemfiles/Gemfile.bare | 4 +- gemfiles/Gemfile.lock.bare | 41 +------------------- lib/i18n-recursive-lookup/version.rb | 2 +- lib/i18n/backend/recursive_lookup.rb | 49 ++++++++++++------------ lib/i18n/error/cyclic_reference_error.rb | 10 +++++ 5 files changed, 37 insertions(+), 69 deletions(-) create mode 100644 lib/i18n/error/cyclic_reference_error.rb diff --git a/gemfiles/Gemfile.bare b/gemfiles/Gemfile.bare index fa75df1..34b8383 100644 --- a/gemfiles/Gemfile.bare +++ b/gemfiles/Gemfile.bare @@ -1,3 +1 @@ -source 'https://rubygems.org' - -gemspec +gemfiles/Gemfile.bare \ No newline at end of file diff --git a/gemfiles/Gemfile.lock.bare b/gemfiles/Gemfile.lock.bare index c330299..3b93ef3 100644 --- a/gemfiles/Gemfile.lock.bare +++ b/gemfiles/Gemfile.lock.bare @@ -1,40 +1 @@ -PATH - remote: . - specs: - i18n-recursive-lookup (0.0.5) - activesupport - i18n - -GEM - remote: https://rubygems.org/ - specs: - activesupport (4.2.1) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - i18n (0.7.0) - json (1.8.2) - metaclass (0.0.1) - minitest (5.6.1) - mocha (0.13.2) - metaclass (~> 0.0.1) - power_assert (0.2.3) - rake (10.0.4) - test-unit (3.0.9) - power_assert - test_declarative (0.0.5) - thread_safe (0.3.5) - tzinfo (1.2.2) - thread_safe (~> 0.1) - -PLATFORMS - ruby - -DEPENDENCIES - i18n-recursive-lookup! - mocha - rake - test-unit - test_declarative +gemfiles/Gemfile.lock.bare \ No newline at end of file diff --git a/lib/i18n-recursive-lookup/version.rb b/lib/i18n-recursive-lookup/version.rb index 7e08f8e..0da0b87 100644 --- a/lib/i18n-recursive-lookup/version.rb +++ b/lib/i18n-recursive-lookup/version.rb @@ -1,5 +1,5 @@ module I18n module RecursiveLookup - VERSION = "0.0.5" + VERSION = '0.0.6' end end diff --git a/lib/i18n/backend/recursive_lookup.rb b/lib/i18n/backend/recursive_lookup.rb index f66b52e..b1c1490 100644 --- a/lib/i18n/backend/recursive_lookup.rb +++ b/lib/i18n/backend/recursive_lookup.rb @@ -1,5 +1,6 @@ require 'active_support' require 'active_support/core_ext' +require 'i18n/error/cyclic_reference_error' module I18n module Backend @@ -14,27 +15,9 @@ module RecursiveLookup def lookup(locale, key, scope = [], options = {}) result = super - unless result - normalized_key = I18n.normalize_keys(nil, key, scope) - return if normalized_key.size < 2 - - # simply strip the last key part, e.g. - # `foo.bar.baz` becomes `foo.bar` - normalized_key.slice!(-1, 1) - - # look the stripped key up - if this is a reference, it will - # get compiled and cached - lookup(locale, normalized_key, [], options) - - # then try again to get our result - it will be found now if the - # previous `lookup` call compiled a new reference with our key, - # otherwise still stay nil - result = super - end - return result unless (result.is_a?(String) or result.is_a?(Hash)) - compiled_result, had_to_compile_result = deep_compile(locale, result, options) + compiled_result, had_to_compile_result = deep_compile(locale, result, options, key) if had_to_compile_result cache_compiled_result(locale, key, compiled_result, scope, options) @@ -44,17 +27,21 @@ def lookup(locale, key, scope = [], options = {}) end #subject is hash or string - def deep_compile(locale, subject, options) + #initial_key is key needs translate. Need to throw to handle_interpolation_match() method + # for detection cyclic references + def deep_compile(locale, subject, options, initial_key) if subject.is_a?(Hash) subject.each do |key, object| - subject[key], _had_to_compile_result = deep_compile(locale, object, options) + subject[key], _had_to_compile_result = deep_compile(locale, object, options, initial_key) end else - compile(locale, subject, options) + compile(locale, subject, options, initial_key) end end - def compile(locale, string, options) + #initial_key is key needs translate. Need to throw to handle_interpolation_match() method + # for detection cyclic references + def compile(locale, string, options, initial_key) had_to_compile_result = false if string.is_a?(String) @@ -63,7 +50,7 @@ def compile(locale, string, options) if embedded_token had_to_compile_result = true - handle_interpolation_match(locale, embedded_token, options) + handle_interpolation_match(locale, embedded_token, options, string, initial_key) else token end @@ -93,8 +80,20 @@ def cache_compiled_result(locale, dot_form_key, compiled_result, scope, options) store_translations(locale, translation_hash, options) end - def handle_interpolation_match(locale, embedded_token, options) + #initial_key is the initial key for recursive lookup the nested keys + #translated_string is the string from yaml file for +initial_key+ + # + def handle_interpolation_match(locale, embedded_token, options, translated_string, initial_key) + + @keys_chain = {} unless options[:fallback_in_progress] == true + @keys_chain ||= {} + + escaped, pattern, key = embedded_token.values_at(1, 2, 3) + @keys_chain[initial_key] = translated_string + if @keys_chain.has_key? key + raise I18n::CyclicReferenceError.new(@keys_chain) + end escaped ? pattern : I18n.translate(key, locale: locale) end end diff --git a/lib/i18n/error/cyclic_reference_error.rb b/lib/i18n/error/cyclic_reference_error.rb new file mode 100644 index 0000000..cb0e27a --- /dev/null +++ b/lib/i18n/error/cyclic_reference_error.rb @@ -0,0 +1,10 @@ +module I18n + class CyclicReferenceError < RuntimeError + attr_reader :keys_chain + + def initialize(keys_chain, msg = 'Cyclic Reference has been detected') + @keys_chain = keys_chain + super "#{msg} in chain #{@keys_chain}" + end + end +end