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