Add HTTP size limits
This commit is contained in:
@@ -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 == "" {
|
||||
|
||||
Reference in New Issue
Block a user