package httprange
import (
)
const (
noneUnit = "none"
httpHeaderRange = "Range"
httpHeaderAcceptRanges = "Accept-Ranges"
httpHeaderContentRange = "Content-Range"
httpHeaderContentType = "Content-Type"
mimeMultipartByteranges = "multipart/byteranges"
)
type Specifier string
func ( Specifier) () string {
, , := strings.Cut(string(), "=")
return
}
type Range struct {
Resp string
Reader io.Reader
}
type Ranger interface {
Range(ctx context.Context, spec Specifier) iter.Seq2[Range, error]
}
type HTTPRanger struct {
Request HTTPRequestBuilder
Client httpclient.Client
}
func ( *HTTPRanger) ( context.Context, Specifier) iter.Seq2[Range, error] {
return func( func(Range, error) bool) {
, := .Request.Build()
if != nil {
_ = (Range{}, )
return
}
.Header = setHeader(.Header, http.Header{
httpHeaderRange: {string()},
})
, := .Client.Do()
if != nil {
_ = (Range{}, )
return
}
:= func() { _ = .Body.Close() }
:= context.AfterFunc(, )
defer func() {
if () {
()
}
}()
if := parseHTTPResponse(
,
.Unit(),
func( Range) bool {
return (, nil)
},
); != nil {
_ = (Range{}, &HTTPResponseError{
Response: ,
cause: ,
})
}
}
}
func ( *http.Response, string, func(Range) bool) error {
if := checkAcceptRangesHeader(.Header, ); != nil {
return
}
switch .StatusCode {
case http.StatusPartialContent:
case http.StatusRequestedRangeNotSatisfiable:
if .Header.Get(httpHeaderContentRange) == "" {
return &UnsatisfiedRangeError{}
}
, := parseContentRangeFromHeader(.Header, )
if != nil {
return errors.Join(
&UnsatisfiedRangeError{},
,
)
}
return &UnsatisfiedRangeError{Resp: }
default:
return &UnexpectedStatusCodeError{
Status: .Status,
StatusCode: .StatusCode,
}
}
if := .Header.Get(httpHeaderContentType); != "" {
, , := mime.ParseMediaType()
if == mimeMultipartByteranges {
if != nil {
return fmt.Errorf(
"httprange: parse Content-Type header: %w",
,
)
}
:= multipart.NewReader(.Body, ["boundary"])
return readMultipartRanges(, , )
}
}
, := parseContentRangeFromHeader(.Header, )
if != nil {
return
}
_ = (Range{
Resp: ,
Reader: .Body,
})
return nil
}
func ( *multipart.Reader, string, func(Range) bool) error {
for := true; ; = false {
, := .NextPart()
if == io.EOF {
if {
return errors.New("httprange: empty multipart ranges")
}
return nil
}
if != nil {
return
}
, := parseContentRangeFromHeader(http.Header(.Header), )
if != nil {
return
}
if !(Range{Resp: , Reader: }) {
return nil
}
}
}
func ( http.Header, string) error {
return checkUnitIsAccepted(.Values(httpHeaderAcceptRanges), )
}
func ( []string, string) error {
if len() == 0 {
return nil
}
for , := range {
if equalFoldASCII(, noneUnit) {
return errRangesNotSupported
}
}
if !httpguts.HeaderValuesContainsToken(, ) {
return &UnacceptedUnitError{
AcceptRanges: ,
}
}
return nil
}
func (, string) bool {
if len() != len() {
return false
}
for := range len() {
if lowerASCII([]) != lowerASCII([]) {
return false
}
}
return true
}
func ( byte) byte {
if 'A' <= && <= 'Z' {
return + ('a' - 'A')
}
return
}