package utils import ( "os" "path/filepath" "testing" ) func TestURL2filename(t *testing.T) { tests := []struct { dirname string urlpath string want string }{ {"/srv", "/index.html", "/srv" + Pathsep + "index.html"}, {"/srv/", "/index.html", "/srv/index.html"}, {"/srv", "/sub/page.md", "/srv" + Pathsep + "sub/page.md"}, {"/srv", "relative.txt", "/srv/relative.txt"}, {"/srv", "/../etc/passwd", "/srv" + Pathsep}, {"/srv", "//admin/secret.lua", "/srv" + Pathsep + "admin" + Pathsep + "secret.lua"}, {"/srv", "/a/./b//c", "/srv" + Pathsep + "a" + Pathsep + "b" + Pathsep + "c"}, {"/srv", "/sub/", "/srv" + Pathsep + "sub" + Pathsep}, {"/srv", "/", "/srv" + Pathsep}, } for _, tt := range tests { got := URL2filename(tt.dirname, tt.urlpath) if got != tt.want { t.Errorf("URL2filename(%q, %q) = %q, want %q", tt.dirname, tt.urlpath, got, tt.want) } } } // Containment helper: only descendants of root should pass. func TestWithinDir(t *testing.T) { root := t.TempDir() cases := []struct { name string path string want bool }{ {"root itself", root, true}, {"direct child", filepath.Join(root, "a.js"), true}, {"nested child", filepath.Join(root, "sub", "a.js"), true}, {"sibling", filepath.Join(root, "..", "other"), false}, {"ancestor", filepath.Dir(root), false}, } for _, c := range cases { if got := WithinDir(root, c.path); got != c.want { t.Errorf("%s: WithinDir(%q,%q) = %v, want %v", c.name, root, c.path, got, c.want) } } } func TestGetFilenames(t *testing.T) { dir, err := os.MkdirTemp("", "algernon-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) // Create some files for _, name := range []string{"a.txt", "b.md", "c.html"} { if err := os.WriteFile(dir+"/"+name, []byte("x"), 0o644); err != nil { t.Fatal(err) } } filenames := GetFilenames(dir) if len(filenames) != 3 { t.Errorf("GetFilenames returned %d files, want 3", len(filenames)) } // Non-existent directory returns empty slice filenames = GetFilenames("/nonexistent_dir_xyz") if len(filenames) != 0 { t.Errorf("GetFilenames on nonexistent dir returned %d files, want 0", len(filenames)) } } func TestDescribeBytes(t *testing.T) { tests := []struct { size int64 want string }{ {0, "0 KiB"}, {1024, "1 KiB"}, {512, "0 KiB"}, {1048576, "1 MiB"}, {2097152, "2 MiB"}, {10240, "10 KiB"}, } for _, tt := range tests { got := DescribeBytes(tt.size) if got != tt.want { t.Errorf("DescribeBytes(%d) = %q, want %q", tt.size, got, tt.want) } } } func TestResolvesWithinDir(t *testing.T) { outside := t.TempDir() secret := filepath.Join(outside, "secret.txt") if err := os.WriteFile(secret, []byte("secret"), 0o600); err != nil { t.Fatal(err) } root := t.TempDir() inside := filepath.Join(root, "inside.txt") if err := os.WriteFile(inside, []byte("ok"), 0o600); err != nil { t.Fatal(err) } links := map[string]string{ "leak": secret, "leakdir": outside, "insidelink": inside, } for name, target := range links { if err := os.Symlink(target, filepath.Join(root, name)); err != nil { t.Skip("symlinks are not supported here:", err) } } for p, want := range map[string]bool{ inside: true, filepath.Join(root, "insidelink"): true, filepath.Join(root, "missing.txt"): true, filepath.Join(root, "leak"): false, filepath.Join(root, "leakdir"): false, filepath.Join(root, "leakdir", "secret.txt"): false, } { if got := ResolvesWithinDir(root, p); got != want { t.Errorf("ResolvesWithinDir(root, %s) = %v, want %v", p, got, want) } } // The root itself may be a symlink, like /srv/www -> /data/www rootLink := filepath.Join(t.TempDir(), "rootlink") if err := os.Symlink(root, rootLink); err != nil { t.Fatal(err) } if !ResolvesWithinDir(rootLink, filepath.Join(rootLink, "inside.txt")) { t.Error("a file in a symlinked root should be within the root") } if ResolvesWithinDir(rootLink, filepath.Join(rootLink, "leak")) { t.Error("a symlink out of a symlinked root should not be within the root") } }