...

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  	"net/textproto"
		13  	"os"
		14  	"reflect"
		15  	"strings"
		16  	"testing"
		17  )
		18  
		19  func TestBoundaryLine(t *testing.T) {
		20  	mr := NewReader(strings.NewReader(""), "myBoundary")
		21  	if !mr.isBoundaryDelimiterLine([]byte("--myBoundary\r\n")) {
		22  		t.Error("expected")
		23  	}
		24  	if !mr.isBoundaryDelimiterLine([]byte("--myBoundary \r\n")) {
		25  		t.Error("expected")
		26  	}
		27  	if !mr.isBoundaryDelimiterLine([]byte("--myBoundary \n")) {
		28  		t.Error("expected")
		29  	}
		30  	if mr.isBoundaryDelimiterLine([]byte("--myBoundary bogus \n")) {
		31  		t.Error("expected fail")
		32  	}
		33  	if mr.isBoundaryDelimiterLine([]byte("--myBoundary bogus--")) {
		34  		t.Error("expected fail")
		35  	}
		36  }
		37  
		38  func escapeString(v string) string {
		39  	bytes, _ := json.Marshal(v)
		40  	return string(bytes)
		41  }
		42  
		43  func expectEq(t *testing.T, expected, actual, what string) {
		44  	if expected == actual {
		45  		return
		46  	}
		47  	t.Errorf("Unexpected value for %s; got %s (len %d) but expected: %s (len %d)",
		48  		what, escapeString(actual), len(actual), escapeString(expected), len(expected))
		49  }
		50  
		51  func TestNameAccessors(t *testing.T) {
		52  	tests := [...][3]string{
		53  		{`form-data; name="foo"`, "foo", ""},
		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 ; filename="foo.txt"; name=foo; baz=quux`, "foo", "foo.txt"},
		60  		{` not-form-data ; filename="bar.txt"; name=foo; baz=quux`, "", "bar.txt"},
		61  	}
		62  	for i, test := range tests {
		63  		p := &Part{Header: make(map[string][]string)}
		64  		p.Header.Set("Content-Disposition", test[0])
		65  		if g, e := p.FormName(), test[1]; g != e {
		66  			t.Errorf("test %d: FormName() = %q; want %q", i, g, e)
		67  		}
		68  		if g, e := p.FileName(), test[2]; g != e {
		69  			t.Errorf("test %d: FileName() = %q; want %q", i, g, e)
		70  		}
		71  	}
		72  }
		73  
		74  var longLine = strings.Repeat("\n\n\r\r\r\n\r\000", (1<<20)/8)
		75  
		76  func testMultipartBody(sep string) string {
		77  	testBody := `
		78  This is a multi-part message.	This line is ignored.
		79  --MyBoundary
		80  Header1: value1
		81  HEADER2: value2
		82  foo-bar: baz
		83  
		84  My value
		85  The end.
		86  --MyBoundary
		87  name: bigsection
		88  
		89  [longline]
		90  --MyBoundary
		91  Header1: value1b
		92  HEADER2: value2b
		93  foo-bar: bazb
		94  
		95  Line 1
		96  Line 2
		97  Line 3 ends in a newline, but just one.
		98  
		99  --MyBoundary
	 100  
	 101  never read data
	 102  --MyBoundary--
	 103  
	 104  
	 105  useless trailer
	 106  `
	 107  	testBody = strings.ReplaceAll(testBody, "\n", sep)
	 108  	return strings.Replace(testBody, "[longline]", longLine, 1)
	 109  }
	 110  
	 111  func TestMultipart(t *testing.T) {
	 112  	bodyReader := strings.NewReader(testMultipartBody("\r\n"))
	 113  	testMultipart(t, bodyReader, false)
	 114  }
	 115  
	 116  func TestMultipartOnlyNewlines(t *testing.T) {
	 117  	bodyReader := strings.NewReader(testMultipartBody("\n"))
	 118  	testMultipart(t, bodyReader, true)
	 119  }
	 120  
	 121  func TestMultipartSlowInput(t *testing.T) {
	 122  	bodyReader := strings.NewReader(testMultipartBody("\r\n"))
	 123  	testMultipart(t, &slowReader{bodyReader}, false)
	 124  }
	 125  
	 126  func testMultipart(t *testing.T, r io.Reader, onlyNewlines bool) {
	 127  	t.Parallel()
	 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.ReplaceAll(s, "\r\n", "\n")
	 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.ReplaceAll(testBody, "\n", "\r\n")
	 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(io.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  type sentinelReader struct {
	 327  	// done is closed when this reader is read from.
	 328  	done chan struct{}
	 329  }
	 330  
	 331  func (s *sentinelReader) Read([]byte) (int, error) {
	 332  	if s.done != nil {
	 333  		close(s.done)
	 334  		s.done = nil
	 335  	}
	 336  	return 0, io.EOF
	 337  }
	 338  
	 339  // TestMultipartStreamReadahead tests that PartReader does not block
	 340  // on reading past the end of a part, ensuring that it can be used on
	 341  // a stream like multipart/x-mixed-replace. See golang.org/issue/15431
	 342  func TestMultipartStreamReadahead(t *testing.T) {
	 343  	testBody1 := `
	 344  This is a multi-part message.	This line is ignored.
	 345  --MyBoundary
	 346  foo-bar: baz
	 347  
	 348  Body
	 349  --MyBoundary
	 350  `
	 351  	testBody2 := `foo-bar: bop
	 352  
	 353  Body 2
	 354  --MyBoundary--
	 355  `
	 356  	done1 := make(chan struct{})
	 357  	reader := NewReader(
	 358  		io.MultiReader(
	 359  			strings.NewReader(testBody1),
	 360  			&sentinelReader{done1},
	 361  			strings.NewReader(testBody2)),
	 362  		"MyBoundary")
	 363  
	 364  	var i int
	 365  	readPart := func(hdr textproto.MIMEHeader, body string) {
	 366  		part, err := reader.NextPart()
	 367  		if part == nil || err != nil {
	 368  			t.Fatalf("Part %d: NextPart failed: %v", i, err)
	 369  		}
	 370  
	 371  		if !reflect.DeepEqual(part.Header, hdr) {
	 372  			t.Errorf("Part %d: part.Header = %v, want %v", i, part.Header, hdr)
	 373  		}
	 374  		data, err := io.ReadAll(part)
	 375  		expectEq(t, body, string(data), fmt.Sprintf("Part %d body", i))
	 376  		if err != nil {
	 377  			t.Fatalf("Part %d: ReadAll failed: %v", i, err)
	 378  		}
	 379  		i++
	 380  	}
	 381  
	 382  	readPart(textproto.MIMEHeader{"Foo-Bar": {"baz"}}, "Body")
	 383  
	 384  	select {
	 385  	case <-done1:
	 386  		t.Errorf("Reader read past second boundary")
	 387  	default:
	 388  	}
	 389  
	 390  	readPart(textproto.MIMEHeader{"Foo-Bar": {"bop"}}, "Body 2")
	 391  }
	 392  
	 393  func TestLineContinuation(t *testing.T) {
	 394  	// This body, extracted from an email, contains headers that span multiple
	 395  	// lines.
	 396  
	 397  	// TODO: The original mail ended with a double-newline before the
	 398  	// final delimiter; this was manually edited to use a CRLF.
	 399  	testBody :=
	 400  		"\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"
	 401  
	 402  	r := NewReader(strings.NewReader(testBody), "Apple-Mail-2-292336769")
	 403  
	 404  	for i := 0; i < 2; i++ {
	 405  		part, err := r.NextPart()
	 406  		if err != nil {
	 407  			t.Fatalf("didn't get a part")
	 408  		}
	 409  		var buf bytes.Buffer
	 410  		n, err := io.Copy(&buf, part)
	 411  		if err != nil {
	 412  			t.Errorf("error reading part: %v\nread so far: %q", err, buf.String())
	 413  		}
	 414  		if n <= 0 {
	 415  			t.Errorf("read %d bytes; expected >0", n)
	 416  		}
	 417  	}
	 418  }
	 419  
	 420  func TestQuotedPrintableEncoding(t *testing.T) {
	 421  	for _, cte := range []string{"quoted-printable", "Quoted-PRINTABLE"} {
	 422  		t.Run(cte, func(t *testing.T) {
	 423  			testQuotedPrintableEncoding(t, cte)
	 424  		})
	 425  	}
	 426  }
	 427  
	 428  func testQuotedPrintableEncoding(t *testing.T, cte string) {
	 429  	// From https://golang.org/issue/4411
	 430  	body := "--0016e68ee29c5d515f04cedf6733\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=text\r\nContent-Transfer-Encoding: " + cte + "\r\n\r\nwords words words words words words words words words words words words wor=\r\nds words words words words words words words words words words words words =\r\nwords words words words words words words words words words words words wor=\r\nds words words words words words words words words words words words words =\r\nwords words words words words words words words words\r\n--0016e68ee29c5d515f04cedf6733\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Disposition: form-data; name=submit\r\n\r\nSubmit\r\n--0016e68ee29c5d515f04cedf6733--"
	 431  	r := NewReader(strings.NewReader(body), "0016e68ee29c5d515f04cedf6733")
	 432  	part, err := r.NextPart()
	 433  	if err != nil {
	 434  		t.Fatal(err)
	 435  	}
	 436  	if te, ok := part.Header["Content-Transfer-Encoding"]; ok {
	 437  		t.Errorf("unexpected Content-Transfer-Encoding of %q", te)
	 438  	}
	 439  	var buf bytes.Buffer
	 440  	_, err = io.Copy(&buf, part)
	 441  	if err != nil {
	 442  		t.Error(err)
	 443  	}
	 444  	got := buf.String()
	 445  	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"
	 446  	if got != want {
	 447  		t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want)
	 448  	}
	 449  }
	 450  
	 451  func TestRawPart(t *testing.T) {
	 452  	// https://github.com/golang/go/issues/29090
	 453  
	 454  	body := strings.Replace(`--0016e68ee29c5d515f04cedf6733
	 455  Content-Type: text/plain; charset="utf-8"
	 456  Content-Transfer-Encoding: quoted-printable
	 457  
	 458  <div dir=3D"ltr">Hello World.</div>
	 459  --0016e68ee29c5d515f04cedf6733
	 460  Content-Type: text/plain; charset="utf-8"
	 461  Content-Transfer-Encoding: quoted-printable
	 462  
	 463  <div dir=3D"ltr">Hello World.</div>
	 464  --0016e68ee29c5d515f04cedf6733--`, "\n", "\r\n", -1)
	 465  
	 466  	r := NewReader(strings.NewReader(body), "0016e68ee29c5d515f04cedf6733")
	 467  
	 468  	// This part is expected to be raw, bypassing the automatic handling
	 469  	// of quoted-printable.
	 470  	part, err := r.NextRawPart()
	 471  	if err != nil {
	 472  		t.Fatal(err)
	 473  	}
	 474  	if _, ok := part.Header["Content-Transfer-Encoding"]; !ok {
	 475  		t.Errorf("missing Content-Transfer-Encoding")
	 476  	}
	 477  	var buf bytes.Buffer
	 478  	_, err = io.Copy(&buf, part)
	 479  	if err != nil {
	 480  		t.Error(err)
	 481  	}
	 482  	got := buf.String()
	 483  	// Data is still quoted-printable.
	 484  	want := `<div dir=3D"ltr">Hello World.</div>`
	 485  	if got != want {
	 486  		t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want)
	 487  	}
	 488  
	 489  	// This part is expected to have automatic decoding of quoted-printable.
	 490  	part, err = r.NextPart()
	 491  	if err != nil {
	 492  		t.Fatal(err)
	 493  	}
	 494  	if te, ok := part.Header["Content-Transfer-Encoding"]; ok {
	 495  		t.Errorf("unexpected Content-Transfer-Encoding of %q", te)
	 496  	}
	 497  
	 498  	buf.Reset()
	 499  	_, err = io.Copy(&buf, part)
	 500  	if err != nil {
	 501  		t.Error(err)
	 502  	}
	 503  	got = buf.String()
	 504  	// QP data has been decoded.
	 505  	want = `<div dir="ltr">Hello World.</div>`
	 506  	if got != want {
	 507  		t.Errorf("wrong part value:\n got: %q\nwant: %q", got, want)
	 508  	}
	 509  }
	 510  
	 511  // Test parsing an image attachment from gmail, which previously failed.
	 512  func TestNested(t *testing.T) {
	 513  	// nested-mime is the body part of a multipart/mixed email
	 514  	// with boundary e89a8ff1c1e83553e304be640612
	 515  	f, err := os.Open("testdata/nested-mime")
	 516  	if err != nil {
	 517  		t.Fatal(err)
	 518  	}
	 519  	defer f.Close()
	 520  	mr := NewReader(f, "e89a8ff1c1e83553e304be640612")
	 521  	p, err := mr.NextPart()
	 522  	if err != nil {
	 523  		t.Fatalf("error reading first section (alternative): %v", err)
	 524  	}
	 525  
	 526  	// Read the inner text/plain and text/html sections of the multipart/alternative.
	 527  	mr2 := NewReader(p, "e89a8ff1c1e83553e004be640610")
	 528  	p, err = mr2.NextPart()
	 529  	if err != nil {
	 530  		t.Fatalf("reading text/plain part: %v", err)
	 531  	}
	 532  	if b, err := io.ReadAll(p); string(b) != "*body*\r\n" || err != nil {
	 533  		t.Fatalf("reading text/plain part: got %q, %v", b, err)
	 534  	}
	 535  	p, err = mr2.NextPart()
	 536  	if err != nil {
	 537  		t.Fatalf("reading text/html part: %v", err)
	 538  	}
	 539  	if b, err := io.ReadAll(p); string(b) != "<b>body</b>\r\n" || err != nil {
	 540  		t.Fatalf("reading text/html part: got %q, %v", b, err)
	 541  	}
	 542  
	 543  	p, err = mr2.NextPart()
	 544  	if err != io.EOF {
	 545  		t.Fatalf("final inner NextPart = %v; want io.EOF", err)
	 546  	}
	 547  
	 548  	// Back to the outer multipart/mixed, reading the image attachment.
	 549  	_, err = mr.NextPart()
	 550  	if err != nil {
	 551  		t.Fatalf("error reading the image attachment at the end: %v", err)
	 552  	}
	 553  
	 554  	_, err = mr.NextPart()
	 555  	if err != io.EOF {
	 556  		t.Fatalf("final outer NextPart = %v; want io.EOF", err)
	 557  	}
	 558  }
	 559  
	 560  type headerBody struct {
	 561  	header textproto.MIMEHeader
	 562  	body	 string
	 563  }
	 564  
	 565  func formData(key, value string) headerBody {
	 566  	return headerBody{
	 567  		textproto.MIMEHeader{
	 568  			"Content-Type":				{"text/plain; charset=ISO-8859-1"},
	 569  			"Content-Disposition": {"form-data; name=" + key},
	 570  		},
	 571  		value,
	 572  	}
	 573  }
	 574  
	 575  type parseTest struct {
	 576  	name		string
	 577  	in, sep string
	 578  	want		[]headerBody
	 579  }
	 580  
	 581  var parseTests = []parseTest{
	 582  	// Actual body from App Engine on a blob upload. The final part (the
	 583  	// Content-Type: message/external-body) is what App Engine replaces
	 584  	// the uploaded file with. The other form fields (prefixed with
	 585  	// "other" in their form-data name) are unchanged. A bug was
	 586  	// reported with blob uploads failing when the other fields were
	 587  	// empty. This was the MIME POST body that previously failed.
	 588  	{
	 589  		name: "App Engine post",
	 590  		sep:	"00151757727e9583fd04bfbca4c6",
	 591  		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--",
	 592  		want: []headerBody{
	 593  			formData("otherEmpty1", ""),
	 594  			formData("otherFoo1", "foo"),
	 595  			formData("otherFoo2", "foo"),
	 596  			formData("otherEmpty2", ""),
	 597  			formData("otherRepeatFoo", "foo"),
	 598  			formData("otherRepeatFoo", "foo"),
	 599  			formData("otherRepeatEmpty", ""),
	 600  			formData("otherRepeatEmpty", ""),
	 601  			formData("submit", "Submit"),
	 602  			{textproto.MIMEHeader{
	 603  				"Content-Type":				{"message/external-body; charset=ISO-8859-1; blob-key=AHAZQqG84qllx7HUqO_oou5EvdYQNS3Mbbkb0RjjBoM_Kc1UqEN2ygDxWiyCPulIhpHRPx-VbpB6RX4MrsqhWAi_ZxJ48O9P2cTIACbvATHvg7IgbvZytyGMpL7xO1tlIvgwcM47JNfv_tGhy1XwyEUO8oldjPqg5Q"},
	 604  				"Content-Disposition": {"form-data; name=file; filename=\"fall.png\""},
	 605  			}, "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"},
	 606  		},
	 607  	},
	 608  
	 609  	// Single empty part, ended with --boundary immediately after headers.
	 610  	{
	 611  		name: "single empty part, --boundary",
	 612  		sep:	"abc",
	 613  		in:	 "--abc\r\nFoo: bar\r\n\r\n--abc--",
	 614  		want: []headerBody{
	 615  			{textproto.MIMEHeader{"Foo": {"bar"}}, ""},
	 616  		},
	 617  	},
	 618  
	 619  	// Single empty part, ended with \r\n--boundary immediately after headers.
	 620  	{
	 621  		name: "single empty part, \r\n--boundary",
	 622  		sep:	"abc",
	 623  		in:	 "--abc\r\nFoo: bar\r\n\r\n\r\n--abc--",
	 624  		want: []headerBody{
	 625  			{textproto.MIMEHeader{"Foo": {"bar"}}, ""},
	 626  		},
	 627  	},
	 628  
	 629  	// Final part empty.
	 630  	{
	 631  		name: "final part empty",
	 632  		sep:	"abc",
	 633  		in:	 "--abc\r\nFoo: bar\r\n\r\n--abc\r\nFoo2: bar2\r\n\r\n--abc--",
	 634  		want: []headerBody{
	 635  			{textproto.MIMEHeader{"Foo": {"bar"}}, ""},
	 636  			{textproto.MIMEHeader{"Foo2": {"bar2"}}, ""},
	 637  		},
	 638  	},
	 639  
	 640  	// Final part empty with newlines after final separator.
	 641  	{
	 642  		name: "final part empty then crlf",
	 643  		sep:	"abc",
	 644  		in:	 "--abc\r\nFoo: bar\r\n\r\n--abc--\r\n",
	 645  		want: []headerBody{
	 646  			{textproto.MIMEHeader{"Foo": {"bar"}}, ""},
	 647  		},
	 648  	},
	 649  
	 650  	// Final part empty with lwsp-chars after final separator.
	 651  	{
	 652  		name: "final part empty then lwsp",
	 653  		sep:	"abc",
	 654  		in:	 "--abc\r\nFoo: bar\r\n\r\n--abc-- \t",
	 655  		want: []headerBody{
	 656  			{textproto.MIMEHeader{"Foo": {"bar"}}, ""},
	 657  		},
	 658  	},
	 659  
	 660  	// No parts (empty form as submitted by Chrome)
	 661  	{
	 662  		name: "no parts",
	 663  		sep:	"----WebKitFormBoundaryQfEAfzFOiSemeHfA",
	 664  		in:	 "------WebKitFormBoundaryQfEAfzFOiSemeHfA--\r\n",
	 665  		want: []headerBody{},
	 666  	},
	 667  
	 668  	// Part containing data starting with the boundary, but with additional suffix.
	 669  	{
	 670  		name: "fake separator as data",
	 671  		sep:	"sep",
	 672  		in:	 "--sep\r\nFoo: bar\r\n\r\n--sepFAKE\r\n--sep--",
	 673  		want: []headerBody{
	 674  			{textproto.MIMEHeader{"Foo": {"bar"}}, "--sepFAKE"},
	 675  		},
	 676  	},
	 677  
	 678  	// Part containing a boundary with whitespace following it.
	 679  	{
	 680  		name: "boundary with whitespace",
	 681  		sep:	"sep",
	 682  		in:	 "--sep \r\nFoo: bar\r\n\r\ntext\r\n--sep--",
	 683  		want: []headerBody{
	 684  			{textproto.MIMEHeader{"Foo": {"bar"}}, "text"},
	 685  		},
	 686  	},
	 687  
	 688  	// With ignored leading line.
	 689  	{
	 690  		name: "leading line",
	 691  		sep:	"MyBoundary",
	 692  		in: strings.Replace(`This is a multi-part message.	This line is ignored.
	 693  --MyBoundary
	 694  foo: bar
	 695  
	 696  
	 697  --MyBoundary--`, "\n", "\r\n", -1),
	 698  		want: []headerBody{
	 699  			{textproto.MIMEHeader{"Foo": {"bar"}}, ""},
	 700  		},
	 701  	},
	 702  
	 703  	// Issue 10616; minimal
	 704  	{
	 705  		name: "issue 10616 minimal",
	 706  		sep:	"sep",
	 707  		in: "--sep \r\nFoo: bar\r\n\r\n" +
	 708  			"a\r\n" +
	 709  			"--sep_alt\r\n" +
	 710  			"b\r\n" +
	 711  			"\r\n--sep--",
	 712  		want: []headerBody{
	 713  			{textproto.MIMEHeader{"Foo": {"bar"}}, "a\r\n--sep_alt\r\nb\r\n"},
	 714  		},
	 715  	},
	 716  
	 717  	// Issue 10616; full example from bug.
	 718  	{
	 719  		name: "nested separator prefix is outer separator",
	 720  		sep:	"----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9",
	 721  		in: strings.Replace(`------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9
	 722  Content-Type: multipart/alternative; boundary="----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt"
	 723  
	 724  ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt
	 725  Content-Type: text/plain; charset="utf-8"
	 726  Content-Transfer-Encoding: 8bit
	 727  
	 728  This is a multi-part message in MIME format.
	 729  
	 730  ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt
	 731  Content-Type: text/html; charset="utf-8"
	 732  Content-Transfer-Encoding: 8bit
	 733  
	 734  html things
	 735  ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt--
	 736  ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9--`, "\n", "\r\n", -1),
	 737  		want: []headerBody{
	 738  			{textproto.MIMEHeader{"Content-Type": {`multipart/alternative; boundary="----=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt"`}},
	 739  				strings.Replace(`------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt
	 740  Content-Type: text/plain; charset="utf-8"
	 741  Content-Transfer-Encoding: 8bit
	 742  
	 743  This is a multi-part message in MIME format.
	 744  
	 745  ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt
	 746  Content-Type: text/html; charset="utf-8"
	 747  Content-Transfer-Encoding: 8bit
	 748  
	 749  html things
	 750  ------=_NextPart_4c2fbafd7ec4c8bf08034fe724b608d9_alt--`, "\n", "\r\n", -1),
	 751  			},
	 752  		},
	 753  	},
	 754  	// Issue 12662: Check that we don't consume the leading \r if the peekBuffer
	 755  	// ends in '\r\n--separator-'
	 756  	{
	 757  		name: "peek buffer boundary condition",
	 758  		sep:	"00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db",
	 759  		in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db
	 760  Content-Disposition: form-data; name="block"; filename="block"
	 761  Content-Type: application/octet-stream
	 762  
	 763  `+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1),
	 764  		want: []headerBody{
	 765  			{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}},
	 766  				strings.Repeat("A", peekBufferSize-65),
	 767  			},
	 768  		},
	 769  	},
	 770  	// Issue 12662: Same test as above with \r\n at the end
	 771  	{
	 772  		name: "peek buffer boundary condition",
	 773  		sep:	"00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db",
	 774  		in: strings.Replace(`--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db
	 775  Content-Disposition: form-data; name="block"; filename="block"
	 776  Content-Type: application/octet-stream
	 777  
	 778  `+strings.Repeat("A", peekBufferSize-65)+"\n--00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--\n", "\n", "\r\n", -1),
	 779  		want: []headerBody{
	 780  			{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}},
	 781  				strings.Repeat("A", peekBufferSize-65),
	 782  			},
	 783  		},
	 784  	},
	 785  	// Issue 12662v2: We want to make sure that for short buffers that end with
	 786  	// '\r\n--separator-' we always consume at least one (valid) symbol from the
	 787  	// peekBuffer
	 788  	{
	 789  		name: "peek buffer boundary condition",
	 790  		sep:	"aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db",
	 791  		in: strings.Replace(`--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db
	 792  Content-Disposition: form-data; name="block"; filename="block"
	 793  Content-Type: application/octet-stream
	 794  
	 795  `+strings.Repeat("A", peekBufferSize)+"\n--aaaaaaaaaa00ffded004d4dd0fdf945fbdef9d9050cfd6a13a821846299b27fc71b9db--", "\n", "\r\n", -1),
	 796  		want: []headerBody{
	 797  			{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="block"; filename="block"`}},
	 798  				strings.Repeat("A", peekBufferSize),
	 799  			},
	 800  		},
	 801  	},
	 802  	// Context: https://github.com/camlistore/camlistore/issues/642
	 803  	// If the file contents in the form happens to have a size such as:
	 804  	// size = peekBufferSize - (len("\n--") + len(boundary) + len("\r") + 1), (modulo peekBufferSize)
	 805  	// then peekBufferSeparatorIndex was wrongly returning (-1, false), which was leading to an nCopy
	 806  	// cut such as:
	 807  	// "somedata\r| |\n--Boundary\r" (instead of "somedata| |\r\n--Boundary\r"), which was making the
	 808  	// subsequent Read miss the boundary.
	 809  	{
	 810  		name: "safeCount off by one",
	 811  		sep:	"08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74",
	 812  		in: strings.Replace(`--08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74
	 813  Content-Disposition: form-data; name="myfile"; filename="my-file.txt"
	 814  Content-Type: application/octet-stream
	 815  
	 816  `, "\n", "\r\n", -1) +
	 817  			strings.Repeat("A", peekBufferSize-(len("\n--")+len("08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74")+len("\r")+1)) +
	 818  			strings.Replace(`
	 819  --08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74
	 820  Content-Disposition: form-data; name="key"
	 821  
	 822  val
	 823  --08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74--
	 824  `, "\n", "\r\n", -1),
	 825  		want: []headerBody{
	 826  			{textproto.MIMEHeader{"Content-Type": {`application/octet-stream`}, "Content-Disposition": {`form-data; name="myfile"; filename="my-file.txt"`}},
	 827  				strings.Repeat("A", peekBufferSize-(len("\n--")+len("08b84578eabc563dcba967a945cdf0d9f613864a8f4a716f0e81caa71a74")+len("\r")+1)),
	 828  			},
	 829  			{textproto.MIMEHeader{"Content-Disposition": {`form-data; name="key"`}},
	 830  				"val",
	 831  			},
	 832  		},
	 833  	},
	 834  
	 835  	roundTripParseTest(),
	 836  }
	 837  
	 838  func TestParse(t *testing.T) {
	 839  Cases:
	 840  	for _, tt := range parseTests {
	 841  		r := NewReader(strings.NewReader(tt.in), tt.sep)
	 842  		got := []headerBody{}
	 843  		for {
	 844  			p, err := r.NextPart()
	 845  			if err == io.EOF {
	 846  				break
	 847  			}
	 848  			if err != nil {
	 849  				t.Errorf("in test %q, NextPart: %v", tt.name, err)
	 850  				continue Cases
	 851  			}
	 852  			pbody, err := io.ReadAll(p)
	 853  			if err != nil {
	 854  				t.Errorf("in test %q, error reading part: %v", tt.name, err)
	 855  				continue Cases
	 856  			}
	 857  			got = append(got, headerBody{p.Header, string(pbody)})
	 858  		}
	 859  		if !reflect.DeepEqual(tt.want, got) {
	 860  			t.Errorf("test %q:\n got: %v\nwant: %v", tt.name, got, tt.want)
	 861  			if len(tt.want) != len(got) {
	 862  				t.Errorf("test %q: got %d parts, want %d", tt.name, len(got), len(tt.want))
	 863  			} else if len(got) > 1 {
	 864  				for pi, wantPart := range tt.want {
	 865  					if !reflect.DeepEqual(wantPart, got[pi]) {
	 866  						t.Errorf("test %q, part %d:\n got: %v\nwant: %v", tt.name, pi, got[pi], wantPart)
	 867  					}
	 868  				}
	 869  			}
	 870  		}
	 871  	}
	 872  }
	 873  
	 874  func partsFromReader(r *Reader) ([]headerBody, error) {
	 875  	got := []headerBody{}
	 876  	for {
	 877  		p, err := r.NextPart()
	 878  		if err == io.EOF {
	 879  			return got, nil
	 880  		}
	 881  		if err != nil {
	 882  			return nil, fmt.Errorf("NextPart: %v", err)
	 883  		}
	 884  		pbody, err := io.ReadAll(p)
	 885  		if err != nil {
	 886  			return nil, fmt.Errorf("error reading part: %v", err)
	 887  		}
	 888  		got = append(got, headerBody{p.Header, string(pbody)})
	 889  	}
	 890  }
	 891  
	 892  func TestParseAllSizes(t *testing.T) {
	 893  	t.Parallel()
	 894  	maxSize := 5 << 10
	 895  	if testing.Short() {
	 896  		maxSize = 512
	 897  	}
	 898  	var buf bytes.Buffer
	 899  	body := strings.Repeat("a", maxSize)
	 900  	bodyb := []byte(body)
	 901  	for size := 0; size < maxSize; size++ {
	 902  		buf.Reset()
	 903  		w := NewWriter(&buf)
	 904  		part, _ := w.CreateFormField("f")
	 905  		part.Write(bodyb[:size])
	 906  		part, _ = w.CreateFormField("key")
	 907  		part.Write([]byte("val"))
	 908  		w.Close()
	 909  		r := NewReader(&buf, w.Boundary())
	 910  		got, err := partsFromReader(r)
	 911  		if err != nil {
	 912  			t.Errorf("For size %d: %v", size, err)
	 913  			continue
	 914  		}
	 915  		if len(got) != 2 {
	 916  			t.Errorf("For size %d, num parts = %d; want 2", size, len(got))
	 917  			continue
	 918  		}
	 919  		if got[0].body != body[:size] {
	 920  			t.Errorf("For size %d, got unexpected len %d: %q", size, len(got[0].body), got[0].body)
	 921  		}
	 922  	}
	 923  }
	 924  
	 925  func roundTripParseTest() parseTest {
	 926  	t := parseTest{
	 927  		name: "round trip",
	 928  		want: []headerBody{
	 929  			formData("empty", ""),
	 930  			formData("lf", "\n"),
	 931  			formData("cr", "\r"),
	 932  			formData("crlf", "\r\n"),
	 933  			formData("foo", "bar"),
	 934  		},
	 935  	}
	 936  	var buf bytes.Buffer
	 937  	w := NewWriter(&buf)
	 938  	for _, p := range t.want {
	 939  		pw, err := w.CreatePart(p.header)
	 940  		if err != nil {
	 941  			panic(err)
	 942  		}
	 943  		_, err = pw.Write([]byte(p.body))
	 944  		if err != nil {
	 945  			panic(err)
	 946  		}
	 947  	}
	 948  	w.Close()
	 949  	t.in = buf.String()
	 950  	t.sep = w.Boundary()
	 951  	return t
	 952  }
	 953  
	 954  func TestNoBoundary(t *testing.T) {
	 955  	mr := NewReader(strings.NewReader(""), "")
	 956  	_, err := mr.NextPart()
	 957  	if got, want := fmt.Sprint(err), "multipart: boundary is empty"; got != want {
	 958  		t.Errorf("NextPart error = %v; want %v", got, want)
	 959  	}
	 960  }
	 961  

View as plain text