Files
ConfigKit/source_file.go

76 lines
2.3 KiB
Go

package configkit
import (
"errors"
"io"
"io/fs"
"os"
)
type sourceReadFailureKind uint8
const (
sourceReadIO sourceReadFailureKind = iota
sourceReadNotRegular
sourceReadLimit
)
// sourceReadFailure never escapes the private source boundary. missingTarget is
// set only for preflight/open failures; a later missing-file error is not optional.
type sourceReadFailure struct {
kind sourceReadFailureKind
cause error
missingTarget bool
}
// readRegularFile borrows stat/open operations so root-bound callers retain their
// containment policy. Preflight rejects FIFOs before open can block. The opened
// handle is checked again and always closed; read-only close errors are ignored.
func readRegularFile(name string, stat func(string) (fs.FileInfo, error), open func(string) (*os.File, error), limit int64) ([]byte, *sourceReadFailure) {
info, err := stat(name)
if err != nil {
return nil, &sourceReadFailure{cause: err, missingTarget: errors.Is(err, fs.ErrNotExist)}
}
if !info.Mode().IsRegular() {
return nil, &sourceReadFailure{kind: sourceReadNotRegular}
}
file, err := open(name)
if err != nil {
return nil, &sourceReadFailure{cause: err, missingTarget: errors.Is(err, fs.ErrNotExist)}
}
defer func() { _ = file.Close() }()
info, err = file.Stat()
if err != nil {
return nil, &sourceReadFailure{cause: err}
}
if !info.Mode().IsRegular() {
return nil, &sourceReadFailure{kind: sourceReadNotRegular}
}
if info.Size() > limit {
return nil, &sourceReadFailure{kind: sourceReadLimit}
}
return readBoundedSource(file, limit)
}
func readBoundedSource(input io.Reader, limit int64) ([]byte, *sourceReadFailure) {
data, err := io.ReadAll(io.LimitReader(input, limit+1))
if err != nil {
return nil, &sourceReadFailure{cause: err}
}
if int64(len(data)) > limit {
return nil, &sourceReadFailure{kind: sourceReadLimit}
}
return data, nil
}
func sourceReadDiagnostic(failure *sourceReadFailure, sourceID string, limitCode Code, limitMessage string) Diagnostic {
switch failure.kind {
case sourceReadNotRegular:
return sourceDiagnostic(CodeSourceNotRegular, sourceID, "source is not a regular file")
case sourceReadLimit:
return sourceDiagnostic(limitCode, sourceID, limitMessage)
default:
return classifySourceError(failure.cause, sourceID)
}
}