Preserve directory traversal failure phases
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
)
|
||||
@@ -181,3 +182,191 @@ func TestLoadingClosesNativeFilesAcrossPipelineOutcomes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectorySelectorsFailOnPostOpenNotExistErrors(t *testing.T) {
|
||||
const canary = "DIRECTORY_FAILURE_CANARY"
|
||||
readFailure := errors.Join(fs.ErrNotExist, errors.New(canary))
|
||||
closeFailure := errors.Join(fs.ErrNotExist, errors.New(canary))
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
selector string
|
||||
fallback bool
|
||||
readError bool
|
||||
noProgress bool
|
||||
closeError bool
|
||||
fallbackErr bool
|
||||
}{
|
||||
{name: "directory batched read with entries", selector: "directory", readError: true},
|
||||
{name: "directory close", selector: "directory", closeError: true},
|
||||
{name: "directory fallback with entries", selector: "directory", fallback: true, fallbackErr: true},
|
||||
{name: "directory no progress", selector: "directory", noProgress: true},
|
||||
{name: "glob batched read with entries", selector: "glob", readError: true},
|
||||
{name: "glob close", selector: "glob", closeError: true},
|
||||
{name: "glob fallback with entries", selector: "glob", fallback: true, fallbackErr: true},
|
||||
{name: "glob no progress", selector: "glob", noProgress: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
main := "configkit:\n imports:\n"
|
||||
if test.selector == "directory" {
|
||||
main += " - dir: [broken, good]\n pattern: '*.yml'\n"
|
||||
} else {
|
||||
main += " - glob: 'broken/*.yml'\n - glob: 'good/*.yml'\n"
|
||||
}
|
||||
filesystem := &observedFilesystem{
|
||||
files: fstest.MapFS{
|
||||
"main.yml": &fstest.MapFile{Data: []byte(main)},
|
||||
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"broken/bad.yml": &fstest.MapFile{Data: []byte("value: bad\n")},
|
||||
"good": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"good/good.yml": &fstest.MapFile{Data: []byte("value: good\n")},
|
||||
},
|
||||
fallback: test.fallback,
|
||||
failReadDir: "broken",
|
||||
readDirErr: readFailure,
|
||||
failNoProgress: "broken",
|
||||
failClose: "broken",
|
||||
closeErr: closeFailure,
|
||||
failFallbackDir: "broken",
|
||||
fallbackErr: readFailure,
|
||||
}
|
||||
if !test.readError {
|
||||
filesystem.failReadDir = ""
|
||||
}
|
||||
if !test.noProgress {
|
||||
filesystem.failNoProgress = ""
|
||||
}
|
||||
if !test.closeError {
|
||||
filesystem.failClose = ""
|
||||
}
|
||||
if !test.fallbackErr {
|
||||
filesystem.failFallbackDir = ""
|
||||
}
|
||||
|
||||
result, err := testLoad[map[string]any](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[map[string]any]{Imports: true})
|
||||
if result != nil {
|
||||
t.Fatalf("load returned a result after directory failure: %#v", result)
|
||||
}
|
||||
var loadError *LoadError
|
||||
if !errors.As(err, &loadError) {
|
||||
t.Fatalf("load error = %#v, want structured LoadError", err)
|
||||
}
|
||||
diagnostics := loadError.Diagnostics()
|
||||
if len(diagnostics) != 1 {
|
||||
t.Fatalf("diagnostics = %#v, want one directory failure", diagnostics)
|
||||
}
|
||||
diagnostic := diagnostics[0]
|
||||
wantCode := CodeImportInvalid
|
||||
if test.selector == "glob" {
|
||||
wantCode = CodeImportRead
|
||||
}
|
||||
if diagnostic.Code != wantCode || diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1" {
|
||||
t.Fatalf("directory diagnostic = %#v, want %q at source-1", diagnostic, wantCode)
|
||||
}
|
||||
var rendered strings.Builder
|
||||
if err := WriteDiagnostics(&rendered, diagnostics); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(err.Error()+diagnostic.Message+rendered.String(), canary) {
|
||||
t.Fatalf("backing error canary escaped: error=%q diagnostic=%#v rendered=%q", err, diagnostic, rendered.String())
|
||||
}
|
||||
if filesystem.fallbackBeforeClose {
|
||||
t.Fatal("fallback directory enumeration began before the opened handle was closed")
|
||||
}
|
||||
filesystem.assertClosed(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDirectoryPreservesReadErrorOverCloseError(t *testing.T) {
|
||||
readFailure := errors.New("read failure")
|
||||
closeFailure := errors.New("close failure")
|
||||
filesystem := &observedFilesystem{
|
||||
files: fstest.MapFS{
|
||||
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"broken/entry": &fstest.MapFile{Data: []byte("entry")},
|
||||
},
|
||||
failReadDir: "broken",
|
||||
readDirErr: readFailure,
|
||||
failClose: "broken",
|
||||
closeErr: closeFailure,
|
||||
}
|
||||
access := newNativeAccess(filesystem, ".", &namespaceToken{})
|
||||
visited := 0
|
||||
err := access.readDirectory("broken", func(fs.DirEntry) bool {
|
||||
visited++
|
||||
return true
|
||||
})
|
||||
if !errors.Is(err, readFailure) || errors.Is(err, closeFailure) || visited != 1 {
|
||||
t.Fatalf("directory read = %v, visited %d; want entries plus primary read failure", err, visited)
|
||||
}
|
||||
if missingDirectoryOpen(err) {
|
||||
t.Fatal("post-open read failure was classified as missing-on-open")
|
||||
}
|
||||
filesystem.assertClosed(t)
|
||||
}
|
||||
|
||||
func TestReadDirectoryVisitStopCloseNotExistIsPostOpen(t *testing.T) {
|
||||
filesystem := &observedFilesystem{
|
||||
files: fstest.MapFS{
|
||||
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"broken/entry": &fstest.MapFile{Data: []byte("entry")},
|
||||
},
|
||||
failClose: "broken",
|
||||
}
|
||||
access := newNativeAccess(filesystem, ".", &namespaceToken{})
|
||||
err := access.readDirectory("broken", func(fs.DirEntry) bool { return false })
|
||||
if !errors.Is(err, fs.ErrNotExist) || missingDirectoryOpen(err) {
|
||||
t.Fatalf("visit-stop close error = %v, missing-on-open=%t", err, missingDirectoryOpen(err))
|
||||
}
|
||||
filesystem.assertClosed(t)
|
||||
}
|
||||
|
||||
func TestDirectorySelectorsKeepMissingRootsOptional(t *testing.T) {
|
||||
for _, selector := range []string{"directory", "glob"} {
|
||||
t.Run(selector, func(t *testing.T) {
|
||||
main := "configkit:\n imports:\n"
|
||||
if selector == "directory" {
|
||||
main += " - dir: [missing, good]\n pattern: '*.yml'\n required: true\n"
|
||||
} else {
|
||||
main += " - glob: 'missing/*.yml'\n - glob: 'good/*.yml'\n required: true\n"
|
||||
}
|
||||
filesystem := &observedFilesystem{files: fstest.MapFS{
|
||||
"main.yml": &fstest.MapFile{Data: []byte(main)},
|
||||
"good": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"good/good.yml": &fstest.MapFile{Data: []byte("value: good\n")},
|
||||
}}
|
||||
result, err := testLoad[map[string]any](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[map[string]any]{Imports: true})
|
||||
if err != nil || result == nil {
|
||||
t.Fatalf("load with missing search root = %#v, %v", result, err)
|
||||
}
|
||||
filesystem.assertClosed(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectorySelectorsTreatMissingOnOpenAsOptional(t *testing.T) {
|
||||
for _, selector := range []string{"directory", "glob"} {
|
||||
t.Run(selector, func(t *testing.T) {
|
||||
main := "configkit:\n imports:\n"
|
||||
if selector == "directory" {
|
||||
main += " - dir: [broken, good]\n pattern: '*.yml'\n required: true\n"
|
||||
} else {
|
||||
main += " - glob: 'broken/*.yml'\n - glob: 'good/*.yml'\n required: true\n"
|
||||
}
|
||||
filesystem := &observedFilesystem{
|
||||
files: fstest.MapFS{
|
||||
"main.yml": &fstest.MapFile{Data: []byte(main)},
|
||||
"broken": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"good": &fstest.MapFile{Mode: fs.ModeDir | 0o700},
|
||||
"good/good.yml": &fstest.MapFile{Data: []byte("value: good\n")},
|
||||
},
|
||||
failOpen: "broken",
|
||||
}
|
||||
result, err := testLoad[map[string]any](filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), Options[map[string]any]{Imports: true})
|
||||
if err != nil || result == nil {
|
||||
t.Fatalf("load after missing-on-open root = %#v, %v", result, err)
|
||||
}
|
||||
filesystem.assertClosed(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user