Bound external reference reads and fingerprints
This commit is contained in:
@@ -3,7 +3,10 @@ package pipeline
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"mime"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -124,10 +127,30 @@ func materializeReferenceTarget(
|
||||
if err != nil {
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q path %q: %w", referenceTargetContext(pipelineID, target), slotName, binding.Source, err)
|
||||
}
|
||||
content, err := os.ReadFile(path)
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q read %q: %w", referenceTargetContext(pipelineID, target), slotName, path, err)
|
||||
}
|
||||
regularFileSize := int64(0)
|
||||
if info, statErr := file.Stat(); statErr == nil && info.Mode().IsRegular() {
|
||||
regularFileSize = info.Size()
|
||||
}
|
||||
content, err := readReferenceContent(file, slot.MaxBytes)
|
||||
closeErr := file.Close()
|
||||
if err == nil && closeErr != nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err != nil {
|
||||
var sizeErr *referenceSizeLimitError
|
||||
if errors.As(err, &sizeErr) {
|
||||
sizeBytes := sizeErr.SizeBytes
|
||||
if regularFileSize > sizeBytes {
|
||||
sizeBytes = regularFileSize
|
||||
}
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q path %q is %d bytes, limit %d", referenceTargetContext(pipelineID, target), slotName, path, sizeBytes, slot.MaxBytes)
|
||||
}
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q read %q: %w", referenceTargetContext(pipelineID, target), slotName, path, err)
|
||||
}
|
||||
if !utf8.Valid(content) {
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q path %q must be UTF-8 text", referenceTargetContext(pipelineID, target), slotName, path)
|
||||
}
|
||||
@@ -135,9 +158,6 @@ func materializeReferenceTarget(
|
||||
if !referenceMediaTypeAccepted(mediaType, slot.AcceptedMediaTypes) {
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q path %q media type %q is not accepted", referenceTargetContext(pipelineID, target), slotName, path, mediaType)
|
||||
}
|
||||
if slot.MaxBytes > 0 && int64(len(content)) > slot.MaxBytes {
|
||||
return contracts.ReferenceSet{}, nil, fmt.Errorf("%s reference slot %q path %q is %d bytes, limit %d", referenceTargetContext(pipelineID, target), slotName, path, len(content), slot.MaxBytes)
|
||||
}
|
||||
if len(content) == 0 {
|
||||
warnings = append(warnings, contracts.Warning{
|
||||
Scope: referenceWarningScope(pipelineID, target, slotName),
|
||||
@@ -163,6 +183,32 @@ func materializeReferenceTarget(
|
||||
return set, warnings, nil
|
||||
}
|
||||
|
||||
type referenceSizeLimitError struct {
|
||||
SizeBytes int64
|
||||
MaxBytes int64
|
||||
}
|
||||
|
||||
func (err *referenceSizeLimitError) Error() string {
|
||||
return fmt.Sprintf("reference is %d bytes, limit %d", err.SizeBytes, err.MaxBytes)
|
||||
}
|
||||
|
||||
func readReferenceContent(reader io.Reader, maxBytes int64) ([]byte, error) {
|
||||
if maxBytes <= 0 {
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
if maxBytes == math.MaxInt64 {
|
||||
return nil, fmt.Errorf("maximum reference size %d cannot be safely bounded", maxBytes)
|
||||
}
|
||||
content, err := io.ReadAll(io.LimitReader(reader, maxBytes+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if int64(len(content)) > maxBytes {
|
||||
return nil, &referenceSizeLimitError{SizeBytes: int64(len(content)), MaxBytes: maxBytes}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func referenceTargetSpec(target ResolvedReferenceTarget, artifactKind contracts.ArtifactKind, catalog ModuleCatalog) (ModuleSpec, error) {
|
||||
switch target.Stage {
|
||||
case StageChunk:
|
||||
|
||||
Reference in New Issue
Block a user