github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/mime/multipart/multipart_test.go (about) 1 // Copyright 2010 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 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net/textproto" 14 "os" 15 "reflect" 16 "strings" 17 "testing" 18 ) 19 20 func TestBoundaryLine(t *testing.T) { 21 mr := NewReader(strings.NewReader(""), "myBoundary") 22 if !mr.isBoundaryDelimiterLine([]byte("--myBoundary\r\n")) { 23 t.Error("expected") 24 } 25 if !mr.isBoundaryDelimiterLine([]byte("--myBoundary \r\n")) { 26 t.Error("expected") 27 } 28 if !mr.isBoundaryDelimiterLine([]byte("--myBoundary \n")) { 29 t.Error("expected") 30 } 31 if mr.isBoundaryDelimiterLine([]byte("--myBoundary bogus \n")) { 32 t.Error("expected fail") 33 } 34 if mr.isBoundaryDelimiterLine([]byte("--myBoundary bogus--")) { 35 t.Error("expected fail") 36 } 37 } 38 39 func escapeString(v string) string { 40 bytes, _ := json.Marshal(v) 41 return string(bytes) 42 } 43 44 func expectEq(t *testing.T, expected, actual, what string) { 45 if expected == actual { 46 return 47 } 48 t.Errorf("Unexpected value for %s; got %s (len %d) but expected: %s (len %d)", 49 what, escapeString(actual), len(actual), escapeString(expected), len(expected)) 50 } 51 52 func TestNameAccessors(t *testing.T) { 53 tests := [...][3]string{ 54 {`form-data; name="foo"`, "foo", ""}, 55 {` form-data ; name=foo`, "foo", ""}, 56 {`FORM-DATA;name="foo"`, "foo", ""}, 57 {` FORM-DATA ; name="foo"`, "foo", ""}, 58 {` FORM-DATA ; name="foo"`, "foo", ""}, 59 {` FORM-DATA ; name=foo`, "foo", ""}, 60 {` FORM-DATA ; filename="foo.txt"; name=foo; baz=quux`, "foo", "foo.txt"}, 61 {` not-form-data ; filename="bar.txt"; name=foo; baz=quux`, "", "bar.txt"}, 62 } 63 for i, test := range tests { 64 p := &Part{Header: make(map[string][]string)} 65 p.Header.Set("Content-Disposition", test[0]) 66 if g, e := p.FormName(), test[1]; g != e { 67 t.Errorf("test %d: FormName() = %q; want %q", i, g, e) 68 } 69 if g, e := p.FileName(), test[2]; g != e { 70 t.Errorf("test %d: FileName() = %q; want %q", i, g, e) 71 } 72 } 73 } 74 75 var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8) 76 77 func testMultipartBody(sep string) string { 78 testBody := ` 79 This is a multi-part message. This line is ignored. 80 --MyBoundary 81 Header1: value1 82 HEADER2: value2 83 foo-bar: baz 84 85 My value 86 The end. 87 --MyBoundary 88 name: bigsection 89 90 [longline] 91 --MyBoundary 92 Header1: value1b 93 HEADER2: value2b 94 foo-bar: bazb 95 96 Line 1 97 Line 2 98 Line 3 ends in a newline, but just one. 99 100 --MyBoundary 101 102 never read data 103 --MyBoundary-- 104 105 106 useless trailer 107 ` 108 testBody = strings.ReplaceAll(testBody, "\n", sep) 109 return strings.Replace(testBody, "[longline]", longLine, 1) 110 } 111 112 func TestMultipart(t *testing.T) { 113 bodyReader := strings.NewReader(testMultipartBody("\r\n")) 114 testMultipart(t, bodyReader, false) 115 } 116 117 func TestMultipartOnlyNewlines(t *testing.T) { 118 bodyReader := strings.NewReader(testMultipartBody("\n")) 119 testMultipart(t, bodyReader, true) 120 } 121 122 func TestMultipartSlowInput(t *testing.T) { 123 bodyReader := strings.NewReader(testMultipartBody("\r\n")) 124 testMultipart(t, &slowReader{bodyReader}, false) 125 } 126 127 func testMultipart(t *testing.T, r io.Reader, onlyNewlines bool) { 128 t.Parallel() 129 reader := NewReader(r, "MyBoundary") 130 buf := new(bytes.Buffer) 131 132 // Part1 133 part, err := reader.NextPart() 134 if part == nil || err != nil { 135 t.Error("Expected part1") 136 return 137 } 138 if x := part.Header.Get("Header1"); x != "value1" { 139 t.Errorf("part.Header.Get(%q) = %q, want %q", "Header1", x, "value1") 140 } 141 if x := part.Header.Get("foo-bar"); x != "baz" { 142 t.Errorf("part.Header.Get(%q) = %q, want %q", "foo-bar", x, "baz") 143 } 144 if x := part.Header.Get("Foo-Bar"); x != "baz" { 145 t.Errorf("part.Header.Get(%q) = %q, want %q", "Foo-Bar", x, "baz") 146 } 147 buf.Reset() 148 if _, err := io.Copy(buf, part); err != nil { 149 t.Errorf("part 1 copy: %v", err) 150 } 151 152 adjustNewlines := func(s string) string { 153 if onlyNewlines { 154 return strings.ReplaceAll(s, "\r\n", "\n") 155 } 156 return s 157 } 158 159 expectEq(t, adjustNewlines("My value\r\nThe end."), buf.String(), "Value of first part") 160 161 // Part2 162 part, err = reader.NextPart() 163 if err != nil { 164 t.Fatalf("Expected part2; got: %v", err) 165 return 166 } 167 if e, g := "bigsection", part.Header.Get("name"); e != g { 168 t.Errorf("part2's name header: expected %q, got %q", e, g) 169 } 170 buf.Reset() 171 if _, err := io.Copy(buf, part); err != nil { 172 t.Errorf("part 2 copy: %v", err) 173 } 174 s := buf.String() 175 if len(s) != len(longLine) { 176 t.Errorf("part2 body expected long line of length %d; got length %d", 177 len(longLine), len(s)) 178 } 179 if s != longLine { 180 t.Errorf("part2 long body didn't match") 181 } 182 183 // Part3 184 part, err = reader.NextPart() 185 if part == nil || err != nil { 186 t.Error("Expected part3") 187 return 188 } 189 if part.Header.Get("foo-bar") != "bazb" { 190 t.Error("Expected foo-bar: bazb") 191 } 192 buf.Reset() 193 if _, err := io.Copy(buf, part); err != nil { 194 t.Errorf("part 3 copy: %v", err) 195 } 196 expectEq(t, adjustNewlines("Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n"), 197 buf.String(), "body of part 3") 198 199 // Part4 200 part, err = reader.NextPart() 201 if part == nil || err != nil { 202 t.Error("Expected part 4 without errors") 203 return 204 } 205 206 // Non-existent part5 207 part, err = reader.NextPart() 208 if part != nil { 209 t.Error("Didn't expect a fifth part.") 210 } 211 if err != io.EOF { 212 t.Errorf("On fifth part expected io.EOF; got %v", err) 213 } 214 } 215 216 func TestVariousTextLineEndings(t *testing.T) { 217 tests := [...]string{ 218 "Foo\nBar", 219 "Foo\nBar\n", 220 "Foo\r\nBar", 221 "Foo\r\nBar\r\n", 222 "Foo\rBar", 223 "Foo\rBar\r", 224 "\x00\x01\x02\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10", 225 } 226 227 for testNum, expectedBody := range tests { 228 body := "--BOUNDARY\r\n" + 229 "Content-Disposition: form-data; name=\"value\"\r\n" + 230 "\r\n" + 231 expectedBody + 232 "\r\n--BOUNDARY--\r\n" 233 bodyReader := strings.NewReader(body) 234 235 reader := NewReader(bodyReader, "BOUNDARY") 236 buf := new(bytes.Buffer) 237 part, err := reader.NextPart() 238 if part == nil { 239 t.Errorf("Expected a body part on text %d", testNum) 240 continue 241 } 242 if err != nil { 243 t.Errorf("Unexpected error on text %d: %v", testNum, err) 244 continue 245 } 246 written, err := io.Copy(buf, part) 247 expectEq(t, expectedBody, buf.String(), fmt.Sprintf("test %d", testNum)) 248 if err != nil { 249 t.Errorf("Error copying multipart; bytes=%v, error=%v", written, err) 250 } 251 252 part, err = reader.NextPart() 253 if part != nil { 254 t.Errorf("Unexpected part in test %d", testNum) 255 } 256 if err != io.EOF { 257 t.Errorf("On test %d expected io.EOF; got %v", testNum, err) 258 } 259 260 } 261 } 262 263 type maliciousReader struct { 264 t *testing.T 265 n int 266 } 267 268 const maxReadThreshold = 1 << 20 269 270 func (mr *maliciousReader) Read(b []byte) (n int, err error) { 271 mr.n += len(b) 272 if mr.n >= maxReadThreshold { 273 mr.t.Fatal("too much was read") 274 return 0, io.EOF 275 } 276 return len(b), nil 277 } 278 279 func TestLineLimit(t *testing.T) { 280 mr := &maliciousReader{t: t} 281 r := NewReader(mr, "fooBoundary") 282 part, err := r.NextPart() 283 if part != nil { 284 t.Errorf("unexpected part read") 285 } 286 if err == nil { 287 t.Errorf("expected an error") 288 } 289 if mr.n >= maxReadThreshold { 290 t.Errorf("expected to read < %d bytes; read %d", maxReadThreshold, mr.n) 291 } 292 } 293 294 func TestMultipartTruncated(t *testing.T) { 295 testBody := ` 296 This is a multi-part message. This line is ignored. 297 --MyBoundary 298 foo-bar: baz 299 300 Oh no, premature EOF! 301 ` 302 body := strings.ReplaceAll(testBody, "\n", "\r\n") 303 bodyReader := strings.NewReader(body) 304 r := NewReader(bodyReader, "MyBoundary") 305 306 part, err := r.NextPart() 307 if err != nil { 308 t.Fatalf("didn't get a part") 309 } 310 _, err = io.Copy(ioutil.Discard, part) 311 if err != io.ErrUnexpectedEOF { 312 t.Fatalf("expected error io.ErrUnexpectedEOF; got %v", err) 313 } 314 } 315 316 type slowReader struct { 317 r io.Reader 318 } 319 320 func (s *slowReader) Read(p []byte) (int, error) { 321 if len(p) == 0 { 322 return s.r.Read(p) 323 } 324 return s.r.Read(p[:1]) 325 } 326 327 type sentinelReader struct { 328 // done is closed when this reader is read from. 329 done chan struct{} 330 } 331 332 func (s *sentinelReader) Read([]byte) (int, error) { 333 if s.done != nil { 334 close(s.done) 335 s.done = nil 336 } 337 return 0, io.EOF 338 } 339 340 // TestMultipartStreamReadahead tests that PartReader does not block 341 // on reading past the end of a part, ensuring that it can be used on 342 // a stream like multipart/x-mixed-replace. See golang.org/issue/15431 343 func TestMultipartStreamReadahead(t *testing.T) { 344 testBody1 := ` 345 This is a multi-part message. This line is ignored. 346 --MyBoundary 347 foo-bar: baz 348 349 Body 350 --MyBoundary 351 ` 352 testBody2 := `foo-bar: bop 353 354 Body 2 355 --MyBoundary-- 356 ` 357 done1 := make(chan struct{}) 358 reader := NewReader( 359 io.MultiReader( 360 strings.NewReader(testBody1), 361 &sentinelReader{done1}, 362 strings.NewReader(testBody2)), 363 "MyBoundary") 364 365 var i int 366 readPart := func(hdr textproto.MIMEHeader, body string) { 367 part, err := reader.NextPart() 368 if part == nil || err != nil { 369 t.Fatalf("Part %d: NextPart failed: %v", i, err) 370 } 371 372 if !reflect.DeepEqual(part.Header, hdr) { 373 t.Errorf("Part %d: part.Header = %v, want %v", i, part.Header, hdr) 374 } 375 data, err := ioutil.ReadAll(part) 376 expectEq(t, body, string(data), fmt.Sprintf("Part %d body", i)) 377 if err != nil { 378 t.Fatalf("Part %d: ReadAll failed: %v", i, err) 379 } 380 i++ 381 } 382 383 readPart(textproto.MIMEHeader{"Foo-Bar": {"baz"}}, "Body") 384 385 select { 386 case <-done1: 387 t.Errorf("Reader read past second boundary") 388 default: 389 } 390 391 readPart(textproto.MIMEHeader{"Foo-Bar": {"bop"}}, "Body 2") 392 } 393 394 func TestLineContinuation(t *testing.T) { 395 // This body, extracted from an email, contains headers that span multiple 396 // lines. 397 398 // TODO: The original mail ended with a double-newline before the 399 // final delimiter; this was manually edited to use a CRLF. 400 testBody := 401 "\n--Apple-Mail-2-292336769\nContent-Transfer-Encoding: 7bit\nContent-Type: text/plain;\n\tcharset=US-ASCII;\n\tdelsp=yes;\n\tformat=flowed\n\nI'm finding the same thing happening on my system (10.4.1).\n\n\n--Apple-Mail-2-292336769\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html;\n\tcharset=ISO-8859-1\n\n<HTML><BODY>I'm finding the same thing =\nhappening on my system (10.4.1).=A0 But I built it with XCode =\n2.0.</BODY></=\nHTML>=\n\r\n--Apple-Mail-2-292336769--\n" 402 403 r := NewReader(strings.NewReader(testBody), "Apple-Mail-2-292336769") 404 405 for i := 0; i < 2; i++ { 406 part, err := r.NextPart() 407 if err != nil { 408 t.Fatalf("didn't get a part") 409 } 410 var buf bytes.Buffer 411 n, err := io.Copy(&buf, part) 412 if err != nil { 413 t.Errorf("error reading part: %v\nread so far: %q", err, buf.String()) 414 } 415 if n <= 0 { 416 t.Errorf("read %d bytes; expected >0", n) 417 } 418 } 419 } 420 421 func TestQuotedPrintableEncoding(t *testing.T) { 422 for _, cte := range []string{"quoted-printable", "Quoted-PRINTABLE"} { 423 t.Run(cte, func(t *testing.T) { 424 testQuotedPrintableEncoding(t, cte) 425 }) 426 } 427 } 428 429 func testQuotedPrintableEncoding(t *testing.T, cte string) { 430 // From https://golang.org/issue/4411 431 body := "--0016e68ee29c5d515f04cedf6733\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=text\r\nContent-Transfer-Encoding: " + cte + "\r\n\r\nwords words words words words words words words words words words words wor=\r\nds words words words words words words words words words words words words =\r\nwords words words words words words words words words words words words wor=\r\nds words words words words words words words words words words words words =\r\nwords words words words words words words words words\r\n--0016e68ee29c5d515f04cedf6733\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=submit\r\n\r\nSubmit\r\n--0016e68ee29c5d515f04cedf6733--" 432 r := NewReader(strings.NewReader(body), "0016e68ee29c5d515f04cedf6733") 433 part, err := r.NextPart() 434 if err != nil { 435 t.Fatal(err) 436 } 437 if te, ok := part.Header["Content-Transfer-Encoding"]; ok { 438 t.Errorf("unexpected Content-Transfer-Encoding of %q", te) 439 } 440 var buf bytes.Buffer 441 _, err = io.Copy(&buf, part) 442 if err != nil { 443 t.Error(err) 444 } 445 got := buf.String() 446 want := "words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words words" 447 if got != want { 448 t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want) 449 } 450 } 451 452 // Test parsing an image attachment from gmail, which previously failed. 453 func TestNested(t *testing.T) { 454 // nested-mime is the body part of a multipart/mixed email 455 // with boundary e89a8ff1c1e83553e304be640612 456 f, err := os.Open("testdata/nested-mime") 457 if err != nil { 458 t.Fatal(err) 459 } 460 defer f.Close() 461 mr := NewReader(f, "e89a8ff1c1e83553e304be640612") 462 p, err := mr.NextPart() 463 if err != nil { 464 t.Fatalf("error reading first section (alternative): %v", err) 465 } 466 467 // Read the inner text/plain and text/html sections of the multipart/alternative. 468 mr2 := NewReader(p, "e89a8ff1c1e83553e004be640610") 469 p, err = mr2.NextPart() 470 if err != nil { 471 t.Fatalf("reading text/plain part: %v", err) 472 } 473 if b, err := ioutil.ReadAll(p); string(b) != "*body*\r\n" || err != nil { 474 t.Fatalf("reading text/plain part: got %q, %v", b, err) 475 } 476 p, err = mr2.NextPart() 477 if err != nil { 478 t.Fatalf("reading text/html part: %v", err) 479 } 480 if b, err := ioutil.ReadAll(p); string(b) != "<b>body</b>\r\n" || err != nil { 481 t.Fatalf("reading text/html part: got %q, %v", b, err) 482 } 483 484 p, err = mr2.NextPart() 485 if err != io.EOF { 486 t.Fatalf("final inner NextPart = %v; want io.EOF", err) 487 } 488 489 // Back to the outer multipart/mixed, reading the image attachment. 490 _, err = mr.NextPart() 491 if err != nil { 492 t.Fatalf("error reading the image attachment at the end: %v", err) 493 } 494 495 _, err = mr.NextPart() 496 if err != io.EOF { 497 t.Fatalf("final outer NextPart = %v; want io.EOF", err) 498 } 499 } 500 501 type headerBody struct { 502 header textproto.MIMEHeader 503 body string 504 } 505 506 func formData(key, value string) headerBody { 507 return headerBody{ 508 textproto.MIMEHeader{ 509 "Content-Type": {"text/plain; charset=ISO-8859-1"}, 510 "Content-Disposition": {"form-data; name=" + key}, 511 }, 512 value, 513 } 514 } 515 516 type parseTest struct { 517 name string 518 in, sep string 519 want []headerBody 520 } 521 522 var parseTests = []parseTest{ 523 // Actual body from App Engine on a blob upload. The final part (the 524 // Content-Type: message/external-body) is what App Engine replaces 525 // the uploaded file with. The other form fields (prefixed with 526 // "other" in their form-data name) are unchanged. A bug was 527 // reported with blob uploads failing when the other fields were 528 // empty. This was the MIME POST body that previously failed. 529 { 530 name: "App Engine post", 531 sep: "00151757727e9583fd04bfbca4c6", 532 in: "--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherEmpty1\r\n\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherFoo1\r\n\r\nfoo\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherFoo2\r\n\r\nfoo\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherEmpty2\r\n\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherRepeatFoo\r\n\r\nfoo\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherRepeatFoo\r\n\r\nfoo\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherRepeatEmpty\r\n\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=otherRepeatEmpty\r\n\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=submit\r\n\r\nSubmit\r\n--00151757727e9583fd04bfbca4c6\r\nContent-Type: message/external-body; charset=ISO-8859-1; blob-key=AHAZQqG84qllx7HUqO_oou5EvdYQNS3Mbbkb0RjjBoM_Kc1UqEN2ygDxWiyCPulIhpHRPx-VbpB6RX4MrsqhWAi_ZxJ48O9P2cTIACbvATHvg7IgbvZytyGMpL7xO1tlIvgwcM47JNfv_tGhy1XwyEUO8oldjPqg5Q\r\nContent-Disposition: form-data; name=file; filename=\"fall.png\"\r\n\r\nContent-Type: image/png\r\nContent-Length: 232303\r\nX-AppEngine-Upload-Creation: 2012-05-10 23:14:02.715173\r\nContent-MD5: MzRjODU1ZDZhZGU1NmRlOWEwZmMwMDdlODBmZTA0NzA=\r\nContent-Disposition: form-data; name=file; filename=\"fall.png\"\r\n\r\n\r\n--00151757727e9583fd04bfbca4c6--", 533 want: []headerBody{ 534 formData("otherEmpty1", ""), 535 formData("otherFoo1", "foo"), 536 formData("otherFoo2", "foo"), 537 formData("otherEmpty2", ""), 538 formData("otherRepeatFoo", "foo"), 539 formData("otherRepeatFoo", "foo"), 540 formData("otherRepeatEmpty", ""), 541 formData("otherRepeatEmpty", ""), 542 formData("submit", "Submit"), 543 {textproto.MIMEHeader{ 544 "Content-Type": {"message/external-body; charset=ISO-8859-1; blob-key=AHAZQqG84qllx7HUqO_oou5EvdYQNS3Mbbkb0RjjBoM_Kc1UqEN2ygDxWiyCPulIhpHRPx-VbpB6RX4MrsqhWAi_ZxJ48O9P2cTIACbvATHvg7IgbvZytyGMpL7xO1tlIvgwcM47JNfv_tGhy1XwyEUO8oldjPqg5Q"}, 545 "Content-Disposition": {"form-data; name=file; filename=\"fall.png\""}, 546 }, "Content-Type: image/png\r\nContent-Length: 232303\r\nX-AppEngine-Upload-Creation: 2012-05-10 23:14:02.715173\r\nContent-MD5: MzRjODU1ZDZhZGU1NmRlOWEwZmMwMDdlODBmZTA0NzA=\r\nContent-Disposition: form-data; name=file; filename=\"fall.png\"\r\n\r\n"}, 547 }, 548 }, 549 550 // Single empty part, ended with --boundary immediately after headers. 551 { 552 name: "single empty part, --boundary", 553 sep: "abc", 554 in: "--abc\r\nFoo: bar\r\n\r\n--abc--", 555 want: []headerBody{ 556 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 557 }, 558 }, 559 560 // Single empty part, ended with \r\n--boundary immediately after headers. 561 { 562 name: "single empty part, \r\n--boundary", 563 sep: "abc", 564 in: "--abc\r\nFoo: bar\r\n\r\n\r\n--abc--", 565 want: []headerBody{ 566 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 567 }, 568 }, 569 570 // Final part empty. 571 { 572 name: "final part empty", 573 sep: "abc", 574 in: "--abc\r\nFoo: bar\r\n\r\n--abc\r\nFoo2: bar2\r\n\r\n--abc--", 575 want: []headerBody{ 576 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 577 {textproto.MIMEHeader{"Foo2": {"bar2"}}, ""}, 578 }, 579 }, 580 581 // Final part empty with newlines after final separator. 582 { 583 name: "final part empty then crlf", 584 sep: "abc", 585 in: "--abc\r\nFoo: bar\r\n\r\n--abc--\r\n", 586 want: []headerBody{ 587 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 588 }, 589 }, 590 591 // Final part empty with lwsp-chars after final separator. 592 { 593 name: "final part empty then lwsp", 594 sep: "abc", 595 in: "--abc\r\nFoo: bar\r\n\r\n--abc-- \t", 596 want: []headerBody{ 597 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 598 }, 599 }, 600 601 // No parts (empty form as submitted by Chrome) 602 { 603 name: "no parts", 604 sep: "----WebKitFormBoundaryQfEAfzFOiSemeHfA", 605 in: "------WebKitFormBoundaryQfEAfzFOiSemeHfA--\r\n", 606 want: []headerBody{}, 607 }, 608 609 // Part containing data starting with the boundary, but with additional suffix. 610 { 611 name: "fake separator as data", 612 sep: "sep", 613 in: "--sep\r\nFoo: bar\r\n\r\n--sepFAKE\r\n--sep--", 614 want: []headerBody{ 615 {textproto.MIMEHeader{"Foo": {"bar"}}, "--sepFAKE"}, 616 }, 617 }, 618 619 // Part containing a boundary with whitespace following it. 620 { 621 name: "boundary with whitespace", 622 sep: "sep", 623 in: "--sep \r\nFoo: bar\r\n\r\ntext\r\n--sep--", 624 want: []headerBody{ 625 {textproto.MIMEHeader{"Foo": {"bar"}}, "text"}, 626 }, 627 }, 628 629 // With ignored leading line. 630 { 631 name: "leading line", 632 sep: "MyBoundary", 633 in: strings.Replace(`This is a multi-part message. This line is ignored. 634 --MyBoundary 635 foo: bar 636 637 638 --MyBoundary--`, "\n", "\r\n", -1), 639 want: []headerBody{ 640 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 641 }, 642 }, 643 644 // Issue 10616; minimal 645 { 646 name: "issue 10616 minimal", 647 sep: "sep", 648 in: "--sep \r\nFoo: bar\r\n\r\n" + 649 "a\r\n" + 650 "--sep_alt\r\n" + 651 "b\r\n" + 652 "\r\n--sep--", 653 want: []headerBody{ 654 {textproto.MIMEHeader{"Foo": {"bar"}}, "a\r\n--sep_alt\r\nb\r\n"}, 655 }, 656 }, 657 658 // Issue 10616; full example from bug. 659 { 660 name: "nested separator prefix is outer separator", 661 sep: "----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9", 662 in: strings.Replace(`------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9 663 Content-Type: multipart/alternative; boundary="----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt" 664 665 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 666 Content-Type: text/plain; charset="utf-8" 667 Content-Transfer-Encoding: 8bit 668 669 This is a multi-part message in MIME format. 670 671 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 672 Content-Type: text/html; charset="utf-8" 673 Content-Transfer-Encoding: 8bit 674 675 html things 676 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt-- 677 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9--`, "\n", "\r\n", -1), 678 want: []headerBody{ 679 {textproto.MIMEHeader{"Content-Type": {`multipart/alternative; boundary="----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt"`}}, 680 strings.Replace(`------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 681 Content-Type: text/plain; charset="utf-8" 682 Content-Transfer-Encoding: 8bit 683 684 This is a multi-part message in MIME format. 685 686 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 687 Content-Type: text/html; charset="utf-8" 688 Content-Transfer-Encoding: 8bit 689 690 html things 691 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt--`, "\n", "\r\n", -1), 692 }, 693 }, 694 }, 695 // Issue 12662: Check that we don't consume the leading \r if the peekBuffer 696 // ends in '\r\n--separator-' 697 { 698 name: "peek buffer boundary condition", 699 sep: "00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db", 700 in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db 701 Content-Disposition: form-data; name="block"; filename="block" 702 Content-Type: application/octet-stream 703 704 `+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1), 705 want: []headerBody{ 706 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}}, 707 strings.Repeat("A", peekBufferSize-65), 708 }, 709 }, 710 }, 711 // Issue 12662: Same test as above with \r\n at the end 712 { 713 name: "peek buffer boundary condition", 714 sep: "00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db", 715 in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db 716 Content-Disposition: form-data; name="block"; filename="block" 717 Content-Type: application/octet-stream 718 719 `+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--\n", "\n", "\r\n", -1), 720 want: []headerBody{ 721 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}}, 722 strings.Repeat("A", peekBufferSize-65), 723 }, 724 }, 725 }, 726 // Issue 12662v2: We want to make sure that for short buffers that end with 727 // '\r\n--separator-' we always consume at least one (valid) symbol from the 728 // peekBuffer 729 { 730 name: "peek buffer boundary condition", 731 sep: "aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db", 732 in: strings.Replace(`--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db 733 Content-Disposition: form-data; name="block"; filename="block" 734 Content-Type: application/octet-stream 735 736 `+strings.Repeat("A", peekBufferSize)+"\n--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1), 737 want: []headerBody{ 738 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}}, 739 strings.Repeat("A", peekBufferSize), 740 }, 741 }, 742 }, 743 // Context: https://github.com/camlistore/camlistore/issues/642 744 // If the file contents in the form happens to have a size such as: 745 // size = peekBufferSize - (len("\n--") + len(boundary) + len("\r") + 1), (modulo peekBufferSize) 746 // then peekBufferSeparatorIndex was wrongly returning (-1, false), which was leading to an nCopy 747 // cut such as: 748 // "somedata\r| |\n--Boundary\r" (instead of "somedata| |\r\n--Boundary\r"), which was making the 749 // subsequent Read miss the boundary. 750 { 751 name: "safeCount off by one", 752 sep: "08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74", 753 in: strings.Replace(`--08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74 754 Content-Disposition: form-data; name="myfile"; filename="my-file.txt" 755 Content-Type: application/octet-stream 756 757 `, "\n", "\r\n", -1) + 758 strings.Repeat("A", peekBufferSize-(len("\n--")+len("08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74")+len("\r")+1)) + 759 strings.Replace(` 760 --08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74 761 Content-Disposition: form-data; name="key" 762 763 val 764 --08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74-- 765 `, "\n", "\r\n", -1), 766 want: []headerBody{ 767 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="myfile"; filename="my-file.txt"`}}, 768 strings.Repeat("A", peekBufferSize-(len("\n--")+len("08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74")+len("\r")+1)), 769 }, 770 {textproto.MIMEHeader{"Content-Disposition": {`form-data; name="key"`}}, 771 "val", 772 }, 773 }, 774 }, 775 776 roundTripParseTest(), 777 } 778 779 func TestParse(t *testing.T) { 780 Cases: 781 for _, tt := range parseTests { 782 r := NewReader(strings.NewReader(tt.in), tt.sep) 783 got := []headerBody{} 784 for { 785 p, err := r.NextPart() 786 if err == io.EOF { 787 break 788 } 789 if err != nil { 790 t.Errorf("in test %q, NextPart: %v", tt.name, err) 791 continue Cases 792 } 793 pbody, err := ioutil.ReadAll(p) 794 if err != nil { 795 t.Errorf("in test %q, error reading part: %v", tt.name, err) 796 continue Cases 797 } 798 got = append(got, headerBody{p.Header, string(pbody)}) 799 } 800 if !reflect.DeepEqual(tt.want, got) { 801 t.Errorf("test %q:\n got: %v\nwant: %v", tt.name, got, tt.want) 802 if len(tt.want) != len(got) { 803 t.Errorf("test %q: got %d parts, want %d", tt.name, len(got), len(tt.want)) 804 } else if len(got) > 1 { 805 for pi, wantPart := range tt.want { 806 if !reflect.DeepEqual(wantPart, got[pi]) { 807 t.Errorf("test %q, part %d:\n got: %v\nwant: %v", tt.name, pi, got[pi], wantPart) 808 } 809 } 810 } 811 } 812 } 813 } 814 815 func partsFromReader(r *Reader) ([]headerBody, error) { 816 got := []headerBody{} 817 for { 818 p, err := r.NextPart() 819 if err == io.EOF { 820 return got, nil 821 } 822 if err != nil { 823 return nil, fmt.Errorf("NextPart: %v", err) 824 } 825 pbody, err := ioutil.ReadAll(p) 826 if err != nil { 827 return nil, fmt.Errorf("error reading part: %v", err) 828 } 829 got = append(got, headerBody{p.Header, string(pbody)}) 830 } 831 } 832 833 func TestParseAllSizes(t *testing.T) { 834 t.Parallel() 835 const maxSize = 5 << 10 836 var buf bytes.Buffer 837 body := strings.Repeat("a", maxSize) 838 bodyb := []byte(body) 839 for size := 0; size < maxSize; size++ { 840 buf.Reset() 841 w := NewWriter(&buf) 842 part, _ := w.CreateFormField("f") 843 part.Write(bodyb[:size]) 844 part, _ = w.CreateFormField("key") 845 part.Write([]byte("val")) 846 w.Close() 847 r := NewReader(&buf, w.Boundary()) 848 got, err := partsFromReader(r) 849 if err != nil { 850 t.Errorf("For size %d: %v", size, err) 851 continue 852 } 853 if len(got) != 2 { 854 t.Errorf("For size %d, num parts = %d; want 2", size, len(got)) 855 continue 856 } 857 if got[0].body != body[:size] { 858 t.Errorf("For size %d, got unexpected len %d: %q", size, len(got[0].body), got[0].body) 859 } 860 } 861 } 862 863 func roundTripParseTest() parseTest { 864 t := parseTest{ 865 name: "round trip", 866 want: []headerBody{ 867 formData("empty", ""), 868 formData("lf", "\n"), 869 formData("cr", "\r"), 870 formData("crlf", "\r\n"), 871 formData("foo", "bar"), 872 }, 873 } 874 var buf bytes.Buffer 875 w := NewWriter(&buf) 876 for _, p := range t.want { 877 pw, err := w.CreatePart(p.header) 878 if err != nil { 879 panic(err) 880 } 881 _, err = pw.Write([]byte(p.body)) 882 if err != nil { 883 panic(err) 884 } 885 } 886 w.Close() 887 t.in = buf.String() 888 t.sep = w.Boundary() 889 return t 890 } 891 892 func TestNoBoundary(t *testing.T) { 893 mr := NewReader(strings.NewReader(""), "") 894 _, err := mr.NextPart() 895 if got, want := fmt.Sprint(err), "multipart: boundary is empty"; got != want { 896 t.Errorf("NextPart error = %v; want %v", got, want) 897 } 898 }