嵌套的content_tags转义内部html ..为什么?

时间:2022-10-27 12:52:27

So, if I loop through and create a collection of li/a tags, I get as expected.. an array of these tags:

所以,如果我循环并创建一个li / a标签的集合,我会按预期得到..这些标签的数组:

(1..5).to_a.map do
  content_tag(:li) do
    link_to("boo", "www.boohoo.com")
  end
end

=> ["<li><a href=\"www.boohoo.com\">boo</a></li>", "<li><a href=\"www.boohoo.com\">boo</a></li>", "<li><a href=\"www.boohoo.com\">boo</a></li>", "<li><a href=\"www.boohoo.com\">boo</a></li>", "<li><a href=\"www.boohoo.com\">boo</a></li>"] 

I call join on them and I get an expected string...

我打电话给他们加入我得到一个预期的字符串......

(1..5).to_a.map do
  content_tag(:li) do
    link_to("boo", "www.boohoo.com")
  end
end.join

=> "<li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li>" 

However, if I nest this one level deeper in an ol tag...

但是,如果我将这一级别更深地嵌入ol标签中......

content_tag(:ol) do
  (1..5).to_a.map do
    content_tag(:li) { link_to("boo", "www.boohoo.com") } 
  end.join
end

 => "<ol>&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;</ol>"

I get escaped inner-html madness!!!

我逃脱了内心的疯狂!

When looking at the rails source code:

在查看rails源代码时:

  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
    if block_given?
      options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
      content_tag_string(name, capture(&block), options, escape)
    else
      content_tag_string(name, content_or_options_with_block, options, escape)
    end
  end

  private

    def content_tag_string(name, content, options, escape = true)
      tag_options = tag_options(options, escape) if options
      "<#{name}#{tag_options}>#{escape ? ERB::Util.h(content) : content}</#{name}>".html_safe
    end

It deceivingly looks like I can just do: content_tag(:li, nil, nil, false) and not have it escape the content.. However:

欺骗性地看起来我可以这样做:content_tag(:li,nil,nil,false)并没有让它逃避内容..但是:

content_tag(:ol, nil, nil, false) do
  (1..5).to_a.map do
    content_tag(:li, nil, nil, false) do 
      link_to("boo", "www.boohoo.com")
    end
  end.join
end
=> "<ol>&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;www.boohoo.com&quot;&gt;boo&lt;/a&gt;&lt;/li&gt;</ol>"

I still am suffering from unwanted html_escape syndrome...

我仍然患有不必要的html_escape综合症...

So the only way I know to avoid this is to do:

因此,我知道避免这种情况的唯一方法是:

content_tag(:ol) do
  (1..5).to_a.map do
    content_tag(:li) do 
      link_to("boo", "www.boohoo.com")
    end
  end.join.html_safe
end

=> "<ol><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li><li><a href=\"www.boohoo.com\">boo</a></li></ol>"

But.. Why does this happen?

但是..为什么会这样?

3 个解决方案

#1


15  

It happens because in Rails 3 the SafeBuffer class was introduced which wraps the String class and overrides the default escaping that would otherwise occur when concat is called.

之所以发生这种情况,是因为在Rails 3中引入了SafeBuffer类,它包装了String类并覆盖了在调用concat时可能发生的默认转义。

In your case, the content_tag(:li) is outputting a proper SafeBuffer, but Array#join doesn't understand SafeBuffers and simply outputs a String. The content_tag(:ol) is then be called with a String as it's value instead of a SafeBuffer and escapes it. So it doesn't so much have to do with nesting as it does to do with join returning a String not a SafeBuffer.

在您的情况下,content_tag(:li)正在输出正确的SafeBuffer,但是Array#join不能理解SafeBuffers并且只是输出一个String。然后使用String作为值而不是SafeBuffer调用content_tag(:ol)并将其转义。所以它与嵌套没有多大关系,因为它与连接返回String而不是SafeBuffer有关。

Calling html_safe on a String, passing the String to raw, or passing the array to safe_join will all return a proper SafeBuffer and prevent the outer content_tag from escaping it.

在String上调用html_safe,将String传递给raw,或者将数组传递给safe_join都将返回正确的SafeBuffer并阻止外部content_tag转义它。

