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  }