Add HTTP size limits

This commit is contained in:
2026-07-05 00:17:07 +00:00
parent a16f66cbc7
commit f7d821067f
15 changed files with 609 additions and 46 deletions

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
"io"
"mime"
"os"
"path/filepath"
@@ -19,6 +20,7 @@ var (
ErrMissingFilePath = errors.New("missing file path for file artifact")
ErrFileNotAllowed = errors.New("file artifact references are not allowed")
ErrFileOutsideRoot = errors.New("file artifact path is outside artifact root")
ErrFileTooLarge = errors.New("file artifact exceeds size limit")
)
// Reader resolves artifact references into actual artifacts.
@@ -40,7 +42,11 @@ func NewCompositeReader() Reader {
}
func NewRestrictedCompositeReader(root string) (Reader, error) {
fileReader, err := newRestrictedFileReader(root)
return NewRestrictedCompositeReaderWithLimit(root, 0)
}
func NewRestrictedCompositeReaderWithLimit(root string, maxBytes int64) (Reader, error) {
fileReader, err := newRestrictedFileReader(root, maxBytes)
if err != nil {
return nil, err
}
@@ -122,10 +128,14 @@ func (r deniedFileReader) Read(ctx context.Context, ref domain.ArtifactRef) (*do
}
type restrictedFileReader struct {
root string
root string
maxBytes int64
}
func newRestrictedFileReader(root string) (Reader, error) {
func newRestrictedFileReader(root string, maxBytes int64) (Reader, error) {
if maxBytes < 0 {
return nil, fmt.Errorf("artifact size limit must be greater than or equal to 0")
}
cleanRoot := strings.TrimSpace(root)
if cleanRoot == "" {
return deniedFileReader{}, nil
@@ -134,7 +144,7 @@ func newRestrictedFileReader(root string) (Reader, error) {
if err != nil {
return nil, fmt.Errorf("resolve artifact root: %w", err)
}
return &restrictedFileReader{root: absRoot}, nil
return &restrictedFileReader{root: absRoot, maxBytes: maxBytes}, nil
}
func (r *restrictedFileReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domain.Artifact, error) {
@@ -152,7 +162,7 @@ func (r *restrictedFileReader) Read(ctx context.Context, ref domain.ArtifactRef)
if err != nil {
return nil, err
}
return readFileArtifact(path)
return readFileArtifactWithLimit(path, r.maxBytes)
}
func (r *restrictedFileReader) resolve(rawPath string) (string, error) {
@@ -181,10 +191,39 @@ func (r *restrictedFileReader) resolve(rawPath string) (string, error) {
}
func readFileArtifact(path string) (*domain.Artifact, error) {
data, err := os.ReadFile(path)
return readFileArtifactWithLimit(path, 0)
}
func readFileArtifactWithLimit(path string, maxBytes int64) (*domain.Artifact, error) {
if maxBytes < 0 {
return nil, fmt.Errorf("file size limit must be greater than or equal to 0")
}
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %w", path, err)
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("failed to stat file %s: %w", path, err)
}
if maxBytes > 0 && info.Size() > maxBytes {
return nil, ErrFileTooLarge
}
var reader io.Reader = file
if maxBytes > 0 {
reader = io.LimitReader(file, maxBytes+1)
}
data, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %w", path, err)
}
if maxBytes > 0 && int64(len(data)) > maxBytes {
return nil, ErrFileTooLarge
}
contentType := mime.TypeByExtension(filepath.Ext(path))
if contentType == "" {