github.com/c12o16h1/go/src@v0.0.0-20200114212001-5a151c0f00ed/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 func TestRawPart(t *testing.T) { 453 // https://github.com/golang/go/issues/29090 454 455 body := strings.Replace(`--0016e68ee29c5d515f04cedf6733 456 Content-Type: text/plain; charset="utf-8" 457 Content-Transfer-Encoding: quoted-printable 458 459 <div dir=3D"ltr">Hello World.</div> 460 --0016e68ee29c5d515f04cedf6733 461 Content-Type: text/plain; charset="utf-8" 462 Content-Transfer-Encoding: quoted-printable 463 464 <div dir=3D"ltr">Hello World.</div> 465 --0016e68ee29c5d515f04cedf6733--`, "\n", "\r\n", -1) 466 467 r := NewReader(strings.NewReader(body), "0016e68ee29c5d515f04cedf6733") 468 469 // This part is expected to be raw, bypassing the automatic handling 470 // of quoted-printable. 471 part, err := r.NextRawPart() 472 if err != nil { 473 t.Fatal(err) 474 } 475 if _, ok := part.Header["Content-Transfer-Encoding"]; !ok { 476 t.Errorf("missing Content-Transfer-Encoding") 477 } 478 var buf bytes.Buffer 479 _, err = io.Copy(&buf, part) 480 if err != nil { 481 t.Error(err) 482 } 483 got := buf.String() 484 // Data is still quoted-printable. 485 want := `<div dir=3D"ltr">Hello World.</div>` 486 if got != want { 487 t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want) 488 } 489 490 // This part is expected to have automatic decoding of quoted-printable. 491 part, err = r.NextPart() 492 if err != nil { 493 t.Fatal(err) 494 } 495 if te, ok := part.Header["Content-Transfer-Encoding"]; ok { 496 t.Errorf("unexpected Content-Transfer-Encoding of %q", te) 497 } 498 499 buf.Reset() 500 _, err = io.Copy(&buf, part) 501 if err != nil { 502 t.Error(err) 503 } 504 got = buf.String() 505 // QP data has been decoded. 506 want = `<div dir="ltr">Hello World.</div>` 507 if got != want { 508 t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want) 509 } 510 } 511 512 // Test parsing an image attachment from gmail, which previously failed. 513 func TestNested(t *testing.T) { 514 // nested-mime is the body part of a multipart/mixed email 515 // with boundary e89a8ff1c1e83553e304be640612 516 f, err := os.Open("testdata/nested-mime") 517 if err != nil { 518 t.Fatal(err) 519 } 520 defer f.Close() 521 mr := NewReader(f, "e89a8ff1c1e83553e304be640612") 522 p, err := mr.NextPart() 523 if err != nil { 524 t.Fatalf("error reading first section (alternative): %v", err) 525 } 526 527 // Read the inner text/plain and text/html sections of the multipart/alternative. 528 mr2 := NewReader(p, "e89a8ff1c1e83553e004be640610") 529 p, err = mr2.NextPart() 530 if err != nil { 531 t.Fatalf("reading text/plain part: %v", err) 532 } 533 if b, err := ioutil.ReadAll(p); string(b) != "*body*\r\n" || err != nil { 534 t.Fatalf("reading text/plain part: got %q, %v", b, err) 535 } 536 p, err = mr2.NextPart() 537 if err != nil { 538 t.Fatalf("reading text/html part: %v", err) 539 } 540 if b, err := ioutil.ReadAll(p); string(b) != "<b>body</b>\r\n" || err != nil { 541 t.Fatalf("reading text/html part: got %q, %v", b, err) 542 } 543 544 p, err = mr2.NextPart() 545 if err != io.EOF { 546 t.Fatalf("final inner NextPart = %v; want io.EOF", err) 547 } 548 549 // Back to the outer multipart/mixed, reading the image attachment. 550 _, err = mr.NextPart() 551 if err != nil { 552 t.Fatalf("error reading the image attachment at the end: %v", err) 553 } 554 555 _, err = mr.NextPart() 556 if err != io.EOF { 557 t.Fatalf("final outer NextPart = %v; want io.EOF", err) 558 } 559 } 560 561 type headerBody struct { 562 header textproto.MIMEHeader 563 body string 564 } 565 566 func formData(key, value string) headerBody { 567 return headerBody{ 568 textproto.MIMEHeader{ 569 "Content-Type": {"text/plain; charset=ISO-8859-1"}, 570 "Content-Disposition": {"form-data; name=" + key}, 571 }, 572 value, 573 } 574 } 575 576 type parseTest struct { 577 name string 578 in, sep string 579 want []headerBody 580 } 581 582 var parseTests = []parseTest{ 583 // Actual body from App Engine on a blob upload. The final part (the 584 // Content-Type: message/external-body) is what App Engine replaces 585 // the uploaded file with. The other form fields (prefixed with 586 // "other" in their form-data name) are unchanged. A bug was 587 // reported with blob uploads failing when the other fields were 588 // empty. This was the MIME POST body that previously failed. 589 { 590 name: "App Engine post", 591 sep: "00151757727e9583fd04bfbca4c6", 592 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--", 593 want: []headerBody{ 594 formData("otherEmpty1", ""), 595 formData("otherFoo1", "foo"), 596 formData("otherFoo2", "foo"), 597 formData("otherEmpty2", ""), 598 formData("otherRepeatFoo", "foo"), 599 formData("otherRepeatFoo", "foo"), 600 formData("otherRepeatEmpty", ""), 601 formData("otherRepeatEmpty", ""), 602 formData("submit", "Submit"), 603 {textproto.MIMEHeader{ 604 "Content-Type": {"message/external-body; charset=ISO-8859-1; blob-key=AHAZQqG84qllx7HUqO_oou5EvdYQNS3Mbbkb0RjjBoM_Kc1UqEN2ygDxWiyCPulIhpHRPx-VbpB6RX4MrsqhWAi_ZxJ48O9P2cTIACbvATHvg7IgbvZytyGMpL7xO1tlIvgwcM47JNfv_tGhy1XwyEUO8oldjPqg5Q"}, 605 "Content-Disposition": {"form-data; name=file; filename=\"fall.png\""}, 606 }, "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"}, 607 }, 608 }, 609 610 // Single empty part, ended with --boundary immediately after headers. 611 { 612 name: "single empty part, --boundary", 613 sep: "abc", 614 in: "--abc\r\nFoo: bar\r\n\r\n--abc--", 615 want: []headerBody{ 616 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 617 }, 618 }, 619 620 // Single empty part, ended with \r\n--boundary immediately after headers. 621 { 622 name: "single empty part, \r\n--boundary", 623 sep: "abc", 624 in: "--abc\r\nFoo: bar\r\n\r\n\r\n--abc--", 625 want: []headerBody{ 626 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 627 }, 628 }, 629 630 // Final part empty. 631 { 632 name: "final part empty", 633 sep: "abc", 634 in: "--abc\r\nFoo: bar\r\n\r\n--abc\r\nFoo2: bar2\r\n\r\n--abc--", 635 want: []headerBody{ 636 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 637 {textproto.MIMEHeader{"Foo2": {"bar2"}}, ""}, 638 }, 639 }, 640 641 // Final part empty with newlines after final separator. 642 { 643 name: "final part empty then crlf", 644 sep: "abc", 645 in: "--abc\r\nFoo: bar\r\n\r\n--abc--\r\n", 646 want: []headerBody{ 647 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 648 }, 649 }, 650 651 // Final part empty with lwsp-chars after final separator. 652 { 653 name: "final part empty then lwsp", 654 sep: "abc", 655 in: "--abc\r\nFoo: bar\r\n\r\n--abc-- \t", 656 want: []headerBody{ 657 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 658 }, 659 }, 660 661 // No parts (empty form as submitted by Chrome) 662 { 663 name: "no parts", 664 sep: "----WebKitFormBoundaryQfEAfzFOiSemeHfA", 665 in: "------WebKitFormBoundaryQfEAfzFOiSemeHfA--\r\n", 666 want: []headerBody{}, 667 }, 668 669 // Part containing data starting with the boundary, but with additional suffix. 670 { 671 name: "fake separator as data", 672 sep: "sep", 673 in: "--sep\r\nFoo: bar\r\n\r\n--sepFAKE\r\n--sep--", 674 want: []headerBody{ 675 {textproto.MIMEHeader{"Foo": {"bar"}}, "--sepFAKE"}, 676 }, 677 }, 678 679 // Part containing a boundary with whitespace following it. 680 { 681 name: "boundary with whitespace", 682 sep: "sep", 683 in: "--sep \r\nFoo: bar\r\n\r\ntext\r\n--sep--", 684 want: []headerBody{ 685 {textproto.MIMEHeader{"Foo": {"bar"}}, "text"}, 686 }, 687 }, 688 689 // With ignored leading line. 690 { 691 name: "leading line", 692 sep: "MyBoundary", 693 in: strings.Replace(`This is a multi-part message. This line is ignored. 694 --MyBoundary 695 foo: bar 696 697 698 --MyBoundary--`, "\n", "\r\n", -1), 699 want: []headerBody{ 700 {textproto.MIMEHeader{"Foo": {"bar"}}, ""}, 701 }, 702 }, 703 704 // Issue 10616; minimal 705 { 706 name: "issue 10616 minimal", 707 sep: "sep", 708 in: "--sep \r\nFoo: bar\r\n\r\n" + 709 "a\r\n" + 710 "--sep_alt\r\n" + 711 "b\r\n" + 712 "\r\n--sep--", 713 want: []headerBody{ 714 {textproto.MIMEHeader{"Foo": {"bar"}}, "a\r\n--sep_alt\r\nb\r\n"}, 715 }, 716 }, 717 718 // Issue 10616; full example from bug. 719 { 720 name: "nested separator prefix is outer separator", 721 sep: "----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9", 722 in: strings.Replace(`------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9 723 Content-Type: multipart/alternative; boundary="----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt" 724 725 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 726 Content-Type: text/plain; charset="utf-8" 727 Content-Transfer-Encoding: 8bit 728 729 This is a multi-part message in MIME format. 730 731 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 732 Content-Type: text/html; charset="utf-8" 733 Content-Transfer-Encoding: 8bit 734 735 html things 736 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt-- 737 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9--`, "\n", "\r\n", -1), 738 want: []headerBody{ 739 {textproto.MIMEHeader{"Content-Type": {`multipart/alternative; boundary="----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt"`}}, 740 strings.Replace(`------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 741 Content-Type: text/plain; charset="utf-8" 742 Content-Transfer-Encoding: 8bit 743 744 This is a multi-part message in MIME format. 745 746 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt 747 Content-Type: text/html; charset="utf-8" 748 Content-Transfer-Encoding: 8bit 749 750 html things 751 ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt--`, "\n", "\r\n", -1), 752 }, 753 }, 754 }, 755 // Issue 12662: Check that we don't consume the leading \r if the peekBuffer 756 // ends in '\r\n--separator-' 757 { 758 name: "peek buffer boundary condition", 759 sep: "00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db", 760 in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db 761 Content-Disposition: form-data; name="block"; filename="block" 762 Content-Type: application/octet-stream 763 764 `+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1), 765 want: []headerBody{ 766 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}}, 767 strings.Repeat("A", peekBufferSize-65), 768 }, 769 }, 770 }, 771 // Issue 12662: Same test as above with \r\n at the end 772 { 773 name: "peek buffer boundary condition", 774 sep: "00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db", 775 in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db 776 Content-Disposition: form-data; name="block"; filename="block" 777 Content-Type: application/octet-stream 778 779 `+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--\n", "\n", "\r\n", -1), 780 want: []headerBody{ 781 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}}, 782 strings.Repeat("A", peekBufferSize-65), 783 }, 784 }, 785 }, 786 // Issue 12662v2: We want to make sure that for short buffers that end with 787 // '\r\n--separator-' we always consume at least one (valid) symbol from the 788 // peekBuffer 789 { 790 name: "peek buffer boundary condition", 791 sep: "aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db", 792 in: strings.Replace(`--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db 793 Content-Disposition: form-data; name="block"; filename="block" 794 Content-Type: application/octet-stream 795 796 `+strings.Repeat("A", peekBufferSize)+"\n--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1), 797 want: []headerBody{ 798 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}}, 799 strings.Repeat("A", peekBufferSize), 800 }, 801 }, 802 }, 803 // Context: https://github.com/camlistore/camlistore/issues/642 804 // If the file contents in the form happens to have a size such as: 805 // size = peekBufferSize - (len("\n--") + len(boundary) + len("\r") + 1), (modulo peekBufferSize) 806 // then peekBufferSeparatorIndex was wrongly returning (-1, false), which was leading to an nCopy 807 // cut such as: 808 // "somedata\r| |\n--Boundary\r" (instead of "somedata| |\r\n--Boundary\r"), which was making the 809 // subsequent Read miss the boundary. 810 { 811 name: "safeCount off by one", 812 sep: "08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74", 813 in: strings.Replace(`--08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74 814 Content-Disposition: form-data; name="myfile"; filename="my-file.txt" 815 Content-Type: application/octet-stream 816 817 `, "\n", "\r\n", -1) + 818 strings.Repeat("A", peekBufferSize-(len("\n--")+len("08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74")+len("\r")+1)) + 819 strings.Replace(` 820 --08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74 821 Content-Disposition: form-data; name="key" 822 823 val 824 --08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74-- 825 `, "\n", "\r\n", -1), 826 want: []headerBody{ 827 {textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="myfile"; filename="my-file.txt"`}}, 828 strings.Repeat("A", peekBufferSize-(len("\n--")+len("08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74")+len("\r")+1)), 829 }, 830 {textproto.MIMEHeader{"Content-Disposition": {`form-data; name="key"`}}, 831 "val", 832 }, 833 }, 834 }, 835 836 roundTripParseTest(), 837 } 838 839 func TestParse(t *testing.T) { 840 Cases: 841 for _, tt := range parseTests { 842 r := NewReader(strings.NewReader(tt.in), tt.sep) 843 got := []headerBody{} 844 for { 845 p, err := r.NextPart() 846 if err == io.EOF { 847 break 848 } 849 if err != nil { 850 t.Errorf("in test %q, NextPart: %v", tt.name, err) 851 continue Cases 852 } 853 pbody, err := ioutil.ReadAll(p) 854 if err != nil { 855 t.Errorf("in test %q, error reading part: %v", tt.name, err) 856 continue Cases 857 } 858 got = append(got, headerBody{p.Header, string(pbody)}) 859 } 860 if !reflect.DeepEqual(tt.want, got) { 861 t.Errorf("test %q:\n got: %v\nwant: %v", tt.name, got, tt.want) 862 if len(tt.want) != len(got) { 863 t.Errorf("test %q: got %d parts, want %d", tt.name, len(got), len(tt.want)) 864 } else if len(got) > 1 { 865 for pi, wantPart := range tt.want { 866 if !reflect.DeepEqual(wantPart, got[pi]) { 867 t.Errorf("test %q, part %d:\n got: %v\nwant: %v", tt.name, pi, got[pi], wantPart) 868 } 869 } 870 } 871 } 872 } 873 } 874 875 func partsFromReader(r *Reader) ([]headerBody, error) { 876 got := []headerBody{} 877 for { 878 p, err := r.NextPart() 879 if err == io.EOF { 880 return got, nil 881 } 882 if err != nil { 883 return nil, fmt.Errorf("NextPart: %v", err) 884 } 885 pbody, err := ioutil.ReadAll(p) 886 if err != nil { 887 return nil, fmt.Errorf("error reading part: %v", err) 888 } 889 got = append(got, headerBody{p.Header, string(pbody)}) 890 } 891 } 892 893 func TestParseAllSizes(t *testing.T) { 894 t.Parallel() 895 maxSize := 5 << 10 896 if testing.Short() { 897 maxSize = 512 898 } 899 var buf bytes.Buffer 900 body := strings.Repeat("a", maxSize) 901 bodyb := []byte(body) 902 for size := 0; size < maxSize; size++ { 903 buf.Reset() 904 w := NewWriter(&buf) 905 part, _ := w.CreateFormField("f") 906 part.Write(bodyb[:size]) 907 part, _ = w.CreateFormField("key") 908 part.Write([]byte("val")) 909 w.Close() 910 r := NewReader(&buf, w.Boundary()) 911 got, err := partsFromReader(r) 912 if err != nil { 913 t.Errorf("For size %d: %v", size, err) 914 continue 915 } 916 if len(got) != 2 { 917 t.Errorf("For size %d, num parts = %d; want 2", size, len(got)) 918 continue 919 } 920 if got[0].body != body[:size] { 921 t.Errorf("For size %d, got unexpected len %d: %q", size, len(got[0].body), got[0].body) 922 } 923 } 924 } 925 926 func roundTripParseTest() parseTest { 927 t := parseTest{ 928 name: "round trip", 929 want: []headerBody{ 930 formData("empty", ""), 931 formData("lf", "\n"), 932 formData("cr", "\r"), 933 formData("crlf", "\r\n"), 934 formData("foo", "bar"), 935 }, 936 } 937 var buf bytes.Buffer 938 w := NewWriter(&buf) 939 for _, p := range t.want { 940 pw, err := w.CreatePart(p.header) 941 if err != nil { 942 panic(err) 943 } 944 _, err = pw.Write([]byte(p.body)) 945 if err != nil { 946 panic(err) 947 } 948 } 949 w.Close() 950 t.in = buf.String() 951 t.sep = w.Boundary() 952 return t 953 } 954 955 func TestNoBoundary(t *testing.T) { 956 mr := NewReader(strings.NewReader(""), "") 957 _, err := mr.NextPart() 958 if got, want := fmt.Sprint(err), "multipart: boundary is empty"; got != want { 959 t.Errorf("NextPart error = %v; want %v", got, want) 960 } 961 }