Now in the case of passing false to the escape argument, this doesn't work when your passing a block to content tag because it is calling capture(&block) ActionView::Helpers::CaptureHelper which is used to pull in the template, or your case the output value of join, which then causes it to call html_escape on the string before it makes its way into the content_tag_string method.

现在在将false传递给escape参数的情况下,当您将块传递给内容标记时,这不起作用,因为它调用了捕获(&block)ActionView :: Helpers :: CaptureHelper,用于拉入模板,或者你的情况是join的输出值,然后它导致它在进入content_tag_string方法之前调用字符串上的html_escape。

  # action_view/helpers/tag_helper.rb
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
    if block_given?
      options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
      # capture(&block) escapes the string from join before being passed
      content_tag_string(name, capture(&block), options, escape)
    else
      content_tag_string(name, content_or_options_with_block, options, escape)
    end
  end

  # action_view/helpers/capture_helper.rb
  def capture(*args)
    value = nil
    buffer = with_output_buffer { value = yield(*args) }
    if string = buffer.presence || value and string.is_a?(String)
      ERB::Util.html_escape string
    end
  end

Since value here is the return value from join, and join returns a String, it calls html_escape before the content_tag code even gets to it with it's own escaping.

由于此处的值是join的返回值,而join返回一个String,因此在content_tag代码进入它之前调用html_escape并使用它自己的转义。

Some reference links for those interested

一些感兴趣的参考链接

https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_view/helpers/capture_helper.rb

https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_view/helpers/tag_helper.rb

http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/

http://railsdispatch.com/posts/security

Edit

Another way to handle this is to do a map/reduce instead of map/join since if reduce is not passed an argument it will use the first element and run the given operation using that object, which in the case of map content_tag will be calling the operation on a SafeBuffer.

处理此问题的另一种方法是执行map / reduce而不是map / join,因为如果reduce没有传递参数,它将使用第一个元素并使用该对象运行给定的操作,在map的情况下,content_tag将调用SafeBuffer上的操作。

content_tag(:ol) do
  (1..5).to_a.map do
    content_tag(:li) do
      link_to(...)
    end
  end.reduce(:<<)
  # Will concat using the SafeBuffer instead of String with join
end

As a one-liner

作为单线

content_tag(:ul) { collection.map {|item| content_tag(:li) { link_to(...) }}.reduce(:<<) }

Add a little meta-spice to clean things up

添加一点meta-spice来清理东西

ul_tag { collection.map_reduce(:<<) {|item| li_link_to(...) } }

Who needs html_safe... this is Ruby!

谁需要html_safe ......这是Ruby!

#2


5  

What happens if you use safe_join?

如果你使用safe_join会发生什么?

content_tag(:ol) do
  safe_join (1..5).to_a.map {
      content_tag(:li) { link_to("boo", "www.boohoo.com") } 
    }, ''
end

Or just use raw?

或者只是使用原始?

content_tag(ol) do
  1.upto(5) {
    raw content_tag(:li) { link_to 'boo', 'www.boohoo.com' }
    #  or maybe
    #    raw content_tag(:li) { raw link_to('boo', 'www.boohoo.com') } 
  }
end

#3


0  

