Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding detection for infinite embedding cycles. #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions gemfiles/Gemfile.bare
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
source 'https://rubygems.org'

gemspec
gemfiles/Gemfile.bare
41 changes: 1 addition & 40 deletions gemfiles/Gemfile.lock.bare
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/i18n-recursive-lookup/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module I18n
module RecursiveLookup
VERSION = "0.0.5"
VERSION = '0.0.6'
end
end
49 changes: 24 additions & 25 deletions lib/i18n/backend/recursive_lookup.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'active_support'
require 'active_support/core_ext'
require 'i18n/error/cyclic_reference_error'

module I18n
module Backend
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions lib/i18n/error/cyclic_reference_error.rb
Original file line number Diff line number Diff line change
@@ -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