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