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

Patch for XSS fix #13

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
44 changes: 44 additions & 0 deletions lib/rails_xss/action_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
70 changes: 70 additions & 0 deletions test/translation_helper_test.rb
Original file line number Diff line number Diff line change
@@ -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 => '<a>Hello World</a>',
:html => '<a>Hello World</a>',
:hello_html => '<a>Hello World</a>',
:interpolated_text => 'Hello %{word}',
:interpolated_html => '<a>Hello %{word}</a>',
}
)
end

def test_translate_hello
assert_equal '<a>Hello World</a>', translate(:'translations.hello')
end

def test_returns_missing_translation_message_wrapped_into_span
expected = '<span class="translation_missing">en, translations, missing</span>'
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 = '<span class="translation_missing">en, translations, missing</span>'
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 '<a>Hello &lt;World&gt;</a>', translate(:'translations.interpolated_html', :word => '<World>')
string_stub = (Struct.new(:to_s)).new; string_stub.to_s = '<World>'
assert_equal '<a>Hello &lt;World&gt;</a>', translate(:'translations.interpolated_html', :word => string_stub)
end

def test_t_escapes_interpolations_in_translations_with_a_html_suffix
assert_equal '<a>Hello &lt;World&gt;</a>', t(:'translations.interpolated_html', :word => '<World>')
end

def test_translate_does_not_escape_interpolations_in_translations_without_a_html_suffix
assert_equal 'Hello <World>', translate(:'translations.interpolated_text', :word => '<World>')
end

def test_translate_escapes_interpolations_with_multiple_keys
assert_deprecated(/Giving an array to translate is deprecated/) do
assert_equal ['<a>Hello &lt;World&gt;</a>', 'Hello <World>'],
translate([:'translations.interpolated_html', :'translations.interpolated_text'], :word => '<World>')
end
end

end