mime/multipart: add helper to build content-disposition header contents

This PR adds an helper FileContentDisposition that builds multipart
Content-Disposition header contents with field name and file name,
escaping quotes and escape characters.

The  function is then called in the related helper CreateFormFile.

The new function allows users to add other custom MIMEHeaders,
without having to rewrite the char escaping logic of field name and
file name, which is provided by the new helper.

Fixes #46771

Change-Id: Ifc82a79583feb6dd609ca1e6024e612fb58c05ce
GitHub-Last-Rev: 969f846fa967d2b3eca7a21ee096b299b8a94546
GitHub-Pull-Request: golang/go#63324
Reviewed-on: https://go-review.googlesource.com/c/go/+/531995
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Luca Maltagliati 2025-03-12 21:40:12 +00:00 committed by Gopher Robot
parent a68bf75d34
commit d729053edf
4 changed files with 30 additions and 3 deletions

1
api/next/46771.txt Normal file
View File

@ -0,0 +1 @@
pkg mime/multipart, func FileContentDisposition(string, string) string #46771

View File

@ -0,0 +1,2 @@
The new helper function [FieldContentDisposition] builds multipart
Content-Disposition header fields.

View File

@ -135,9 +135,7 @@ func escapeQuotes(s string) string {
// a new form-data header with the provided field name and file name.
func (w *Writer) CreateFormFile(fieldname, filename string) (io.Writer, error) {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
escapeQuotes(fieldname), escapeQuotes(filename)))
h.Set("Content-Disposition", FileContentDisposition(fieldname, filename))
h.Set("Content-Type", "application/octet-stream")
return w.CreatePart(h)
}
@ -151,6 +149,13 @@ func (w *Writer) CreateFormField(fieldname string) (io.Writer, error) {
return w.CreatePart(h)
}
// FileContentDisposition returns the value of a Content-Disposition header
// with the provided field name and file name.
func FileContentDisposition(fieldname, filename string) string {
return fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
escapeQuotes(fieldname), escapeQuotes(filename))
}
// WriteField calls [Writer.CreateFormField] and then writes the given value.
func (w *Writer) WriteField(fieldname, value string) error {
p, err := w.CreateFormField(fieldname)

View File

@ -172,3 +172,22 @@ func TestSortedHeader(t *testing.T) {
t.Fatalf("\n got: %q\nwant: %q\n", buf.String(), want)
}
}
func TestFileContentDisposition(t *testing.T) {
tests := []struct {
fieldname string
filename string
want string
}{
{"somefield", "somefile.txt", `form-data; name="somefield"; filename="somefile.txt"`},
{`field"withquotes"`, "somefile.txt", `form-data; name="field\"withquotes\""; filename="somefile.txt"`},
{`somefield`, `somefile"withquotes".txt`, `form-data; name="somefield"; filename="somefile\"withquotes\".txt"`},
{`somefield\withbackslash`, "somefile.txt", `form-data; name="somefield\\withbackslash"; filename="somefile.txt"`},
{"somefield", `somefile\withbackslash.txt`, `form-data; name="somefield"; filename="somefile\\withbackslash.txt"`},
}
for i, tt := range tests {
if found := FileContentDisposition(tt.fieldname, tt.filename); found != tt.want {
t.Errorf(`%d. found: "%s"; want: "%s"`, i, found, tt.want)
}
}
}