2011-09-26 8 views
8

Quindi, se io scorrere e creare una collezione di Li/a tag, ottengo come previsto .. una serie di questi tag:content_tags nested escape inner html .. perché?

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

chiamo unirsi su di loro e ottengo uno stringa di attesa .. .

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

Tuttavia, se il nido questo livello più profondo in un 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>" 

vengo scappato interno-html follia !!!

Se si guarda il codice sorgente di rotaie:

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 

Sembra ingannevolmente come io posso solo fare: content_tag (: Li, nil, nil, false) e non l'hanno sfuggire il contenuto .. Tuttavia:

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

lo sono ancora affetti da sindrome html_escape indesiderati ...

Quindi l'unico modo che conosco per evitare questo è quello di fare:

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

Ma .. Perché succede?

+0

Questo rappresenta solo lo scopo o un comportamento logico richiede il lato server per popolare questo blocco? – Anatoly

risposta

15

Succede perché in Rails 3 è stata introdotta la classe SafeBuffer che include la classe String e sostituisce l'escape di default che altrimenti si verifica quando viene chiamato concaten

Nel tuo caso, il co ntent_tag (: li) sta emettendo un SafeBuffer appropriato, ma l'unione # di array non capisce SafeBuffers e semplicemente emette una stringa. Il content_tag (: ol) viene quindi chiamato con una stringa come valore invece che come SafeBuffer e lo ignora. Quindi non ha molto a che fare con il nesting come fa con join che restituisce una stringa non un SafeBuffer.

Chiamare html_safe su una stringa, passare la stringa a raw o passare la matrice a safe_join restituirà tutti un SafeBuffer appropriato e impedirà a external content_tag di evaderlo.

Ora, nel caso di passaggio falso all'argomento di escape, questo non funziona quando si passa un blocco al tag del contenuto perché chiama capture(&block) ActionView :: Helpers :: CaptureHelper che viene utilizzato per inserire il modello, o nel tuo caso il valore di uscita di join, che quindi lo fa chiamare html_escape sulla stringa prima che entri nel metodo 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 

Dal valore qui è il valore restituito da unire, e unirsi restituisce una stringa, si chiama html_escape prima che il codice content_tag diventa ancora ad esso con la sua fuga.

Alcuni link di riferimento per chi è interessato

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

Modifica

Un altro modo per gestire questo è fare una mappa/ridurre invece di mappa/join poiché se non viene passato un argomento verrà usato il primo elemento ed eseguirà l'operazione data usando quell'oggetto, che nel caso della mappa content_tag chiamerà l'operazione su 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 

come un one-liner

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

Aggiungere un po 'di meta-spezie per pulire le cose

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

Chi ha bisogno di html_safe ... questo è Rubino!

+0

Bella risposta! Come sei arrivato a map_reduce? hai creato il tuo metodo o è stato creato in Rails (Ruby)? – Cristian

+0

L'ho appena inventato, è patch scimmia su Enumerable, è semplicemente davvero, def map_reduce (op, & block) self.map (& block) .reduce (op) end – Cluster

0

Non positivo, ma penso che l'escape HTML avvenga a ogni "livello" (per mancanza di un termine migliore, ogni iterazione) - ciò che intendo è a livello di blocco interno (1..5) .... e poi a livello esterno di blocco (content_tag (:?? ol) fare ...

+0

Io non la penso così .. Succede dal chiamare "join" .. content_tag (: div) {content_tag (: ol)} output "

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

    4

    Cosa succede se si utilizza safe_join

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

    o semplicemente usare crudo

    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