// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package multipart
import (
"bytes"
"io"
"os"
"strings"
"testing"
)
func TestReadForm(t *testing.T) {
b := strings.NewReader(strings.ReplaceAll(message, "\n", "\r\n"))
r := NewReader(b, boundary)
f, err := r.ReadForm(25)
if err != nil {
t.Fatal("ReadForm:", err)
}
defer f.RemoveAll()
if g, e := f.Value["texta"][0], textaValue; g != e {
t.Errorf("texta value = %q, want %q", g, e)
}
if g, e := f.Value["textb"][0], textbValue; g != e {
t.Errorf("texta value = %q, want %q", g, e)
}
fd := testFile(t, f.File["filea"][0], "filea.txt", fileaContents)
if _, ok := fd.(*os.File); ok {
t.Error("file is *os.File, should not be")
}
fd.Close()
fd = testFile(t, f.File["fileb"][0], "fileb.txt", filebContents)
if _, ok := fd.(*os.File); !ok {
t.Errorf("file has unexpected underlying type %T", fd)
}
fd.Close()
}
func TestReadFormWithNamelessFile(t *testing.T) {
b := strings.NewReader(strings.ReplaceAll(messageWithFileWithoutName, "\n", "\r\n"))
r := NewReader(b, boundary)
f, err := r.ReadForm(25)
if err != nil {
t.Fatal("ReadForm:", err)
}
defer f.RemoveAll()
if g, e := f.Value["hiddenfile"][0], filebContents; g != e {
t.Errorf("hiddenfile value = %q, want %q", g, e)
}
}
func TestReadFormWithTextContentType(t *testing.T) {
// From https://github.com/golang/go/issues/24041
b := strings.NewReader(strings.ReplaceAll(messageWithTextContentType, "\n", "\r\n"))
r := NewReader(b, boundary)
f, err := r.ReadForm(25)
if err != nil {
t.Fatal("ReadForm:", err)
}
defer f.RemoveAll()
if g, e := f.Value["texta"][0], textaValue; g != e {
t.Errorf("texta value = %q, want %q", g, e)
}
}
func testFile(t *testing.T, fh *FileHeader, efn, econtent string) File {
if fh.Filename != efn {
t.Errorf("filename = %q, want %q", fh.Filename, efn)
}
if fh.Size != int64(len(econtent)) {
t.Errorf("size = %d, want %d", fh.Size, len(econtent))
}
f, err := fh.Open()
if err != nil {
t.Fatal("opening file:", err)
}
b := new(bytes.Buffer)
_, err = io.Copy(b, f)
if err != nil {
t.Fatal("copying contents:", err)
}
if g := b.String(); g != econtent {
t.Errorf("contents = %q, want %q", g, econtent)
}
return f
}
const (
fileaContents = "This is a test file."
filebContents = "Another test file."
textaValue = "foo"
textbValue = "bar"
boundary = `MyBoundary`
)
const messageWithFileWithoutName = `
--MyBoundary
Content-Disposition: form-data; name="hiddenfile"; filename=""
Content-Type: text/plain
` + filebContents + `
--MyBoundary--
`
const messageWithTextContentType = `
--MyBoundary
Content-Disposition: form-data; name="texta"
Content-Type: text/plain
` + textaValue + `
--MyBoundary
`
const message = `
--MyBoundary
Content-Disposition: form-data; name="filea"; filename="filea.txt"
Content-Type: text/plain
` + fileaContents + `
--MyBoundary
Content-Disposition: form-data; name="fileb"; filename="fileb.txt"
Content-Type: text/plain
` + filebContents + `
--MyBoundary
Content-Disposition: form-data; name="texta"
` + textaValue + `
--MyBoundary
Content-Disposition: form-data; name="textb"
` + textbValue + `
--MyBoundary--
`
func TestReadForm_NoReadAfterEOF(t *testing.T) {
maxMemory := int64(32) << 20
boundary := `---------------------------8d345eef0d38dc9`
body := `
-----------------------------8d345eef0d38dc9
Content-Disposition: form-data; name="version"
171
-----------------------------8d345eef0d38dc9--`
mr := NewReader(&failOnReadAfterErrorReader{t: t, r: strings.NewReader(body)}, boundary)
f, err := mr.ReadForm(maxMemory)
if err != nil {
t.Fatal(err)
}
t.Logf("Got: %#v", f)
}
// failOnReadAfterErrorReader is an io.Reader wrapping r.
// It fails t if any Read is called after a failing Read.
type failOnReadAfterErrorReader struct {
t *testing.T
r io.Reader
sawErr error
}
func (r *failOnReadAfterErrorReader) Read(p []byte) (n int, err error) {
if r.sawErr != nil {
r.t.Fatalf("unexpected Read on Reader after previous read saw error %v", r.sawErr)
}
n, err = r.r.Read(p)
r.sawErr = err
return
}
// TestReadForm_NonFileMaxMemory asserts that the ReadForm maxMemory limit is applied
// while processing non-file form data as well as file form data.
func TestReadForm_NonFileMaxMemory(t *testing.T) {
largeTextValue := strings.Repeat("1", (10<<20)+25)
message := `--MyBoundary
Content-Disposition: form-data; name="largetext"
` + largeTextValue + `
--MyBoundary--
`
testBody := strings.ReplaceAll(message, "\n", "\r\n")
testCases := []struct {
name string
maxMemory int64
err error
}{
{"smaller", 50, nil},
{"exact-fit", 25, nil},
{"too-large", 0, ErrMessageTooLarge},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b := strings.NewReader(testBody)
r := NewReader(b, boundary)
f, err := r.ReadForm(tc.maxMemory)
if err == nil {
defer f.RemoveAll()
}
if tc.err != err {
t.Fatalf("ReadForm error - got: %v; expected: %v", tc.err, err)
}
if err == nil {
if g := f.Value["largetext"][0]; g != largeTextValue {
t.Errorf("largetext mismatch: got size: %v, expected size: %v", len(g), len(largeTextValue))
}
}
})
}
}