Files
garage-ui/backend/internal/handlers/range.go
T
Noste c0976a1e1d feat(preview): Implement object preview functionality (#94)
* feat(preview): implement object preview functionality with token-based access

* docs: update README to include inline object preview feature

* test: add range read error handling and attachment content disposition tests
2026-07-15 00:01:04 +02:00

69 lines
1.6 KiB
Go

package handlers
import (
"strconv"
"strings"
)
// byteRange is a resolved, inclusive byte range within an object.
type byteRange struct {
start int64
end int64
}
// parseRangeHeader resolves a Range request header against the object size.
// It supports a single "bytes=" range in its three forms: start-end, start-,
// and -suffix. A nil result with unsatisfiable false means serve the full
// object with 200; absent, malformed, and multi-range headers all land there,
// which RFC 9110 permits. unsatisfiable true means respond 416.
func parseRangeHeader(header string, size int64) (rng *byteRange, unsatisfiable bool) {
spec, ok := strings.CutPrefix(header, "bytes=")
if !ok {
return nil, false
}
spec = strings.TrimSpace(spec)
if strings.Contains(spec, ",") {
return nil, false
}
startStr, endStr, ok := strings.Cut(spec, "-")
if !ok {
return nil, false
}
startStr = strings.TrimSpace(startStr)
endStr = strings.TrimSpace(endStr)
// Suffix form "-n" asks for the last n bytes.
if startStr == "" {
n, err := strconv.ParseInt(endStr, 10, 64)
if err != nil {
return nil, false
}
if n <= 0 || size == 0 {
return nil, true
}
if n > size {
n = size
}
return &byteRange{start: size - n, end: size - 1}, false
}
start, err := strconv.ParseInt(startStr, 10, 64)
if err != nil || start < 0 {
return nil, false
}
if start >= size {
return nil, true
}
if endStr == "" {
return &byteRange{start: start, end: size - 1}, false
}
end, err := strconv.ParseInt(endStr, 10, 64)
if err != nil || end < start {
return nil, false
}
if end >= size {
end = size - 1
}
return &byteRange{start: start, end: end}, false
}