Deprecate implicitly coercing objects to string in ActiveSupport::SafeBuffer

This commit is contained in:
Jean Boussier 2021-05-02 11:58:54 +02:00
parent 2c8821abfd
commit 147f207a57
5 changed files with 65 additions and 12 deletions

View File

@ -2610,7 +2610,9 @@ module ActionView
else
options[:child_index] = nested_child_index(name)
end
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
if content = fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
output << content
end
end
output
elsif association

View File

@ -923,7 +923,7 @@ module ActionView
def form_tag_with_body(html_options, content)
output = form_tag_html(html_options)
output << content
output << content.to_s if content
output.safe_concat("</form>")
end

View File

@ -1,3 +1,13 @@
* Deprecate `ActiveSupport::SafeBuffer`'s incorrect implicit conversion of objects into string.
Except for a few methods like `String#%`, objects must implement `#to_str`
to be implictly converted to a String in string operations. In some
circumstances `ActiveSupport::SafeBuffer` was incorrectly calling the
explicit conversion method (`#to_s`) on them. This behavior is now
deprecated.
*Jean Boussier*
* Allow nested access to keys on `Rails.application.credentials`
Previously only top level keys in `credentials.yml.enc` could be accessed with method calls. Now any key can.

View File

@ -184,27 +184,27 @@ module ActiveSupport #:nodoc:
end
def concat(value)
super(html_escape_interpolated_argument(value))
super(implicit_html_escape_interpolated_argument(value))
end
alias << concat
def insert(index, value)
super(index, html_escape_interpolated_argument(value))
super(index, implicit_html_escape_interpolated_argument(value))
end
def prepend(value)
super(html_escape_interpolated_argument(value))
super(implicit_html_escape_interpolated_argument(value))
end
def replace(value)
super(html_escape_interpolated_argument(value))
super(implicit_html_escape_interpolated_argument(value))
end
def []=(*args)
if args.length == 3
super(args[0], args[1], html_escape_interpolated_argument(args[2]))
super(args[0], args[1], implicit_html_escape_interpolated_argument(args[2]))
else
super(args[0], html_escape_interpolated_argument(args[1]))
super(args[0], implicit_html_escape_interpolated_argument(args[1]))
end
end
@ -222,9 +222,9 @@ module ActiveSupport #:nodoc:
def %(args)
case args
when Hash
escaped_args = args.transform_values { |arg| html_escape_interpolated_argument(arg) }
escaped_args = args.transform_values { |arg| explicit_html_escape_interpolated_argument(arg) }
else
escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
escaped_args = Array(args).map { |arg| explicit_html_escape_interpolated_argument(arg) }
end
self.class.new(super(escaped_args))
@ -289,10 +289,33 @@ module ActiveSupport #:nodoc:
end
private
def html_escape_interpolated_argument(arg)
def explicit_html_escape_interpolated_argument(arg)
(!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
end
def implicit_html_escape_interpolated_argument(arg)
if !html_safe? || arg.html_safe?
arg
else
arg_string = begin
arg.to_str
rescue NoMethodError => error
if error.name == :to_str
str = arg.to_s
ActiveSupport::Deprecation.warn <<~MSG.squish
Implicit conversion of #{arg.class} into String by ActiveSupport::SafeBuffer
is deprecated and will be removed in Rails 7.1.
You must explicitly cast it to a String.
MSG
str
else
raise
end
end
CGI.escapeHTML(arg_string)
end
end
def set_block_back_references(block, match_data)
block.binding.eval("proc { |m| $~ = m }").call(match_data)
rescue ArgumentError

View File

@ -786,10 +786,15 @@ class OutputSafetyTest < ActiveSupport::TestCase
def setup
@string = +"hello"
@object = Class.new(Object) do
def to_s
def to_str
"other"
end
end.new
@to_s_object = Class.new(Object) do
def to_s
"to_s"
end
end.new
end
test "A string is unsafe by default" do
@ -817,6 +822,14 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_not_predicate @object, :html_safe?
end
test "Adding an object not responding to `#to_str` to a safe string is deprecated" do
string = @string.html_safe
assert_deprecated("Implicit conversion of #{@to_s_object.class} into String by ActiveSupport::SafeBuffer is deprecated") do
string << @to_s_object
end
assert_equal "helloto_s", string
end
test "Adding an object to a safe string returns a safe string" do
string = @string.html_safe
string << @object
@ -913,6 +926,11 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_not_predicate @other_string, :html_safe?
end
test "% method explicitly cast the argument to string" do
@other_string = "other%s"
assert_equal "otherto_s", @other_string % @to_s_object
end
test "Concatting unsafe onto safe with % yields escaped safe" do
@other_string = "other%s".html_safe
string = @other_string % "<foo>"