fix: strip nested BasePathFs base segment from BasePathFile.Name

BasePathFile.Name trimmed only filepath.Clean(path) from the wrapped
file name. filepath.Clean drops the trailing separator, and a nested
BasePathFile reports a name whose base segment is preceded by a leading
separator (for example "/123/x"), which the cleaned base ("123") does
not match. The base then leaked into Name and callers double-prepended
it. Trim the separator-prefixed form too so nested BasePathFs report a
name that round-trips.

Fixes #428
This commit is contained in:
dualfroz committed 2026-09-02 11:39:06 +02:00
1 parent 9f78513f63
commit b778e8e78b
2 files changed
+38 -1

No files matched your search

+10 -1
View File
@@ -37,7 +37,16 @@ type BasePathFile struct {
func (f *BasePathFile) Name() string {
sourcename := f.File.Name()
return strings.TrimPrefix(sourcename, filepath.Clean(f.path))
base := filepath.Clean(f.path)
name := strings.TrimPrefix(sourcename, base)
if name == sourcename {
// filepath.Clean drops the leading separator from the base path, so a
// name produced by a wrapped (nested) BasePathFile can still carry the
// base preceded by a separator. Strip that form as well, otherwise the
// base segment leaks into the reported name.
name = strings.TrimPrefix(sourcename, string(filepath.Separator)+base)
}
return name
}
func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) {
+28
View File
@@ -307,3 +307,31 @@ func TestBasePathTempFile(t *testing.T) {
t.Fatalf("TempFile realpath leaked: expected %s, got %s", expected, actual)
}
}
// Regression test for #428: a nested BasePathFs must strip its own base
// segment from BasePathFile.Name(), even though filepath.Clean drops the
// leading separator that the wrapped (inner) file name carries.
func TestNestedBasePathFileName(t *testing.T) {
baseFs := &MemMapFs{}
if err := baseFs.MkdirAll("/files/123", 0o777); err != nil {
t.Fatal(err)
}
fs1 := NewBasePathFs(baseFs, "/files")
fs2 := NewBasePathFs(fs1, "123")
f, err := fs2.OpenFile("/afero-test.txt", os.O_CREATE|os.O_RDWR, 0o644)
if err != nil {
t.Fatalf("open through nested BasePathFs failed: %v", err)
}
defer f.Close()
if expected, actual := "/afero-test.txt", f.Name(); expected != actual {
t.Fatalf("nested BasePathFile.Name() = %q, want %q", actual, expected)
}
// The returned name must round-trip: using it via fs2 resolves back to
// the same file instead of double-prepending the base segment.
if err := fs2.Chmod(f.Name(), 0o600); err != nil {
t.Fatalf("chmod via returned name failed: %v", err)
}
}