package server import ( "mime" "strings" "testing" ) // Content-Disposition is the header that keeps an uploaded file from being // rendered, so it has to survive whatever a filename throws at it. func TestContentDisposition(t *testing.T) { cases := []struct { name string wantASCII string wantExt bool // expect an RFC 5987 filename* parameter too }{ {"MyGame.zip", "MyGame.zip", false}, {"notes (draft).txt", "notes (draft).txt", false}, {`quo"te.txt`, "quo_te.txt", true}, {`back\slash.txt`, "back_slash.txt", true}, {"naïve.txt", "na_ve.txt", true}, {"日本語.bin", "___.bin", true}, {"🙂.png", "_.png", true}, } for _, c := range cases { got := contentDisposition(c.name) // It must always parse, and always be an attachment. disp, params, err := mime.ParseMediaType(got) if err != nil { t.Errorf("contentDisposition(%q) = %q: does not parse: %v", c.name, got, err) continue } if disp != "attachment" { t.Errorf("contentDisposition(%q): disposition = %q", c.name, disp) } // Whatever the encoding, the decoded filename must be the real one when // an extended parameter is present. if c.wantExt { if !strings.Contains(got, "filename*=UTF-8''") { t.Errorf("contentDisposition(%q) = %q: no RFC 5987 parameter", c.name, got) } if params["filename"] != c.name { t.Errorf("contentDisposition(%q): decoded filename = %q", c.name, params["filename"]) } } else if params["filename"] != c.wantASCII { t.Errorf("contentDisposition(%q): filename = %q, want %q", c.name, params["filename"], c.wantASCII) } // Nothing may break out of the header, in any spelling. if strings.ContainsAny(got, "\r\n") { t.Errorf("contentDisposition(%q) = %q: contains a line break", c.name, got) } if i := strings.Index(got, `filename="`); i >= 0 { quoted := got[i+len(`filename="`):] quoted = quoted[:strings.Index(quoted, `"`)] if strings.ContainsAny(quoted, `"\`) { t.Errorf("contentDisposition(%q): unescaped quoting in %q", c.name, quoted) } } } } func TestASCIIFilenameNeverEmpty(t *testing.T) { for _, in := range []string{"", "...", "___", " ", "🙂"} { if got := asciiFilename(in); strings.Trim(got, "_. ") == "" { t.Errorf("asciiFilename(%q) = %q, which names nothing", in, got) } } } func TestEncodeRFC5987(t *testing.T) { cases := map[string]string{ "plain.txt": "plain.txt", "a b.txt": "a%20b.txt", "naïve": "na%C3%AFve", `q"x`: "q%22x", } for in, want := range cases { if got := encodeRFC5987(in); got != want { t.Errorf("encodeRFC5987(%q) = %q, want %q", in, got, want) } } }