Prevent escaping forward slashes in templates

2019-03-01 03:46发布

I'm working on converting a pet project of mine from Python to Go just to help me get a bit familiar with the language. An issue I am currently facing is that it's escaping my forward slashes. So it will receive a string like:

/location/to/something

and it then becomes

%2flocation%2fto%2fsomething

Now, it's only doing this when it's in a link (from what I've been reading this escaping is contextual) so this is what the line in the HTML template looks like:

<tr><td><a href="/file?file={{.FullFilePath}}">{{.FileName}}</a></td></tr>

If possible, how can I prevent this in either the template or the code itself?

This is what my templating function looks like (yes, I know it's hackish)

func renderTemplate(w http.ResponseWriter, tmpl string) {
    t, err := template.ParseFiles(templates_dir+"base.html", templates_dir+tmpl)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    if tmpl == "view.html" {
        err = t.Execute(w, FileList)
    } else {
        err = t.Execute(w, nil)
    }
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

2条回答
Evening l夕情丶
2楼-- · 2019-03-01 04:14

As the value of .FullFilePath, pass a value of type template.URL instead of string, which will tell the html/template package not to escape it.

For example:

func main() {
    t := template.Must(template.New("").Parse(templ))

    m := map[string]interface{}{
        "FileName":     "something.txt",
        "FileFullPath": template.URL("/location/to/something"),
    }

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const templ = `<tr><td><a href="/file?file={{.FileFullPath}}">{{.FileName}}</a></td></tr>`

Output (try it on the Go Playground):

<tr><td><a href="/file?file=/location/to/something">something.txt</a></td></tr>

Note that even though forward slashes / are allowed in URLs, the reason why the template package still encodes them is because it analyses the URL and sees that the value you want to include is the value of a URL parameter (file=XXX), and so it also encodes the slashes (so that everything you pass in will be part of the value of the file URL parameter).

If you plan to acquire this file path at the server side from URL parameters, then what the template package does is the correct and proper way.

But know that by doing this, you'll lose the safety that prevents code injection into URLs. If you're the one providing the values and you know they are safe, there is no problem. But if the data comes from a user input for example, never do this.

Also note that if you pass the whole URL (and not just a part of it), it will work without using template.URL (try this variant on the Go Playground):

func main() {
    t := template.Must(template.New("").Parse(templ))

    m := map[string]interface{}{
        "FileName": "something.txt",
        "FileURL":  "/file?file=/location/to/something",
    }

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const templ = `<tr><td><a href="{{.FileURL}}">{{.FileName}}</a></td></tr>`

Also note that the recommended way in my opinion would be to include the file path as part of the URL path and not as the value of a parameter, so instead you should create urls like this:

/file/location/to/something

Map your handler (which serves the file content, see this answer as an example) to the /file/ pattern, and when it is matched and your handler is called, cut off the /file/ prefix from the path r.URL.Path, and the rest will be the full file path. If you choose this, you also won't need the template.URL conversion (because the value you include is not a value of a URL parameter anymore):

func main() {
    t := template.Must(template.New("").Parse(templ))

    m := map[string]interface{}{
        "FileName":     "something.txt",
        "FileFullPath": "/location/to/something",
    }

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const templ = `<tr><td><a href="/file{{.FileFullPath}}">{{.FileName}}</a></td></tr>`

Try this on the Go Playground.

Also very important: never parse templates in your handler functions! For details see:

It takes too much time when using "template" package to generate a dynamic web page to client in golang

查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-03-01 04:16

OK, So the solution I've found (and please post if there's a better one) is based on an answer here.

I changed the struct I was using from:

type File struct {
    FullFilePath string
    FileName     string
}

To this:

type File struct {
    FullFilePath template.HTML
    FileName     string
}

And moved the html into the FullFilePath name, and then placed that in template.HTML so each FullFilePath name I was generating was done like so:

file := File{template.HTML("<a href=\"/file?file=" + path + "\"</a>"), f.Name()}

And my template file line was changed to this:

<tr><td>{{.FullFilePath}}{{.FileName}}</td></tr>
查看更多
登录 后发表回答