package adminapi import ( "strings" "testing" "github.com/iceBear67/simplepages/api" ) func TestCheckProjectName(t *testing.T) { valid := []string{ "a", "0", "demo", "demo-site", "demo_site", "demo.site", "a1.b-c_d", strings.Repeat("z", 63), } for _, name := range valid { if err := checkProjectName(name); err != nil { t.Errorf("checkProjectName(%q) = %v, want nil", name, err) } } // Everything that could become a path separator, a traversal or a hidden // file once the name is turned into a "~name" entry in $WEBROOT. invalid := []string{ "", ".", "..", "./x", "../x", "/demo", "demo/", "a/b", `a\b`, ".hidden", "-lead", "_lead", "Demo", "DEMO", "démo", "demo ", " demo", "de mo", "demo\t", "demo\n", "demo\x00", "~demo", "demo%2f", strings.Repeat("z", 64), } for _, name := range invalid { err := checkProjectName(name) if err == nil { t.Errorf("checkProjectName(%q) = nil, want an error", name) continue } if got := api.CodeOf(err); got != api.CodeInvalidProjectName { t.Errorf("checkProjectName(%q) code = %q, want %q", name, got, api.CodeInvalidProjectName) } } } func TestCheckSitePath(t *testing.T) { valid := []string{ "index.html", "404.html", "a/b/c.html", "a.b/c", "_next/index.html", "страница.html", strings.Repeat("a", maxSitePathLen), } for _, p := range valid { if err := checkSitePath("index_file", p); err != nil { t.Errorf("checkSitePath(%q) = %v, want nil", p, err) } } invalid := []string{ "", ".", "..", "/index.html", "index.html/", "a//b", "a/./b", "a/../b", "../../etc/passwd", `a\b`, "a\x00b", "a\nb", "a\x7fb", strings.Repeat("a", maxSitePathLen+1), } for _, p := range invalid { err := checkSitePath("index_file", p) if err == nil { t.Errorf("checkSitePath(%q) = nil, want an error", p) continue } if got := api.CodeOf(err); got != api.CodeInvalidPath { t.Errorf("checkSitePath(%q) code = %q, want %q", p, got, api.CodeInvalidPath) } } } func TestCheckHeaderValue(t *testing.T) { valid := []string{ "", "public, max-age=0, must-revalidate", "no-store", "public,\tmax-age=31536000, immutable", strings.Repeat("a", maxCacheControlLen), } for _, v := range valid { if err := checkHeaderValue("cache_control", v); err != nil { t.Errorf("checkHeaderValue(%q) = %v, want nil", v, err) } } // A CR or LF here would let a project append headers of its own to every // response it serves, on an origin it shares with every other project. invalid := []string{ "public\r\nX-Evil: 1", "public\nX-Evil: 1", "public\r", "public\x00", "public\x7f", "public é", strings.Repeat("a", maxCacheControlLen+1), } for _, v := range invalid { if err := checkHeaderValue("cache_control", v); err == nil { t.Errorf("checkHeaderValue(%q) = nil, want an error", v) } } } func TestCheckText(t *testing.T) { // Free text is allowed to be anything printable, including non-ASCII: it is // a display name, not a header value. valid := []string{"", "Demo Site", "デモ", "a — b", strings.Repeat("x", maxDisplayNameLen)} for _, v := range valid { if err := checkText("display_name", v, maxDisplayNameLen); err != nil { t.Errorf("checkText(%q) = %v, want nil", v, err) } } // Control characters are not: these strings are printed to operator // terminals and written into structured logs. invalid := []string{ "a\x1b[31mred", "line\nbreak", "car\rriage", "nul\x00", "del\x7f", "tab\there", strings.Repeat("x", maxDisplayNameLen+1), } for _, v := range invalid { if err := checkText("display_name", v, maxDisplayNameLen); err == nil { t.Errorf("checkText(%q) = nil, want an error", v) } } }