Commit eac59508 authored by Russ Cox's avatar Russ Cox

mime: preserve unnecessary backslash escapes as literals

When MSIE sends a full file path (in "intranet mode"), it does not
escape backslashes: "C:\dev\go\foo.txt", not "C:\\dev\\go\\foo.txt".

No known MIME generators emit unnecessary backslash escapes
for simple token characters like numbers and letters.

If we see an unnecessary backslash escape, assume it is from MSIE
and intended as a literal backslash. This makes Go servers deal better
with MSIE without affecting the way they handle conforming MIME
generators.

Fixes #15664.

Change-Id: Ia3b03b978317d968dc11b2f6de1df913c6bcbfcc
Reviewed-on: https://go-review.googlesource.com/32175Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 2bafbe11
...@@ -248,24 +248,33 @@ func consumeValue(v string) (value, rest string) { ...@@ -248,24 +248,33 @@ func consumeValue(v string) (value, rest string) {
} }
// parse a quoted-string // parse a quoted-string
rest = v[1:] // consume the leading quote
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
var nextIsLiteral bool for i := 1; i < len(v); i++ {
for idx, r := range rest { r := v[i]
switch { if r == '"' {
case nextIsLiteral: return buffer.String(), v[i+1:]
buffer.WriteRune(r) }
nextIsLiteral = false // When MSIE sends a full file path (in "intranet mode"), it does not
case r == '"': // escape backslashes: "C:\dev\go\foo.txt", not "C:\\dev\\go\\foo.txt".
return buffer.String(), rest[idx+1:] //
case r == '\\': // No known MIME generators emit unnecessary backslash escapes
nextIsLiteral = true // for simple token characters like numbers and letters.
case r != '\r' && r != '\n': //
buffer.WriteRune(r) // If we see an unnecessary backslash escape, assume it is from MSIE
default: // and intended as a literal backslash. This makes Go servers deal better
// with MSIE without affecting the way they handle conforming MIME
// generators.
if r == '\\' && i+1 < len(v) && !isTokenChar(rune(v[i+1])) {
buffer.WriteByte(v[i+1])
i++
continue
}
if r == '\r' || r == '\n' {
return "", v return "", v
} }
buffer.WriteByte(v[i])
} }
// Did not find end quote.
return "", v return "", v
} }
......
...@@ -138,10 +138,11 @@ func TestParseMediaType(t *testing.T) { ...@@ -138,10 +138,11 @@ func TestParseMediaType(t *testing.T) {
m("title", "This is even more ***fun*** isn't it!")}, m("title", "This is even more ***fun*** isn't it!")},
// Tests from http://greenbytes.de/tech/tc2231/ // Tests from http://greenbytes.de/tech/tc2231/
// Note: Backslash escape handling is a bit loose, like MSIE.
// TODO(bradfitz): add the rest of the tests from that site. // TODO(bradfitz): add the rest of the tests from that site.
{`attachment; filename="f\oo.html"`, {`attachment; filename="f\oo.html"`,
"attachment", "attachment",
m("filename", "foo.html")}, m("filename", "f\\oo.html")},
{`attachment; filename="\"quoting\" tested.html"`, {`attachment; filename="\"quoting\" tested.html"`,
"attachment", "attachment",
m("filename", `"quoting" tested.html`)}, m("filename", `"quoting" tested.html`)},
...@@ -165,7 +166,7 @@ func TestParseMediaType(t *testing.T) { ...@@ -165,7 +166,7 @@ func TestParseMediaType(t *testing.T) {
m("filename", "foo-%41.html")}, m("filename", "foo-%41.html")},
{`attachment; filename="foo-%\41.html"`, {`attachment; filename="foo-%\41.html"`,
"attachment", "attachment",
m("filename", "foo-%41.html")}, m("filename", "foo-%\\41.html")},
{`filename=foo.html`, {`filename=foo.html`,
"", m()}, "", m()},
{`x=y; filename=foo.html`, {`x=y; filename=foo.html`,
...@@ -220,18 +221,21 @@ func TestParseMediaType(t *testing.T) { ...@@ -220,18 +221,21 @@ func TestParseMediaType(t *testing.T) {
// Empty string used to be mishandled. // Empty string used to be mishandled.
{`foo; bar=""`, "foo", m("bar", "")}, {`foo; bar=""`, "foo", m("bar", "")},
// Microsoft browers in intranet mode do not think they need to escape \ in file name.
{`form-data; name="file"; filename="C:\dev\go\robots.txt"`, "form-data", m("name", "file", "filename", `C:\dev\go\robots.txt`)},
} }
for _, test := range tests { for _, test := range tests {
mt, params, err := ParseMediaType(test.in) mt, params, err := ParseMediaType(test.in)
if err != nil { if err != nil {
if test.t != "" { if test.t != "" {
t.Errorf("for input %q, unexpected error: %v", test.in, err) t.Errorf("for input %#q, unexpected error: %v", test.in, err)
continue continue
} }
continue continue
} }
if g, e := mt, test.t; g != e { if g, e := mt, test.t; g != e {
t.Errorf("for input %q, expected type %q, got %q", t.Errorf("for input %#q, expected type %q, got %q",
test.in, e, g) test.in, e, g)
continue continue
} }
...@@ -239,7 +243,7 @@ func TestParseMediaType(t *testing.T) { ...@@ -239,7 +243,7 @@ func TestParseMediaType(t *testing.T) {
continue continue
} }
if !reflect.DeepEqual(params, test.p) { if !reflect.DeepEqual(params, test.p) {
t.Errorf("for input %q, wrong params.\n"+ t.Errorf("for input %#q, wrong params.\n"+
"expected: %#v\n"+ "expected: %#v\n"+
" got: %#v", " got: %#v",
test.in, test.p, params) test.in, test.p, params)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment