File size: 4,034 Bytes
7107f0b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
// Package http_range implements http range parsing.
package http_range
import (
"errors"
"fmt"
"net/http"
"net/textproto"
"strconv"
"strings"
)
// Range specifies the byte range to be sent to the client.
type Range struct {
Start int64
Length int64 // limit of bytes to read, -1 for unlimited
}
// ContentRange returns Content-Range header value.
func (r Range) ContentRange(size int64) string {
return fmt.Sprintf("bytes %d-%d/%d", r.Start, r.Start+r.Length-1, size)
}
var (
// ErrNoOverlap is returned by ParseRange if first-byte-pos of
// all 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) {
return nil, ErrInvalid
}
var ranges []Range
noOverlap := false
for _, ra := range strings.Split(s[len(b):], ",") {
ra = textproto.TrimString(ra)
if ra == "" {
continue
}
i := strings.Index(ra, "-")
if i < 0 {
return nil, ErrInvalid
}
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 <suffix-length>
// 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.Length = size - r.Start
} 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.Length = size - r.Start
} else {
i, err := strconv.ParseInt(end, 10, 64)
if err != nil || r.Start > i {
return nil, ErrInvalid
}
if i >= size {
i = size - 1
}
r.Length = i - r.Start + 1
}
}
ranges = append(ranges, r)
}
if noOverlap && len(ranges) == 0 {
// The specified ranges did not overlap with the content.
return nil, ErrNoOverlap
}
return ranges, nil
}
// ParseContentRange this function parse content-range in http response
func ParseContentRange(s string) (start, end int64, err error) {
if s == "" {
return 0, 0, ErrInvalid
}
const b = "bytes "
if !strings.HasPrefix(s, b) {
return 0, 0, ErrInvalid
}
p1 := strings.Index(s, "-")
p2 := strings.Index(s, "/")
if p1 < 0 || p2 < 0 {
return 0, 0, ErrInvalid
}
startStr, endStr := textproto.TrimString(s[len(b):p1]), textproto.TrimString(s[p1+1:p2])
start, startErr := strconv.ParseInt(startStr, 10, 64)
end, endErr := strconv.ParseInt(endStr, 10, 64)
return start, end, errors.Join(startErr, endErr)
}
func (r Range) MimeHeader(contentType string, size int64) textproto.MIMEHeader {
return textproto.MIMEHeader{
"Content-Range": {r.ContentRange(size)},
"Content-Type": {contentType},
}
}
// ApplyRangeToHttpHeader for http request header
func ApplyRangeToHttpHeader(p Range, headerRef http.Header) http.Header {
header := headerRef
if header == nil {
header = http.Header{}
}
if p.Start == 0 && p.Length < 0 {
header.Del("Range")
} else {
end := ""
if p.Length >= 0 {
end = strconv.FormatInt(p.Start+p.Length-1, 10)
}
header.Set("Range", fmt.Sprintf("bytes=%v-%v", p.Start, end))
}
return header
}
|