fix(migration): bound per-entry zip cap by configured files.maxsize
The hard-coded 500 MB per-entry cap meant operators who set a tighter files.maxsize could not actually enforce it on imports. Derive the cap from files.maxsize with a floor so data.json / filters.json / VERSION entries can still be read when the configured limit is tiny. Clamp the uint64->int64 conversion and the LimitReader cap so absurd configuration values do not overflow into MinInt64 and cause io.LimitReader to treat every entry as EOF.
This commit is contained in:
parent
db7f1445a8
commit
abfbcb4cf3
|
|
@ -22,9 +22,11 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
|
@ -33,26 +35,52 @@ import (
|
|||
"code.vikunja.io/api/pkg/utils"
|
||||
vversion "code.vikunja.io/api/pkg/version"
|
||||
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
const logPrefix = "[Vikunja File Import] "
|
||||
const maxZipEntrySize = 500 * 1024 * 1024 // 500 MB
|
||||
|
||||
// ErrFileTooLarge is returned when a file in the zip archive exceeds maxZipEntrySize
|
||||
var ErrFileTooLarge = fmt.Errorf("file exceeds maximum allowed size of %d bytes", maxZipEntrySize)
|
||||
// minZipEntryCap ensures data.json / filters.json / VERSION entries can
|
||||
// still be read when files.maxsize is tiny.
|
||||
const minZipEntryCap = 4 * 1024 * 1024 // 4 MB
|
||||
|
||||
// readZipEntry reads from r into a buffer, returning ErrFileTooLarge if the content exceeds maxZipEntrySize.
|
||||
// Unlike io.LimitReader which silently truncates, this function explicitly detects overflow.
|
||||
// maxZipEntrySize returns max(files.maxsize, minZipEntryCap).
|
||||
func maxZipEntrySize() int64 {
|
||||
mb := config.GetMaxFileSizeInMBytes()
|
||||
// Clamp before multiply: absurd configs would overflow int64.
|
||||
const maxInt64MB = uint64(math.MaxInt64 / datasize.MB)
|
||||
if mb > maxInt64MB {
|
||||
return math.MaxInt64
|
||||
}
|
||||
fromConfig := int64(mb) * int64(datasize.MB)
|
||||
if fromConfig < minZipEntryCap {
|
||||
return minZipEntryCap
|
||||
}
|
||||
return fromConfig
|
||||
}
|
||||
|
||||
// ErrFileTooLarge is returned when a file in the zip archive exceeds the effective cap.
|
||||
var ErrFileTooLarge = fmt.Errorf("zip entry exceeds the configured maximum file size")
|
||||
|
||||
// readZipEntry reads from r into a buffer, returning ErrFileTooLarge if
|
||||
// the content exceeds the effective cap. Unlike io.LimitReader it
|
||||
// explicitly detects overflow rather than silently truncating.
|
||||
func readZipEntry(r io.Reader) (*bytes.Buffer, error) {
|
||||
// Read one extra byte to detect overflow
|
||||
limitedReader := io.LimitReader(r, maxZipEntrySize+1)
|
||||
limit := maxZipEntrySize()
|
||||
// Avoid limit+1 overflowing to MinInt64 when limit == MaxInt64,
|
||||
// which io.LimitReader would treat as EOF (every entry reads empty).
|
||||
readCap := limit
|
||||
if readCap != math.MaxInt64 {
|
||||
readCap++
|
||||
}
|
||||
limitedReader := io.LimitReader(r, readCap)
|
||||
var buf bytes.Buffer
|
||||
n, err := buf.ReadFrom(limitedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n > maxZipEntrySize {
|
||||
if n > limit {
|
||||
return nil, ErrFileTooLarge
|
||||
}
|
||||
return &buf, nil
|
||||
|
|
|
|||
Loading…
Reference in New Issue