...
Run Format

Source file src/mime/multipart/multipart_test.go

Documentation: mime/multipart

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

View as plain text