diff --git a/internal/http_range/range.go b/internal/http_range/range.go index 4448591..ca79bdd 100644 --- a/internal/http_range/range.go +++ b/internal/http_range/range.go @@ -2,105 +2,66 @@ package http_range import ( "errors" - "fmt" - "net/textproto" "strconv" "strings" ) -// Range specifies the byte range to be sent to the client. type Range struct { Start int64 End int64 } -// ContentRange returns Content-Range header value. -func (r Range) ContentRange(size int64) string { - return fmt.Sprintf("bytes %d-%d/%d", r.Start, r.End, size) -} - var ( - // ErrNoOverlap is returned by ParseRange if first-byte-pos of - // all of the byte-range-spec values is greater than the content size. ErrNoOverlap = errors.New("invalid range: failed to overlap") - // ErrInvalid is returned by ParseRange on invalid input. ErrInvalid = errors.New("invalid range") ) -// ParseRange parses a Range header string as per RFC 7233. -// ErrNoOverlap is returned if none of the ranges overlap. -// ErrInvalid is returned if s is invalid range. -func ParseRange(s string, size int64) ([]Range, error) { // nolint:gocognit - if s == "" { - return nil, nil // header not present - } - const b = "bytes=" - if !strings.HasPrefix(s, b) { +func Parse(header string, size int64) ([]*Range, error) { + index := strings.Index(header, "=") + + if index == -1 { return nil, ErrInvalid } - var ranges []Range - noOverlap := false - for _, ra := range strings.Split(s[len(b):], ",") { - ra = textproto.TrimString(ra) - if ra == "" { + + size64 := int64(size) + arr := strings.Split(header[index+1:], ",") + ranges := make([]*Range, 0, len(arr)) + + for _, value := range arr { + r := strings.Split(value, "-") + start, startErr := strconv.ParseInt(r[0], 10, 64) + end, endErr := strconv.ParseInt(r[1], 10, 64) + + if startErr != nil && endErr != nil { continue } - i := strings.Index(ra, "-") - if i < 0 { - return nil, ErrInvalid + + // -nnn and nnn- + if startErr != nil { + start = size64 - end + end = size64 - 1 + } else if endErr != nil { + end = size64 - 1 } - start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:]) - var r Range - if start == "" { - // If no start is specified, end specifies the - // range start relative to the end of the file, - // and we are dealing with - // which has to be a non-negative integer as per - // RFC 7233 Section 2.1 "Byte-Ranges". - if end == "" || end[0] == '-' { - return nil, ErrInvalid - } - i, err := strconv.ParseInt(end, 10, 64) - if i < 0 || err != nil { - return nil, ErrInvalid - } - if i > size { - i = size - } - r.Start = size - i - r.End = size - 1 - } else { - i, err := strconv.ParseInt(start, 10, 64) - if err != nil || i < 0 { - return nil, ErrInvalid - } - if i >= size { - // If the range begins after the size of the content, - // then it does not overlap. - noOverlap = true - continue - } - r.Start = i - if end == "" { - // If no end is specified, range extends to end of the file. - r.End = size - 1 - } else { - i, err := strconv.ParseInt(end, 10, 64) - if err != nil || r.Start > i { - return nil, ErrInvalid - } - if i >= size { - i = size - 1 - } - r.End = i - } + + if end >= size64 { + end = size64 - 1 } - ranges = append(ranges, r) + + if start > end || start < 0 { + continue + } + + ranges = append(ranges, &Range{ + Start: start, + End: end, + }) } - if noOverlap && len(ranges) == 0 { - // The specified ranges did not overlap with the content. + + if len(ranges) == 0 { return nil, ErrNoOverlap } + return ranges, nil } diff --git a/pkg/services/file.go b/pkg/services/file.go index ce0b8f0..8c467f4 100644 --- a/pkg/services/file.go +++ b/pkg/services/file.go @@ -499,7 +499,7 @@ func (fs *FileService) GetFileStream(c *gin.Context) { end = file.Size - 1 w.WriteHeader(http.StatusOK) } else { - ranges, err := http_range.ParseRange(rangeHeader, file.Size) + ranges, err := http_range.Parse(rangeHeader, file.Size) if err == http_range.ErrNoOverlap { w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", file.Size)) http.Error(w, http_range.ErrNoOverlap.Error(), http.StatusRequestedRangeNotSatisfiable)