github.com/euank/go@v0.0.0-20160829210321-495514729181/src/mime/multipart/multipart_test.go (about)

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