如何在HTTP标头字段中引用字符串



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)
}
  1. https://golang.org/pkg/mime/multipart
  2. 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"")

最新更新