防止转义模板中的正斜杠

Prevent escaping forward slashes in templates

我正在努力将我的一个宠物项目从 Python 转换为 Go,只是为了帮助我稍微熟悉一下这门语言。我目前面临的一个问题是它正在逃避我的正斜杠。所以它会收到一个像这样的字符串:

/location/to/something

然后变成

%2flocation%2fto%2fsomething

现在,它仅在 link 中执行此操作(根据我一直在阅读的内容,此转义是上下文相关的)所以这就是 HTML 模板中的行:

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

如果可能,我怎样才能在模板或代码本身中防止这种情况发生?

这就是我的模板函数的样子(是的,我知道它很老套)

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)
    }
}

好的,所以我找到的解决方案(如果有更好的解决方案,请 post 解决)是基于 here.

的答案

我更改了我使用的结构:

type File struct {
    FullFilePath string
    FileName     string
}

为此:

type File struct {
    FullFilePath template.HTML
    FileName     string
}

并将 html 移动到 FullFilePath 名称中,然后将其放入 template.HTML 因此我生成的每个 FullFilePath 名称都是这样完成的:

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

我的模板文件行更改为:

<tr><td>{{.FullFilePath}}{{.FileName}}</td></tr>

作为.FullFilePath的值,传一个template.URL instead of string, which will tell the html/template包类型的值不转义。

例如:

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

输出(在 Go Playground 上尝试):

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

请注意,即使在 URL 中允许使用正斜杠 /template 包仍然对其进行编码的原因是因为它分析了 URL 和看到您要包含的值是 URL 参数 (file=XXX) 的值,因此它也对斜杠进行编码(这样您传入的所有内容都将成为 file URL 参数).

如果你打算在服务器端从URL参数获取这个文件路径,那么template包所做的是正确和正确的方法。

但是要知道,这样做会失去防止代码注入 URL 的安全性。如果您是提供这些值的人并且您知道它们是安全的,那么就没有问题。但是,如果数据来自用户输入,则永远不要这样做。

另请注意,如果您传递整个 URL(而不是其中的一部分),它会在不使用 template.URL 的情况下工作(在 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>`

另请注意,我认为推荐的方法是将文件路径作为 URL 路径的一部分,而不是作为参数的值,因此您应该像这样创建 url:

/file/location/to/something

将您的处理程序(提供文件内容,参见 作为示例)映射到 /file/ 模式,当它匹配并调用您的处理程序时,切断 /file/ 前缀来自路径 r.URL.Path,其余的将是完整的文件路径。如果你选择这个,你也不需要 template.URL 转换(因为你包含的值不再是 URL 参数的值):

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

Go Playground 上试试这个。

同样非常重要:永远不要在处理函数中解析模板!详情见:

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