Source file src/mime/multipart/formdata_test.go

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package multipart
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"math"
    12  	"net/textproto"
    13  	"os"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  func TestReadForm(t *testing.T) {
    19  	b := strings.NewReader(strings.ReplaceAll(message, "\n", "\r\n"))
    20  	r := NewReader(b, boundary)
    21  	f, err := r.ReadForm(25)
    22  	if err != nil {
    23  		t.Fatal("ReadForm:", err)
    24  	}
    25  	defer f.RemoveAll()
    26  	if g, e := f.Value["texta"][0], textaValue; g != e {
    27  		t.Errorf("texta value = %q, want %q", g, e)
    28  	}
    29  	if g, e := f.Value["textb"][0], textbValue; g != e {
    30  		t.Errorf("texta value = %q, want %q", g, e)
    31  	}
    32  	fd := testFile(t, f.File["filea"][0], "filea.txt", fileaContents)
    33  	if _, ok := fd.(*os.File); ok {
    34  		t.Error("file is *os.File, should not be")
    35  	}
    36  	fd.Close()
    37  	fd = testFile(t, f.File["fileb"][0], "fileb.txt", filebContents)
    38  	if _, ok := fd.(*os.File); !ok {
    39  		t.Errorf("file has unexpected underlying type %T", fd)
    40  	}
    41  	fd.Close()
    42  }
    43  
    44  func TestReadFormWithNamelessFile(t *testing.T) {
    45  	b := strings.NewReader(strings.ReplaceAll(messageWithFileWithoutName, "\n", "\r\n"))
    46  	r := NewReader(b, boundary)
    47  	f, err := r.ReadForm(25)
    48  	if err != nil {
    49  		t.Fatal("ReadForm:", err)
    50  	}
    51  	defer f.RemoveAll()
    52  
    53  	if g, e := f.Value["hiddenfile"][0], filebContents; g != e {
    54  		t.Errorf("hiddenfile value = %q, want %q", g, e)
    55  	}
    56  }
    57  
    58  // Issue 40430: Handle ReadForm(math.MaxInt64)
    59  func TestReadFormMaxMemoryOverflow(t *testing.T) {
    60  	b := strings.NewReader(strings.ReplaceAll(messageWithTextContentType, "\n", "\r\n"))
    61  	r := NewReader(b, boundary)
    62  	f, err := r.ReadForm(math.MaxInt64)
    63  	if err != nil {
    64  		t.Fatalf("ReadForm(MaxInt64): %v", err)
    65  	}
    66  	if f == nil {
    67  		t.Fatal("ReadForm(MaxInt64): missing form")
    68  	}
    69  }
    70  
    71  func TestReadFormWithTextContentType(t *testing.T) {
    72  	// From https://github.com/golang/go/issues/24041
    73  	b := strings.NewReader(strings.ReplaceAll(messageWithTextContentType, "\n", "\r\n"))
    74  	r := NewReader(b, boundary)
    75  	f, err := r.ReadForm(25)
    76  	if err != nil {
    77  		t.Fatal("ReadForm:", err)
    78  	}
    79  	defer f.RemoveAll()
    80  
    81  	if g, e := f.Value["texta"][0], textaValue; g != e {
    82  		t.Errorf("texta value = %q, want %q", g, e)
    83  	}
    84  }
    85  
    86  func testFile(t *testing.T, fh *FileHeader, efn, econtent string) File {
    87  	if fh.Filename != efn {
    88  		t.Errorf("filename = %q, want %q", fh.Filename, efn)
    89  	}
    90  	if fh.Size != int64(len(econtent)) {
    91  		t.Errorf("size = %d, want %d", fh.Size, len(econtent))
    92  	}
    93  	f, err := fh.Open()
    94  	if err != nil {
    95  		t.Fatal("opening file:", err)
    96  	}
    97  	b := new(strings.Builder)
    98  	_, err = io.Copy(b, f)
    99  	if err != nil {
   100  		t.Fatal("copying contents:", err)
   101  	}
   102  	if g := b.String(); g != econtent {
   103  		t.Errorf("contents = %q, want %q", g, econtent)
   104  	}
   105  	return f
   106  }
   107  
   108  const (
   109  	fileaContents = "This is a test file."
   110  	filebContents = "Another test file."
   111  	textaValue    = "foo"
   112  	textbValue    = "bar"
   113  	boundary      = `MyBoundary`
   114  )
   115  
   116  const messageWithFileWithoutName = `
   117  --MyBoundary
   118  Content-Disposition: form-data; name="hiddenfile"; filename=""
   119  Content-Type: text/plain
   120  
   121  ` + filebContents + `
   122  --MyBoundary--
   123  `
   124  
   125  const messageWithTextContentType = `
   126  --MyBoundary
   127  Content-Disposition: form-data; name="texta"
   128  Content-Type: text/plain
   129  
   130  ` + textaValue + `
   131  --MyBoundary
   132  `
   133  
   134  const message = `
   135  --MyBoundary
   136  Content-Disposition: form-data; name="filea"; filename="filea.txt"
   137  Content-Type: text/plain
   138  
   139  ` + fileaContents + `
   140  --MyBoundary
   141  Content-Disposition: form-data; name="fileb"; filename="fileb.txt"
   142  Content-Type: text/plain
   143  
   144  ` + filebContents + `
   145  --MyBoundary
   146  Content-Disposition: form-data; name="texta"
   147  
   148  ` + textaValue + `
   149  --MyBoundary
   150  Content-Disposition: form-data; name="textb"
   151  
   152  ` + textbValue + `
   153  --MyBoundary--
   154  `
   155  
   156  func TestReadForm_NoReadAfterEOF(t *testing.T) {
   157  	maxMemory := int64(32) << 20
   158  	boundary := `---------------------------8d345eef0d38dc9`
   159  	body := `
   160  -----------------------------8d345eef0d38dc9
   161  Content-Disposition: form-data; name="version"
   162  
   163  171
   164  -----------------------------8d345eef0d38dc9--`
   165  
   166  	mr := NewReader(&failOnReadAfterErrorReader{t: t, r: strings.NewReader(body)}, boundary)
   167  
   168  	f, err := mr.ReadForm(maxMemory)
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  	t.Logf("Got: %#v", f)
   173  }
   174  
   175  // failOnReadAfterErrorReader is an io.Reader wrapping r.
   176  // It fails t if any Read is called after a failing Read.
   177  type failOnReadAfterErrorReader struct {
   178  	t      *testing.T
   179  	r      io.Reader
   180  	sawErr error
   181  }
   182  
   183  func (r *failOnReadAfterErrorReader) Read(p []byte) (n int, err error) {
   184  	if r.sawErr != nil {
   185  		r.t.Fatalf("unexpected Read on Reader after previous read saw error %v", r.sawErr)
   186  	}
   187  	n, err = r.r.Read(p)
   188  	r.sawErr = err
   189  	return
   190  }
   191  
   192  // TestReadForm_NonFileMaxMemory asserts that the ReadForm maxMemory limit is applied
   193  // while processing non-file form data as well as file form data.
   194  func TestReadForm_NonFileMaxMemory(t *testing.T) {
   195  	if testing.Short() {
   196  		t.Skip("skipping in -short mode")
   197  	}
   198  	n := 10 << 20
   199  	largeTextValue := strings.Repeat("1", n)
   200  	message := `--MyBoundary
   201  Content-Disposition: form-data; name="largetext"
   202  
   203  ` + largeTextValue + `
   204  --MyBoundary--
   205  `
   206  	testBody := strings.ReplaceAll(message, "\n", "\r\n")
   207  	// Try parsing the form with increasing maxMemory values.
   208  	// Changes in how we account for non-file form data may cause the exact point
   209  	// where we change from rejecting the form as too large to accepting it to vary,
   210  	// but we should see both successes and failures.
   211  	const failWhenMaxMemoryLessThan = 128
   212  	for maxMemory := int64(0); maxMemory < failWhenMaxMemoryLessThan*2; maxMemory += 16 {
   213  		b := strings.NewReader(testBody)
   214  		r := NewReader(b, boundary)
   215  		f, err := r.ReadForm(maxMemory)
   216  		if err != nil {
   217  			continue
   218  		}
   219  		if g := f.Value["largetext"][0]; g != largeTextValue {
   220  			t.Errorf("largetext mismatch: got size: %v, expected size: %v", len(g), len(largeTextValue))
   221  		}
   222  		f.RemoveAll()
   223  		if maxMemory < failWhenMaxMemoryLessThan {
   224  			t.Errorf("ReadForm(%v): no error, expect to hit memory limit when maxMemory < %v", maxMemory, failWhenMaxMemoryLessThan)
   225  		}
   226  		return
   227  	}
   228  	t.Errorf("ReadForm(x) failed for x < 1024, expect success")
   229  }
   230  
   231  // TestReadForm_MetadataTooLarge verifies that we account for the size of field names,
   232  // MIME headers, and map entry overhead while limiting the memory consumption of parsed forms.
   233  func TestReadForm_MetadataTooLarge(t *testing.T) {
   234  	for _, test := range []struct {
   235  		name string
   236  		f    func(*Writer)
   237  	}{{
   238  		name: "large name",
   239  		f: func(fw *Writer) {
   240  			name := strings.Repeat("a", 10<<20)
   241  			w, _ := fw.CreateFormField(name)
   242  			w.Write([]byte("value"))
   243  		},
   244  	}, {
   245  		name: "large MIME header",
   246  		f: func(fw *Writer) {
   247  			h := make(textproto.MIMEHeader)
   248  			h.Set("Content-Disposition", `form-data; name="a"`)
   249  			h.Set("X-Foo", strings.Repeat("a", 10<<20))
   250  			w, _ := fw.CreatePart(h)
   251  			w.Write([]byte("value"))
   252  		},
   253  	}, {
   254  		name: "many parts",
   255  		f: func(fw *Writer) {
   256  			for i := 0; i < 110000; i++ {
   257  				w, _ := fw.CreateFormField("f")
   258  				w.Write([]byte("v"))
   259  			}
   260  		},
   261  	}} {
   262  		t.Run(test.name, func(t *testing.T) {
   263  			var buf bytes.Buffer
   264  			fw := NewWriter(&buf)
   265  			test.f(fw)
   266  			if err := fw.Close(); err != nil {
   267  				t.Fatal(err)
   268  			}
   269  			fr := NewReader(&buf, fw.Boundary())
   270  			_, err := fr.ReadForm(0)
   271  			if err != ErrMessageTooLarge {
   272  				t.Errorf("fr.ReadForm() = %v, want ErrMessageTooLarge", err)
   273  			}
   274  		})
   275  	}
   276  }
   277  
   278  // TestReadForm_ManyFiles_Combined tests that a multipart form containing many files only
   279  // results in a single on-disk file.
   280  func TestReadForm_ManyFiles_Combined(t *testing.T) {
   281  	const distinct = false
   282  	testReadFormManyFiles(t, distinct)
   283  }
   284  
   285  // TestReadForm_ManyFiles_Distinct tests that setting GODEBUG=multipartfiles=distinct
   286  // results in every file in a multipart form being placed in a distinct on-disk file.
   287  func TestReadForm_ManyFiles_Distinct(t *testing.T) {
   288  	t.Setenv("GODEBUG", "multipartfiles=distinct")
   289  	const distinct = true
   290  	testReadFormManyFiles(t, distinct)
   291  }
   292  
   293  func testReadFormManyFiles(t *testing.T, distinct bool) {
   294  	var buf bytes.Buffer
   295  	fw := NewWriter(&buf)
   296  	const numFiles = 10
   297  	for i := 0; i < numFiles; i++ {
   298  		name := fmt.Sprint(i)
   299  		w, err := fw.CreateFormFile(name, name)
   300  		if err != nil {
   301  			t.Fatal(err)
   302  		}
   303  		w.Write([]byte(name))
   304  	}
   305  	if err := fw.Close(); err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	fr := NewReader(&buf, fw.Boundary())
   309  	fr.tempDir = t.TempDir()
   310  	form, err := fr.ReadForm(0)
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	for i := 0; i < numFiles; i++ {
   315  		name := fmt.Sprint(i)
   316  		if got := len(form.File[name]); got != 1 {
   317  			t.Fatalf("form.File[%q] has %v entries, want 1", name, got)
   318  		}
   319  		fh := form.File[name][0]
   320  		file, err := fh.Open()
   321  		if err != nil {
   322  			t.Fatalf("form.File[%q].Open() = %v", name, err)
   323  		}
   324  		if distinct {
   325  			if _, ok := file.(*os.File); !ok {
   326  				t.Fatalf("form.File[%q].Open: %T, want *os.File", name, file)
   327  			}
   328  		}
   329  		got, err := io.ReadAll(file)
   330  		file.Close()
   331  		if string(got) != name || err != nil {
   332  			t.Fatalf("read form.File[%q]: %q, %v; want %q, nil", name, string(got), err, name)
   333  		}
   334  	}
   335  	dir, err := os.Open(fr.tempDir)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  	defer dir.Close()
   340  	names, err := dir.Readdirnames(0)
   341  	if err != nil {
   342  		t.Fatal(err)
   343  	}
   344  	wantNames := 1
   345  	if distinct {
   346  		wantNames = numFiles
   347  	}
   348  	if len(names) != wantNames {
   349  		t.Fatalf("temp dir contains %v files; want 1", len(names))
   350  	}
   351  	if err := form.RemoveAll(); err != nil {
   352  		t.Fatalf("form.RemoveAll() = %v", err)
   353  	}
   354  	names, err = dir.Readdirnames(0)
   355  	if err != nil {
   356  		t.Fatal(err)
   357  	}
   358  	if len(names) != 0 {
   359  		t.Fatalf("temp dir contains %v files; want 0", len(names))
   360  	}
   361  }
   362  
   363  func TestReadFormLimits(t *testing.T) {
   364  	for _, test := range []struct {
   365  		values           int
   366  		files            int
   367  		extraKeysPerFile int
   368  		wantErr          error
   369  		godebug          string
   370  	}{
   371  		{values: 1000},
   372  		{values: 1001, wantErr: ErrMessageTooLarge},
   373  		{values: 500, files: 500},
   374  		{values: 501, files: 500, wantErr: ErrMessageTooLarge},
   375  		{files: 1000},
   376  		{files: 1001, wantErr: ErrMessageTooLarge},
   377  		{files: 1, extraKeysPerFile: 9998}, // plus Content-Disposition and Content-Type
   378  		{files: 1, extraKeysPerFile: 10000, wantErr: ErrMessageTooLarge},
   379  		{godebug: "multipartmaxparts=100", values: 100},
   380  		{godebug: "multipartmaxparts=100", values: 101, wantErr: ErrMessageTooLarge},
   381  		{godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 48},
   382  		{godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 50, wantErr: ErrMessageTooLarge},
   383  	} {
   384  		name := fmt.Sprintf("values=%v/files=%v/extraKeysPerFile=%v", test.values, test.files, test.extraKeysPerFile)
   385  		if test.godebug != "" {
   386  			name += fmt.Sprintf("/godebug=%v", test.godebug)
   387  		}
   388  		t.Run(name, func(t *testing.T) {
   389  			if test.godebug != "" {
   390  				t.Setenv("GODEBUG", test.godebug)
   391  			}
   392  			var buf bytes.Buffer
   393  			fw := NewWriter(&buf)
   394  			for i := 0; i < test.values; i++ {
   395  				w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i))
   396  				fmt.Fprintf(w, "value %v", i)
   397  			}
   398  			for i := 0; i < test.files; i++ {
   399  				h := make(textproto.MIMEHeader)
   400  				h.Set("Content-Disposition",
   401  					fmt.Sprintf(`form-data; name="file%v"; filename="file%v"`, i, i))
   402  				h.Set("Content-Type", "application/octet-stream")
   403  				for j := 0; j < test.extraKeysPerFile; j++ {
   404  					h.Set(fmt.Sprintf("k%v", j), "v")
   405  				}
   406  				w, _ := fw.CreatePart(h)
   407  				fmt.Fprintf(w, "value %v", i)
   408  			}
   409  			if err := fw.Close(); err != nil {
   410  				t.Fatal(err)
   411  			}
   412  			fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary())
   413  			form, err := fr.ReadForm(1 << 10)
   414  			if err == nil {
   415  				defer form.RemoveAll()
   416  			}
   417  			if err != test.wantErr {
   418  				t.Errorf("ReadForm = %v, want %v", err, test.wantErr)
   419  			}
   420  		})
   421  	}
   422  }
   423  
   424  func BenchmarkReadForm(b *testing.B) {
   425  	for _, test := range []struct {
   426  		name string
   427  		form func(fw *Writer, count int)
   428  	}{{
   429  		name: "fields",
   430  		form: func(fw *Writer, count int) {
   431  			for i := 0; i < count; i++ {
   432  				w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i))
   433  				fmt.Fprintf(w, "value %v", i)
   434  			}
   435  		},
   436  	}, {
   437  		name: "files",
   438  		form: func(fw *Writer, count int) {
   439  			for i := 0; i < count; i++ {
   440  				w, _ := fw.CreateFormFile(fmt.Sprintf("field%v", i), fmt.Sprintf("file%v", i))
   441  				fmt.Fprintf(w, "value %v", i)
   442  			}
   443  		},
   444  	}} {
   445  		b.Run(test.name, func(b *testing.B) {
   446  			for _, maxMemory := range []int64{
   447  				0,
   448  				1 << 20,
   449  			} {
   450  				var buf bytes.Buffer
   451  				fw := NewWriter(&buf)
   452  				test.form(fw, 10)
   453  				if err := fw.Close(); err != nil {
   454  					b.Fatal(err)
   455  				}
   456  				b.Run(fmt.Sprintf("maxMemory=%v", maxMemory), func(b *testing.B) {
   457  					b.ReportAllocs()
   458  					for i := 0; i < b.N; i++ {
   459  						fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary())
   460  						form, err := fr.ReadForm(maxMemory)
   461  						if err != nil {
   462  							b.Fatal(err)
   463  						}
   464  						form.RemoveAll()
   465  					}
   466  
   467  				})
   468  			}
   469  		})
   470  	}
   471  }
   472  

View as plain text