"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
i = len(v)
}
mediatype = strings.TrimSpace(strings.ToLower(v[0:i]))
+ if !validMediaTypeOrDisposition(mediatype) {
+ return "", nil
+ }
+
params = make(map[string]string)
v = v[i:]
}
key, value, rest := consumeMediaParam(v)
if key == "" {
+ if strings.TrimSpace(rest) == ";" {
+ // Ignore trailing semicolons.
+ // Not an error.
+ return
+ }
// Parse error.
return "", nil
}
// 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)
}
buffer.WriteRune(rune)
nextIsLiteral = false
- case rune == '"':
+ case rune == leadQuote:
return buffer.String(), rest[idx+1:]
case IsQText(rune):
buffer.WriteRune(rune)
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
package mime
import (
+ "reflect"
"testing"
)
}
}
+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)
}
}
}