TL;DR:给定任意文件名作为Gostring
值,创建指定该文件名的Content-Disposition
头字段的最佳方法是什么?
我正在编写一个Go-net/http处理程序,我想设置Content-Disposition
头字段来指定浏览器在保存文件时应该使用的文件名。根据MDN,语法为:
Content-Disposition: attachment; filename="filename.jpg"
和CCD_ 4;带引号的字符串";。然而,我没有看到任何提及";引用";在net/http文档中。只提到HTML和URL转义。
带引号的字符串是否与URL转义相同或至少兼容?我可以只使用url吗。QueryEscape或url。PathEscape?如果是,我应该使用哪一个,或者两者都安全吗?HTTP引用的字符串看起来类似于URL转义,但我无法立即找到任何信息来说明它们是否兼容,或者是否存在需要担心的边缘情况。
或者,我是否应该使用一个更高级别的包来处理构建包含这样的参数的HTTP头字段值的细节?
HTTP引号字符串在RFC 7230:中定义
quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
obs-text = %x80-FF
quoted-pair = "" ( HTAB / SP / VCHAR / obs-text )
其中VCHAR是任何可见的ASCII字符。
根据RFC,以下函数引用:
// quotedString returns s quoted per quoted-string in RFC 7230.
func quotedString(s string) (string, error) {
var result strings.Builder
result.Grow(len(s) + 2) // optimize for case where no are added.
result.WriteByte('"')
for i := 0; i < len(s); i++ {
b := s[i]
if (b < ' ' && b != 't') || b == 0x7f {
return "", fmt.Errorf("invalid byte %0x", b)
}
if b == '\' || b == '"' {
result.WriteByte('\')
}
result.WriteByte(b)
}
result.WriteByte('"')
return result.String(), nil
}
使用这样的功能:
qf, err := quotedString(f)
if err != nil {
// handle invalid byte in filename f
}
header.Set("Content-Disposition", "attachment; filename=" + qf)
修复无效字节而不是报告错误可能比较方便。清理无效的UTF8可能也是个好主意。这里有一个报价函数可以做到这一点:
// cleanQuotedString returns s quoted per quoted-string in RFC 7230 with invalid
// bytes and invalid UTF8 replaced with _.
func cleanQuotedString(s string) string {
var result strings.Builder
result.Grow(len(s) + 2) // optimize for case where no are added.
result.WriteByte('"')
for _, r := range s {
if (r < ' ' && r != 't') || r == 0x7f || r == 0xfffd {
r = '_'
}
if r == '\' || r == '"' {
result.WriteByte('\')
}
result.WriteRune(r)
}
result.WriteByte('"')
return result.String()
}
如果您知道文件名不包含无效字节,那么从mime/multipart包源复制以下代码:
var quoteEscaper = strings.NewReplacer("\", "\\", `"`, "\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
标准库代码与Steven Penny的答案中的代码相似,但标准库代码只分配和构建一次替换器,而不是每次调用escapeQuotes
。
一种方法是使用multipart
包[1]:
package main
import (
"mime/multipart"
"strings"
)
func main() {
b := new(strings.Builder)
m := multipart.NewWriter(b)
defer m.Close()
m.CreateFormFile("attachment", "filename.jpg")
print(b.String())
}
结果:
--81200ce57413eafde86bb95b1ba47121862043451ba5e55cda9af9573277
Content-Disposition: form-data; name="attachment"; filename="filename.jpg"
Content-Type: application/octet-stream
或者你可以使用这个函数,基于Go源代码[2]:
package escape
import "strings"
func escapeQuotes(s string) string {
return strings.NewReplacer(``, `\`, `"`, `"`).Replace(s)
}
- https://golang.org/pkg/mime/multipart
- https://github.com/golang/go/blob/go1.16.5/src/mime/multipart/writer.go#L132-L136
我认为这只是意味着文件名周围应该有普通的引号。假设你想修复文件名,那么你可以按照MDN的建议设置标题。
不转义引号可能是可能的,但前提是文件名不包含空格:
w.Header().Set("Content-Disposition", "attachment; filename=testsomething.txt")
文件名周围的引号允许有空格:
w.Header().Set("Content-Disposition", "attachment; filename="test something.txt"")
使用多行引号(`(代替
w.Header().Set("Content-Disposition", `attachment; filename="test something.txt"`)
您需要确保文件名不包含任何可能被操作系统解释的字符,有些字符会破坏文件的路径。例如,拥有/或\可能会导致下载出现一些问题,或者文件名太长。
假设文件名不是最终用户定义的,你可能会没事。如果使用用户自由文本,那么你可能需要以某种方式进行限制和验证。
这有什么问题,只是像这样转义和添加其他头值?
w.Header().Add("Content-Disposition", "attachment; filename="flename.txt"")