Files
Matthew HoltandCopilot 13d000e010 fileserver: anchor file-match paths relative to root
Use filepath.Rel rather than prefix trimming for both existing and fallback candidates. Preserve dotfiles and directory suffixes, and always expose a slash-prefixed path so rewrites and downstream path guards agree.

Addresses scan report 3188624e-2157-49e6-b3e0-8c5049b72269.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-11 11:06:41 -07:00

70 lines
2.1 KiB
Go

package fileserver
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/caddyserver/caddy/v2/internal/filesystems"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
)
func TestFileMatcherRootRelative(t *testing.T) {
dir := t.TempDir()
t.Chdir(dir)
if err := os.Mkdir("public", 0o700); err != nil {
t.Fatal(err)
}
for _, root := range []string{"", ".", "./", "public", dir, filepath.VolumeName(dir) + string(filepath.Separator)} {
t.Run(root, func(t *testing.T) {
// The filesystem root case still writes only inside the temporary directory.
base := root
if base == "" {
base = "."
}
prefix := ""
if root == filepath.VolumeName(dir)+string(filepath.Separator) {
relative, err := filepath.Rel(root, dir)
if err != nil {
t.Fatal(err)
}
prefix = filepath.ToSlash(relative) + "/"
}
for _, name := range []string{"private.txt", ".private", "missing"} {
path := prefix + name
if name != "missing" {
if err := os.WriteFile(filepath.Join(base, filepath.FromSlash(path)), []byte("synthetic"), 0o600); err != nil {
t.Fatal(err)
}
}
policy := tryPolicyFirstExist
if name == "missing" {
policy = tryPolicyFirstExistFallback
}
m := MatchFile{
fsmap: &filesystems.FileSystemMap{}, Root: root,
TryFiles: []string{path}, TryPolicy: policy,
}
req := httptest.NewRequest(http.MethodGet, "/"+path, nil)
repl := caddyhttp.NewTestReplacer(req)
matched, err := m.MatchWithError(req)
if err != nil || !matched {
t.Fatalf("matching %q: matched=%v, error=%v", path, matched, err)
}
want := "/" + path
if got, _ := repl.Get("http.matchers.file.relative"); got != want {
t.Fatalf("relative path = %q, want %q", got, want)
}
rewrite.Rewrite{URI: "{http.matchers.file.relative}"}.Rewrite(req, repl)
if !(caddyhttp.MatchPath{strings.ToLower(want)}).Match(req) {
t.Fatalf("rewritten path %q bypassed downstream path guard %q", req.URL.Path, want)
}
}
})
}
}