Not positive, but I think that the html escaping happens at each "layer" (for lack of a better term; each iteration)― what I mean is at the inner block level (1..5)…. and then at the outer block level (content_tag(:ol) do ...

不是积极的,但我认为html转义发生在每个“层”(缺少更好的术语;每次迭代) - 我的意思是在内部块级别(1..5)....然后在外部块级别(content_tag(:ol)做...

#1


15  

It happens because in Rails 3 the SafeBuffer class was introduced which wraps the String class and overrides the default escaping that would otherwise occur when concat is called.

之所以发生这种情况,是因为在Rails 3中引入了SafeBuffer类,它包装了String类并覆盖了在调用concat时可能发生的默认转义。

In your case, the content_tag(:li) is outputting a proper SafeBuffer, but Array#join doesn't understand SafeBuffers and simply outputs a String. The content_tag(:ol) is then be called with a String as it's value instead of a SafeBuffer and escapes it. So it doesn't so much have to do with nesting as it does to do with join returning a String not a SafeBuffer.

在您的情况下,content_tag(:li)正在输出正确的SafeBuffer,但是Array#join不能理解SafeBuffers并且只是输出一个String。然后使用String作为值而不是SafeBuffer调用content_tag(:ol)并将其转义。所以它与嵌套没有多大关系,因为它与连接返回String而不是SafeBuffer有关。

Calling html_safe on a String, passing the String to raw, or passing the array to safe_join will all return a proper SafeBuffer and prevent the outer content_tag from escaping it.

在String上调用html_safe,将String传递给raw,或者将数组传递给safe_join都将返回正确的SafeBuffer并阻止外部content_tag转义它。

Now in the case of passing false to the escape argument, this doesn't work when your passing a block to content tag because it is calling capture(&block) ActionView::Helpers::CaptureHelper which is used to pull in the template, or your case the output value of join, which then causes it to call html_escape on the string before it makes its way into the content_tag_string method.

现在在将false传递给escape参数的情况下,当您将块传递给内容标记时,这不起作用,因为它调用了捕获(&block)ActionView :: Helpers :: CaptureHelper,用于拉入模板,或者你的情况是join的输出值,然后它导致它在进入content_tag_string方法之前调用字符串上的html_escape。

  # action_view/helpers/tag_helper.rb
  def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
    if block_given?
      options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
      # capture(&block) escapes the string from join before being passed
      content_tag_string(name, capture(&block), options, escape)
    else
      content_tag_string(name, content_or_options_with_block, options, escape)
    end
  end

  # action_view/helpers/capture_helper.rb
  def capture(*args)
    value = nil
    buffer = with_output_buffer { value = yield(*args) }
    if string = buffer.presence || value and string.is_a?(String)
      ERB::Util.html_escape string
    end
  end

Since value here is the return value from join, and join returns a String, it calls html_escape before the content_tag code even gets to it with it's own escaping.

由于此处的值是join的返回值,而join返回一个String,因此在content_tag代码进入它之前调用html_escape并使用它自己的转义。

Some reference links for those interested

一些感兴趣的参考链接

https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_view/helpers/capture_helper.rb

https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_view/helpers/tag_helper.rb

http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/

http://railsdispatch.com/posts/security

Edit

Another way to handle this is to do a map/reduce instead of map/join since if reduce is not passed an argument it will use the first element and run the given operation using that object, which in the case of map content_tag will be calling the operation on a SafeBuffer.

处理此问题的另一种方法是执行map / reduce而不是map / join,因为如果reduce没有传递参数,它将使用第一个元素并使用该对象运行给定的操作,在map的情况下,content_tag将调用SafeBuffer上的操作。

content_tag(:ol) do
  (1..5).to_a.map do
    content_tag(:li) do
      link_to(...)
    end
  end.reduce(:<<)
  # Will concat using the SafeBuffer instead of String with join
end

As a one-liner

作为单线

content_tag(:ul) { collection.map {|item| content_tag(:li) { link_to(...) }}.reduce(:<<) }

Add a little meta-spice to clean things up

添加一点meta-spice来清理东西

ul_tag { collection.map_reduce(:<<) {|item| li_link_to(...) } }

Who needs html_safe... this is Ruby!

谁需要html_safe ......这是Ruby!

#2


5  

What happens if you use safe_join?

如果你使用safe_join会发生什么?

content_tag(:ol) do
  safe_join (1..5).to_a.map {
      content_tag(:li) { link_to("boo", "www.boohoo.com") } 
    }, ''
end

Or just use raw?

或者只是使用原始?

content_tag(ol) do
  1.upto(5) {
    raw content_tag(:li) { link_to 'boo', 'www.boohoo.com' }
    #  or maybe
    #    raw content_tag(:li) { raw link_to('boo', 'www.boohoo.com') } 
  }
end

#3


0  

Not positive, but I think that the html escaping happens at each "layer" (for lack of a better term; each iteration)― what I mean is at the inner block level (1..5)…. and then at the outer block level (content_tag(:ol) do ...

不是积极的,但我认为html转义发生在每个“层”(缺少更好的术语;每次迭代) - 我的意思是在内部块级别(1..5)....然后在外部块级别(content_tag(:ol)做...