75 lines
2.3 KiB
Go
75 lines
2.3 KiB
Go
package engine
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/fs"
|
|
)
|
|
|
|
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[F fs.File](name string, stat func(string) (fs.FileInfo, error), open func(string) (F, 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)
|
|
}
|
|
}
|