github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/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 http://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  	roundTripParseTest(),
   569  }
   570  
   571  func TestParse(t *testing.T) {
   572  Cases:
   573  	for _, tt := range parseTests {
   574  		r := NewReader(strings.NewReader(tt.in), tt.sep)
   575  		got := []headerBody{}
   576  		for {
   577  			p, err := r.NextPart()
   578  			if err == io.EOF {
   579  				break
   580  			}
   581  			if err != nil {
   582  				t.Errorf("in test %q, NextPart: %v", tt.name, err)
   583  				continue Cases
   584  			}
   585  			pbody, err := ioutil.ReadAll(p)
   586  			if err != nil {
   587  				t.Errorf("in test %q, error reading part: %v", tt.name, err)
   588  				continue Cases
   589  			}
   590  			got = append(got, headerBody{p.Header, string(pbody)})
   591  		}
   592  		if !reflect.DeepEqual(tt.want, got) {
   593  			t.Errorf("test %q:\n got: %v\nwant: %v", tt.name, got, tt.want)
   594  			if len(tt.want) != len(got) {
   595  				t.Errorf("test %q: got %d parts, want %d", tt.name, len(got), len(tt.want))
   596  			} else if len(got) > 1 {
   597  				for pi, wantPart := range tt.want {
   598  					if !reflect.DeepEqual(wantPart, got[pi]) {
   599  						t.Errorf("test %q, part %d:\n got: %v\nwant: %v", tt.name, pi, got[pi], wantPart)
   600  					}
   601  				}
   602  			}
   603  		}
   604  	}
   605  }
   606  
   607  func roundTripParseTest() parseTest {
   608  	t := parseTest{
   609  		name: "round trip",
   610  		want: []headerBody{
   611  			formData("empty", ""),
   612  			formData("lf", "\n"),
   613  			formData("cr", "\r"),
   614  			formData("crlf", "\r\n"),
   615  			formData("foo", "bar"),
   616  		},
   617  	}
   618  	var buf bytes.Buffer
   619  	w := NewWriter(&buf)
   620  	for _, p := range t.want {
   621  		pw, err := w.CreatePart(p.header)
   622  		if err != nil {
   623  			panic(err)
   624  		}
   625  		_, err = pw.Write([]byte(p.body))
   626  		if err != nil {
   627  			panic(err)
   628  		}
   629  	}
   630  	w.Close()
   631  	t.in = buf.String()
   632  	t.sep = w.Boundary()
   633  	return t
   634  }