From bc6621a2cb7993cf01091b5c32bc89948c8eaa5f Mon Sep 17 00:00:00 2001 From: John Nishinaga Date: Wed, 23 Nov 2011 16:47:21 -0500 Subject: [PATCH] escape interpolations in translate helper when using keys ending with 'html' --- lib/rails_xss/action_view.rb | 44 +++++++++++++++++++++ test/translation_helper_test.rb | 70 +++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 test/translation_helper_test.rb diff --git a/lib/rails_xss/action_view.rb b/lib/rails_xss/action_view.rb index 95cb0d4..db22f2a 100644 --- a/lib/rails_xss/action_view.rb +++ b/lib/rails_xss/action_view.rb @@ -79,6 +79,50 @@ def link_to(*args, &block) end end end + + module TranslationHelper + HTML_SAFE_TRANSLATION_KEY_RE = /(\b|_|\.)html$/ + ESCAPE_INTERPOLATIONS_RESERVED_KEYS = I18n::Backend::Base::RESERVED_KEYS + [:locale, :raise, :cascade] + + # Replace translate to escape any interpolations when using keys ending + # with html. We don't use method chaining because it can't cover edge cases + # involving multiple keys. + # + # @see https://groups.google.com/group/rubyonrails-security/browse_thread/thread/2b61d70fb73c7cc5 + def translate(keys, options = {}) + if multiple_keys = keys.is_a?(Array) + ActiveSupport::Deprecation.warn "Giving an array to translate is deprecated, please give a symbol or a string instead", caller + end + + options[:raise] = true + keys = scope_keys_by_partial(keys) + html_safe_options = nil + + translations = keys.map do |key| + if key.to_s =~ HTML_SAFE_TRANSLATION_KEY_RE + unless html_safe_options + html_safe_options = options.dup + options.except(*ESCAPE_INTERPOLATIONS_RESERVED_KEYS).each do |name, value| + html_safe_options[name] = ERB::Util.html_escape(value.to_s) + end + end + I18n.translate(key, html_safe_options).html_safe + else + I18n.translate(key, options) + end + end + + if multiple_keys || translations.size > 1 + translations + else + translations.first + end + rescue I18n::MissingTranslationData => e + keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope]) + content_tag('span', keys.join(', '), :class => 'translation_missing') + end + alias :t :translate + end end end diff --git a/test/translation_helper_test.rb b/test/translation_helper_test.rb new file mode 100644 index 0000000..1c46d8e --- /dev/null +++ b/test/translation_helper_test.rb @@ -0,0 +1,70 @@ +require 'test_helper' + +class TranslationHelperTest < ActionView::TestCase + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TranslationHelper + + def setup + I18n.backend.store_translations(:en, + :translations => { + :hello => 'Hello World', + :html => 'Hello World', + :hello_html => 'Hello World', + :interpolated_text => 'Hello %{word}', + :interpolated_html => 'Hello %{word}', + } + ) + end + + def test_translate_hello + assert_equal 'Hello World', translate(:'translations.hello') + end + + def test_returns_missing_translation_message_wrapped_into_span + expected = 'en, translations, missing' + assert_equal expected, translate(:"translations.missing") + assert_equal true, translate(:"translations.missing").html_safe? + end + + def test_with_array_of_keys_returns_missing_translation_message + expected = 'en, translations, missing' + assert_deprecated(/Giving an array to translate is deprecated/) do + assert_equal expected, translate([:"translations.missing", :"translations.interpolated_text"]) + end + end + + def test_translate_does_not_mark_plain_text_as_safe_html + assert !translate(:'translations.hello').html_safe? + end + + def test_translate_marks_translations_named_html_as_safe_html + assert translate(:'translations.html').html_safe? + end + + def test_translate_marks_translations_with_a_html_suffix_as_safe_html + assert translate(:'translations.hello_html').html_safe? + end + + def test_translate_escapes_interpolations_in_translations_with_a_html_suffix + assert_equal 'Hello <World>', translate(:'translations.interpolated_html', :word => '') + string_stub = (Struct.new(:to_s)).new; string_stub.to_s = '' + assert_equal 'Hello <World>', translate(:'translations.interpolated_html', :word => string_stub) + end + + def test_t_escapes_interpolations_in_translations_with_a_html_suffix + assert_equal 'Hello <World>', t(:'translations.interpolated_html', :word => '') + end + + def test_translate_does_not_escape_interpolations_in_translations_without_a_html_suffix + assert_equal 'Hello ', translate(:'translations.interpolated_text', :word => '') + end + + def test_translate_escapes_interpolations_with_multiple_keys + assert_deprecated(/Giving an array to translate is deprecated/) do + assert_equal ['Hello <World>', 'Hello '], + translate([:'translations.interpolated_html', :'translations.interpolated_text'], :word => '') + end + end + +end