html/templates - Replacing newlines with <br>
Asked Answered
H

5

10

I'm loading a text file that has newlines in it, and pass it to html/templates.

Substituting the \n with <br> in the loaded string, they are escaped by the template to html &lt;br&gt; and displayed in the browser, instead of causing a line return.

How can I change this behavior without switching to text/templates (which doesn't have XSS protection)?

Hydnocarpate answered 8/12, 2012 at 15:45 Comment(0)
Y
12

It seems you could run template.HTMLEscape() on your text first to sanitize it, then do the \n to
substitution that you trust, then use that as pre-escaped and trusted template data.

Update: Expanding on Kocka's example, this is what I had in mind:

package main

import (
    "html/template"
    "os"
    "strings"
)

const page = `<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <p>{{.}}</p>
  </body>
</html>`

const text = `first line
<script>dangerous</script>
last line`

func main() {
    t := template.Must(template.New("page").Parse(page))
    safe := template.HTMLEscapeString(text)
    safe = strings.Replace(safe, "\n", "<br>", -1)
    t.Execute(os.Stdout, template.HTML(safe)) // template.HTML encapsulates a known safe HTML document fragment.
}

http://play.golang.org/p/JiH0uD5Zh2

Output is

<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <p>first line<br>&lt;script&gt;dangerous&lt;/script&gt;<br>last line</p>
  </body>
</html>

And text rendered in the browser is

first line
<script>dangerous</script>
last line
Yawning answered 8/12, 2012 at 21:59 Comment(0)
P
4

Not sure where you're substituting \n for <br> but if it's in go, you can cast the string as template.HTML so it's not escaped.

See: http://golang.org/pkg/html/template/#HTML

If it's in a template, there should be a pipeline available, {{. | html}}

Pru answered 8/12, 2012 at 16:29 Comment(3)
I can't cast it to template.HTML because my string isn't safe. Could you elaborate the pileline trick? Thanks a lotHydnocarpate
if the string isn't safe then the pipeline isn't going to help either. Try splitting the string on "\n" and passing the resulting slice into the template. Use range to print the string and insert <br>. For example: Arr := strings.Split(myString, "\n") in the go code, and {{range Arr}}{{.}}<br>{{end}} in the template.Pru
@dskinner, I know it's late, but you should write yours out as a full response. It's a much cleaner solution.Tarazi
A
2

You can do it like this:

package main

import (
    "html/template"
    "os"
)

const page = `<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <p>{{.}}</p>
  </body>
</html>`

func main() {
    t := template.Must(template.New("page").Parse(page))
    t.Execute(os.Stdout, template.HTML("<br>"))
}

Try it out!

Addict answered 9/12, 2012 at 9:51 Comment(1)
This is actually the right track --- but saw, that the input is not save, which requires more processing, as correctly done in the accepted answer.Gustative
I
2

The accepted answer can easily be turned into a custom template function:

func replaceNewline(s string) template.HTML {
    return template.HTML(strings.Replace(template.HTMLEscapeString(s), "\n", "<br>", -1))
}

Add it with func (*Template) Funcs

Example

package main

import (
    "html/template"
    "os"
    "strings"
)

const page = `<!DOCTYPE html>
<html>
  <body>
    <p>{{. | replaceNewline}}</p>
  </body>
</html>`

const text = `first line
<script>dangerous</script>
last line`

func main() {
    t := template.Must(template.New("page").Funcs(template.FuncMap{
        "replaceNewline": func(s string) template.HTML {
            return template.HTML(strings.Replace(template.HTMLEscapeString(s), "\n", "<br>", -1))
        },
    }).Parse(page))
    t.Execute(os.Stdout, text)
}

Output

<!DOCTYPE html>
<html>
  <body>
    <p>first line<br>&lt;script&gt;dangerous&lt;/script&gt;<br>last line</p>
  </body>
</html>

Go Playground

Idomeneus answered 9/12, 2020 at 10:31 Comment(1)
Excellent answer!Condyloid
A
1

It is unnecessary to pass your entire template as an unsafe template (and this is bad practice).

You should pass a map to your template, and only explicitly 'unsafe' the elements you want to use as such, eg.

package main

import "bytes"
import "fmt"
import "html/template"
import "strings"

var input = `
  {{ define "LAYOUT" }}
    <html>
      <body>
        {{ template "CONTENT" . }}
      </body>
    </html>
  {{ end }}

  {{ define "CONTENT" }}
    Unsafe content: {{ .Unsafe }}
    Newlines converted to <br/> follow:
    {{ .Normal }}
  {{ end }}

  {{ template "LAYOUT" . }}
`

var other = `
  Hello
  World
  Again
`

var other2 = `
  <script>alert("Owned!");</script>
`

func main() {

    var t, err = template.New("sample").Parse(input)
    if err != nil {
        panic(err)
    }

    var fixed = strings.Replace(other, "\n", "\n<br/>", -1)
    var model = map[string]interface{}{
        "Normal": template.HTML(fixed),
        "Unsafe": other2,
    }

    var out bytes.Buffer
    t.Execute(&out, model) # <--- !! Notice the model is NOT an HTML type.

    var raw = out.String()
    fmt.Printf("%s", raw)
}

Yields:

Unsafe content:    &lt;script&gt;alert(&#34;Owned!&#34;);&lt;/script&gt;

Newlines converted to <br/> follow:
 <br/>  Hello 
 <br/>  World 
 <br/>  Again 
 <br/>

  </body>
</html>
Airiness answered 28/2, 2013 at 5:53 Comment(1)
While the idea is right, the implementation is wrong: marking a string as "safe" by wrapping it in template.HTML(...) disables all other escaping, so e.g. any single-line <script> elements in other would be passed through to the output verbatim. You need to add more escaping before marking fixed as safe, e.g. by using template.HTMLEscape() before substituting the newlines.Rattat

© 2022 - 2024 — McMap. All rights reserved.