github.com/megatontech/mynoteforgo@v0.0.0-20200507084910-5d0c6ea6e890/源码/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  }