2011-09-26 18 views
8

Por lo tanto, si yo recorrer y crear una colección de li/a etiquetas, me sale como se esperaba .. un conjunto de las siguientes etiquetas:content_tags anidados escape html interno ... ¿por qué?

(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>"] 

llamo unirse a ellos y consigo una cadena esperada .. .

(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>" 

Sin embargo, si el nido éste nivel más profundo en una etiqueta 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>" 

consigo escapé del centro de la html locura !!!

Al mirar el código fuente rieles:

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 

Es engañosamente parece que sólo puede hacer: content_tag (: li, nada, nada, falsa) y no tienen que escapar el contenido .. Sin embargo:

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>" 

todavía estoy sufriendo de síndrome de html_escape no deseada ...

Así que la única manera que conozco para evitar esto es hacer:

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>" 

Pero ... ¿Por qué sucede esto?

+0

¿Esto es solo un propósito o algún comportamiento lógico requiere que el servidor pueble este bloque? – Anatoly

Respuesta

15

Esto sucede porque en Rails 3 se introdujo la clase SafeBuffer que ajusta la clase String y anula el escape predeterminado que de otro modo se produciría al llamar a concat.

En su caso, la compañía ntent_tag (: li) está produciendo un SafeBuffer adecuado, pero Array # join no comprende SafeBuffers y simplemente emite un String. El content_tag (: ol) luego se llama con una cadena como su valor en lugar de un SafeBuffer y se escapa. Por lo tanto, no tiene mucho que ver con el anidamiento, como lo hace con join al devolver un String, no un SafeBuffer.

Llamar a html_safe en un String, pasar el String a raw, o pasar el conjunto a safe_join devolverá un SafeBuffer adecuado y evitará que el outer content_tag escape de él.

Ahora, en el caso de pasar falso al argumento de escape, esto no funciona cuando pasa un bloque a la etiqueta de contenido porque está llamando al capture(&block) ActionView :: Helpers :: CaptureHelper que se utiliza para extraer la plantilla, o en su caso, el valor de salida de join, que luego hace que llame al html_escape en la cadena antes de que llegue al método content_tag_string.

# 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 

Desde aquí el valor es el valor de retorno de unirse a, y se unen devuelve una cadena, que llama antes del código html_escape content_tag incluso llega a él con su propio escape.

Algunos enlaces de referencia para los interesados ​​

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

Editar

Otra forma de manejar esto es hacer un mapa/reducir en lugar de asignar/unir, ya que si reduce no se pasa un argumento usará el primer elemento y ejecutará la operación dada usando ese objeto, que en el caso del mapa content_tag llamará a la operación en un 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 

Como una sola línea

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

Añadir un poco de meta-especia a limpiar las cosas

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

¿Quién necesita html_safe ... esto es rubí!

+0

¡Buena respuesta! ¿Cómo llegaste a map_reduce? ¿Hiciste tu propio método o está construido en Rails (Ruby)? – Cristian

+0

Acabo de inventarlo, es parche parche en Enumerable, es simplemente realmente, def map_reduce (op, y block) self.map (y bloque) .reduce (op) end – Cluster

0

No es positivo, pero creo que el escape html ocurre en cada "capa" (por falta de un término mejor, cada iteración) - lo que quiero decir es en el nivel de bloque interno (1..5) .... y luego en el nivel externo bloque (content_tag (?:? ol) hacer ...

+0

No lo creo .. Sucede al llamar a "join" .. content_tag (: div) {content_tag (: ol)} salidas "

    " ... Donde content_tag (: div) {[content_tag (: ol) ] .join} salidas "
    <ol></ol>
    " – patrick

    4

    ¿Qué ocurre si se utiliza safe_join

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

    o simplemente utilizar prima

    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 
    
    Cuestiones relacionadas