From 90e4ece365cd8220a7efd589556fa7e59c1ca939 Mon Sep 17 00:00:00 2001
From: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri, 15 Apr 2011 16:33:52 -0700
Subject: [PATCH] mime: bunch more tests, few minor parsing fixes

Working towards issue 1119

Using test data from http://greenbytes.de/tech/tc2231/

R=r
CC=golang-dev
https://golang.org/cl/4430049
---
 src/pkg/mime/mediatype.go      | 35 +++++++++++-
 src/pkg/mime/mediatype_test.go | 99 +++++++++++++++++++++++++++++-----
 2 files changed, 120 insertions(+), 14 deletions(-)

diff --git a/src/pkg/mime/mediatype.go b/src/pkg/mime/mediatype.go
index eb629aa6f7..e9e649f950 100644
--- a/src/pkg/mime/mediatype.go
+++ b/src/pkg/mime/mediatype.go
@@ -10,6 +10,24 @@ import (
 	"unicode"
 )
 
+func validMediaTypeOrDisposition(s string) bool {
+	typ, rest := consumeToken(s)
+	if typ == "" {
+		return false
+	}
+	if rest == "" {
+		return true
+	}
+	if !strings.HasPrefix(rest, "/") {
+		return false
+	}
+	subtype, rest := consumeToken(rest[1:])
+	if subtype == "" {
+		return false
+	}
+	return rest == ""
+}
+
 // ParseMediaType parses a media type value and any optional
 // parameters, per RFC 1531.  Media types are the values in
 // Content-Type and Content-Disposition headers (RFC 2183).  On
@@ -22,6 +40,10 @@ func ParseMediaType(v string) (mediatype string, params map[string]string) {
 		i = len(v)
 	}
 	mediatype = strings.TrimSpace(strings.ToLower(v[0:i]))
+	if !validMediaTypeOrDisposition(mediatype) {
+		return "", nil
+	}
+
 	params = make(map[string]string)
 
 	v = v[i:]
@@ -32,6 +54,11 @@ func ParseMediaType(v string) (mediatype string, params map[string]string) {
 		}
 		key, value, rest := consumeMediaParam(v)
 		if key == "" {
+			if strings.TrimSpace(rest) == ";" {
+				// Ignore trailing semicolons.
+				// Not an error.
+				return
+			}
 			// Parse error.
 			return "", nil
 		}
