package lib import ( "context" "net/url" "os" "strings" "testing" "time" "github.com/stretchr/testify/require" "golang.org/x/net/webdav" ) // makeListingFS builds an in-memory file system from a map of names to // contents, where a name ending in "/" is a directory. The in-memory file // system accepts names that some platforms reject (e.g. ":" or "<" on // Windows), so hostile names can be tested everywhere. func makeListingFS(t *testing.T, m map[string]string) webdav.FileSystem { ctx := context.Background() fs := webdav.NewMemFS() for name, data := range m { if strings.HasSuffix(name, "/") { require.NoError(t, fs.Mkdir(ctx, "/"+strings.TrimSuffix(name, "/"), 0o755)) continue } f, err := fs.OpenFile(ctx, "/"+name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) require.NoError(t, err) _, err = f.Write([]byte(data)) require.NoError(t, err) require.NoError(t, f.Close()) } return fs } func renderListing(t *testing.T, fs webdav.FileSystem, dirPath string, sorting ListingSortOptions, listing Listing) string { html, err := RenderListing(context.Background(), fs, dirPath, sorting, listing) require.NoError(t, err) return html } var sortByNameAsc = ListingSortOptions{Field: listingSortByName, Order: listingSortAsc} func TestParseListingSortQuery(t *testing.T) { t.Run("defaults", func(t *testing.T) { got := ParseListingSortQuery(url.Values{}) if got.Field != listingSortByDate || got.Order != listingSortDesc { t.Fatalf("unexpected defaults: %+v", got) } }) t.Run("valid values", func(t *testing.T) { got := ParseListingSortQuery(url.Values{"C": {"S"}, "O": {"D"}}) if got.Field != listingSortBySize || got.Order != listingSortDesc { t.Fatalf("unexpected parsed values: %+v", got) } }) t.Run("invalid values fallback", func(t *testing.T) { got := ParseListingSortQuery(url.Values{"C": {"invalid"}, "O": {"nope"}}) if got.Field != listingSortByDate || got.Order != listingSortDesc { t.Fatalf("unexpected fallback values: %+v", got) } }) } func TestRenderDirectoryListingBasic(t *testing.T) { fs := makeListingFS(t, map[string]string{ "file1.txt": "content1", "subdir/": "", }) html := renderListing(t, fs, "/", sortByNameAsc, Listing{ShowPath: true}) if !strings.Contains(html, "") || !strings.Contains(html, ``) { t.Fatal("expected basic html table output") } if !strings.Contains(html, "Index of /") || !strings.Contains(html, "

Index of /

") { t.Fatal("expected index title and heading") } if !strings.Contains(html, `subdir/`) { t.Fatal("expected directory entry") } if !strings.Contains(html, `file1.txt`) { t.Fatal("expected file entry") } if !strings.HasSuffix(html, "\n
\n") { t.Fatal("expected default document closing") } } func TestRenderDirectoryListingHeaderFooter(t *testing.T) { fs := makeListingFS(t, nil) html := renderListing(t, fs, "/", sortByNameAsc, Listing{ Header: "Custom

HEADER

", Footer: "

FOOTER

", ShowPath: true, }) if !strings.HasPrefix(html, "Custom

HEADER

") { t.Fatal("expected header to be rendered unescaped at the start") } if !strings.HasSuffix(html, "

FOOTER

") { t.Fatal("expected footer to be rendered unescaped at the end") } if strings.Count(html, "") != 1 || strings.Count(html, "") != 1 { t.Fatal("expected exactly one html open/close tag when header/footer own the shell") } if strings.Count(html, "") != 1 || strings.Count(html, "") != 1 { t.Fatal("expected exactly one body open/close tag when header/footer own the shell") } if strings.Contains(html, ``) { t.Fatal("expected default shell to be skipped when a custom header is set") } } func TestRenderDirectoryListingSortLinks(t *testing.T) { fs := makeListingFS(t, nil) html := renderListing(t, fs, "/", ListingSortOptions{Field: listingSortByDate, Order: listingSortAsc}, Listing{ShowPath: true}) if !strings.Contains(html, `href="?C=N&O=A"`) { t.Fatal("expected name sort link") } if !strings.Contains(html, `href="?C=M&O=D"`) { t.Fatal("expected toggled date sort link") } if !strings.Contains(html, `href="?C=S&O=A"`) { t.Fatal("expected size sort link") } } func TestRenderDirectoryListingEscapingAndLinks(t *testing.T) { fs := makeListingFS(t, map[string]string{ "a b&c.txt": "x", "mydir/": "", }) html := renderListing(t, fs, "/", sortByNameAsc, Listing{ShowPath: true}) if !strings.Contains(html, `title="a b&c<d>.txt">a b&c<d>.txt`) { t.Fatal("expected escaped display name") } if !strings.Contains(html, `href="a%20b&c%3Cd%3E.txt"`) { t.Fatal("expected url-escaped href") } if !strings.Contains(html, `href="mydir/"`) { t.Fatal("expected trailing slash for directory links") } } func TestRenderDirectoryListingHostileNames(t *testing.T) { fs := makeListingFS(t, map[string]string{ "javascript:alert(window.location)": "x", "javascript:alert(document.cookie)/": "", "