safe_join 和 content_tag 方法替换 html_safe

safe_join and content_tag approach to replace html_safe

我正在使用 Rails' content_tag 助手构建一个 HTML 代码块。我现在面临的挑战是从一个数组中加入 HTML 个字符串,其中 HTML 个元素由 content_tag.

生成

RuboCop Rails/OutputSafety 参考。

例如:

options = ["<li>Three</li>", "<li>Four</li>", "<li>Five</li>"]

# This is code to generate blocks of HTML
out = []
out << content_tag(:ul,  
   content_tag(:li, "One") + 
   content_tag(:li, "Two") + 
   options.join(''),
:class => ["class_1", "class_2"])
safe_join(out)

# Expect result should be like
<ul class="class_1 class_2">
   <li>One</li>
   <li>Two</li>
   <li>Three</li>
   <li>Four</li>
   <li>Five</li>
</ul>

# Actual result
<ul class="class_1 class_2">
   <li>One</li>
   <li>Two</li>
   "<li>Three</li><li>Four</li><li>Five</li>"
</ul>

但是,如果我使用下面的 html_safe 方法,它将起作用。

%{<ul>
   <li>One</li>
   <li>Two</li>
   #{options.join('')}
 </ul>
}.html_safe

关于我应该更改的内容有什么建议吗?

# New apporach
options = ["Three", "Four", "Five"]
out = []
out << content_tag(:ul,  
   content_tag(:li, "One") + 
   content_tag(:li, "Two") + 
   options.collect do |option|
      content_tag(:li, "#{option[0]}")
   end.join(""),
:class => ["class_1", "class_2"])
safe_join(out)

# New approach result
<ul class="class_1 class_2">
   <li>One</li>
   <li>Two</li>
   "<li>Three</li><li>Four</li><li>Five</li>"
</ul>

问题是您将输出与来自 options 数组的不安全字符串连接起来。这是唯一应该使用 html_safe 方法以确保整个输出安全的地方:

out << content_tag(:ul,  
   content_tag(:li, "One") + 
   content_tag(:li, "Two") + 
   options.join('').html_safe,
:class => ["class_1", "class_2"])

编辑

首先 safe_join 方法不像 html_safe 方法那样工作,它不仅使连接的字符串 html_safe。如果连接的字符串不是 html_safe 以避免有害内容,它也会使 html 转义。

https://apidock.com/rails/ActionView/Helpers/OutputSafetyHelper/safe_join

在您的例子中,safe_join 方法根本没有对 out 数组中的字符串执行任何操作,因为它们已经是 html_safe。

result = content_tag(:ul,  
           content_tag(:li, "One") + 
           content_tag(:li, "Two") + 
           options.join(''),
           :class => ["class_1", "class_2"])

result.html_safe? # => true

问题的原因是您将安全字符串与不安全字符串连接在一起:

content_tag(:li, "Two") + options.join('')

content_tag(:li, "Two").html_safe? # => true
options.join('').html_safe?        # => false

在那一刻 options.join('') 被 html 逃脱了,因为它不安全。看例子:

# html tags in the second string are escaped, since it is not safe
"<li>One</li>".html_safe + "<li>Two</li>" # => "<li>One</li>&lt;li&gt;Two&lt;/li&gt;"

# nothing has been escaped, since everything is safe
"<li>One</li>".html_safe + "<li>Two</li>".html_safe # => "<li>One</li><li>Two</li>"

因此,为了获得预期结果,必须满足 2 个条件:

  1. safe_join 方法必须采用 html_safe 字符串数组。如果它们不是 html_safe,所有 html 标签都将被转义。
  2. 不要将安全字符串与不安全字符串连接起来,否则不安全字符串将被转义。

如您所见,您没有满足第二个条件。

关于新方法的建议

.join("") 方法使结果字符串不安全,即使数组包含安全字符串也是如此。使用 safe_join:

   content_tag(:li, "One") + 
   content_tag(:li, "Two") + 
   safe_join(
     options.collect do |option|
       content_tag(:li, option)
     end
   )