@@ -66,10 +93,12 @@ func consumeToken(v string) (token, rest string) {
 // quoted-string) and the rest of the string.  On failure, returns
 // ("", v).
 func consumeValue(v string) (value, rest string) {
-	if !strings.HasPrefix(v, `"`) {
+	if !strings.HasPrefix(v, `"`) && !strings.HasPrefix(v, `'`) {
 		return consumeToken(v)
 	}
 
+	leadQuote := int(v[0])
+
 	// parse a quoted-string
 	rest = v[1:] // consume the leading quote
 	buffer := new(bytes.Buffer)
@@ -83,7 +112,7 @@ func consumeValue(v string) (value, rest string) {
 			}
 			buffer.WriteRune(rune)
 			nextIsLiteral = false
-		case rune == '"':
+		case rune == leadQuote:
 			return buffer.String(), rest[idx+1:]
 		case IsQText(rune):
 			buffer.WriteRune(rune)
@@ -108,10 +137,12 @@ func consumeMediaParam(v string) (param, value, rest string) {
 	if param == "" {
 		return "", "", v
 	}
+	rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
 	if !strings.HasPrefix(rest, "=") {
 		return "", "", v
 	}
 	rest = rest[1:] // consume equals sign
+	rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
 	value, rest = consumeValue(rest)
 	if value == "" {
 		return "", "", v
diff --git a/src/pkg/mime/mediatype_test.go b/src/pkg/mime/mediatype_test.go
index 4891e899d4..f960315957 100644
--- a/src/pkg/mime/mediatype_test.go
+++ b/src/pkg/mime/mediatype_test.go
@@ -5,6 +5,7 @@
 package mime
 
 import (
+	"reflect"
 	"testing"
 )
 
@@ -85,23 +86,97 @@ func TestConsumeMediaParam(t *testing.T) {
 	}
 }
 
+type mediaTypeTest struct {
+	in string
+	t  string
+	p  map[string]string
+}
+
 func TestParseMediaType(t *testing.T) {
-	tests := [...]string{
-		`form-data; name="foo"`,
-		` form-data ; name=foo`,
-		`FORM-DATA;name="foo"`,
-		` FORM-DATA ; name="foo"`,
-		` FORM-DATA ; name="foo"`,
-		`form-data; key=value;  blah="value";name="foo" `,
+	// Convenience map initializer
+	m := func(s ...string) map[string]string {
+		sm := make(map[string]string)
+		for i := 0; i < len(s); i += 2 {
+			sm[s[i]] = s[i+1]
+		}
+		return sm
+	}
+
+	nameFoo := map[string]string{"name": "foo"}
+	tests := []mediaTypeTest{
+		{`form-data; name="foo"`, "form-data", nameFoo},
+		{` form-data ; name=foo`, "form-data", nameFoo},
+		{`FORM-DATA;name="foo"`, "form-data", nameFoo},
+		{` FORM-DATA ; name="foo"`, "form-data", nameFoo},
+		{` FORM-DATA ; name="foo"`, "form-data", nameFoo},
+
+		{`form-data; key=value;  blah="value";name="foo" `,
+			"form-data",
+			m("key", "value", "blah", "value", "name", "foo")},
+
+		// Tests from http://greenbytes.de/tech/tc2231/
+		// TODO(bradfitz): add the rest of the tests from that site.
+		{`attachment; filename="f\oo.html"`,
+			"attachment",
+			m("filename", "foo.html")},
+		{`attachment; filename="\"quoting\" tested.html"`,
+			"attachment",
+			m("filename", `"quoting" tested.html`)},
+		{`attachment; filename="Here's a semicolon;.html"`,
+			"attachment",
+			m("filename", "Here's a semicolon;.html")},
+		{`attachment; foo="\"\\";filename="foo.html"`,
+			"attachment",
+			m("foo", "\"\\", "filename", "foo.html")},
+		{`attachment; filename=foo.html`,
+			"attachment",
+			m("filename", "foo.html")},
+		{`attachment; filename=foo.html ;`,
+			"attachment",
+			m("filename", "foo.html")},
+		{`attachment; filename='foo.html'`,
+			"attachment",
+			m("filename", "foo.html")},
+		{`attachment; filename="foo-%41.html"`,
+			"attachment",
+			m("filename", "foo-%41.html")},
+		{`attachment; filename="foo-%\41.html"`,
+			"attachment",
+			m("filename", "foo-%41.html")},
+		{`filename=foo.html`,
+			"", m()},
+		{`x=y; filename=foo.html`,
+			"", m()},
+		{`"foo; filename=bar;baz"; filename=qux`,
+			"", m()},
+		{`inline; attachment; filename=foo.html`,
+			"", m()},
+		{`attachment; filename="foo.html".txt`,
+			"", m()},
+		{`attachment; filename="bar`,
+			"", m()},
+		{`attachment; creation-date="Wed, 12 Feb 1997 16:29:51 -0500"`,
+			"attachment",
+			m("creation-date", "Wed, 12 Feb 1997 16:29:51 -0500")},
+		{`foobar`, "foobar", m()},
+		// TODO(bradfitz): rest of them, including RFC2231 encoded UTF-8 and
+		// other charsets.
 	}
 	for _, test := range tests {
-		mt, params := ParseMediaType(test)
-		if mt != "form-data" {
-			t.Errorf("expected type form-data for %s, got [%s]", test, mt)
+		mt, params := ParseMediaType(test.in)
+		if g, e := mt, test.t; g != e {
+			t.Errorf("for input %q, expected type %q, got %q",
+				test.in, e, g)
+			continue
+		}
+		if len(params) == 0 && len(test.p) == 0 {
 			continue
 		}
-		if params["name"] != "foo" {
-			t.Errorf("expected name=foo for %s", test)
+		if !reflect.DeepEqual(params, test.p) {
+			t.Errorf("for input %q, wrong params.\n"+
+				"expected: %#v\n"+
+				"     got: %#v",
+				test.in, test.p, params)
 		}
 	}
 }
-- 
2.30.9