mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
105 lines
3.1 KiB
Go
105 lines
3.1 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package download
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
type representationContract uint8
|
|
|
|
const (
|
|
representationUnspecified representationContract = iota
|
|
immutableRepresentation
|
|
mutableRepresentation
|
|
)
|
|
|
|
// Source binds a transport to its representation stability.
|
|
type Source struct {
|
|
transport Transport
|
|
representation representationContract
|
|
}
|
|
|
|
// ImmutableSource allows multipart reads without a validator.
|
|
func ImmutableSource(transport Transport) Source {
|
|
return Source{transport: transport, representation: immutableRepresentation}
|
|
}
|
|
|
|
// MutableSource requires a strong ETag before combining responses.
|
|
func MutableSource(transport Transport) Source {
|
|
return Source{transport: transport, representation: mutableRepresentation}
|
|
}
|
|
|
|
type representationSession struct {
|
|
transport Transport
|
|
contract representationContract
|
|
totalSize int64
|
|
validator string
|
|
hasValidator bool
|
|
}
|
|
|
|
func newRepresentationSession(source Source, first contentRange, header http.Header) *representationSession {
|
|
validator, hasValidator := strongETag(header)
|
|
return &representationSession{
|
|
transport: source.transport,
|
|
contract: source.representation,
|
|
totalSize: first.total,
|
|
validator: validator,
|
|
hasValidator: hasValidator,
|
|
}
|
|
}
|
|
|
|
func (s *representationSession) multipartAllowed() bool {
|
|
return s.contract == immutableRepresentation || s.hasValidator
|
|
}
|
|
|
|
func (s *representationSession) request(byteRange ByteRange) Request {
|
|
request := Request{Range: &byteRange}
|
|
if s.hasValidator {
|
|
request.IfRange = s.validator
|
|
}
|
|
return request
|
|
}
|
|
|
|
func (s *representationSession) validatePartial(header http.Header, got contentRange, requested ByteRange) error {
|
|
if got.start != requested.Start {
|
|
return protocolError("range response is %s, want it to start at byte %d", got, requested.Start)
|
|
}
|
|
if got.end > requested.End {
|
|
return protocolError("range response is %s, outside requested %s", got, requested.HeaderValue())
|
|
}
|
|
if got.total != s.totalSize {
|
|
if s.hasValidator {
|
|
return protocolError("server ignored If-Range: range response is %s, want total %d", got, s.totalSize)
|
|
}
|
|
return representationChanged("resource size changed while downloading: range response is %s, want total %d", got, s.totalSize)
|
|
}
|
|
return s.observeValidator(header)
|
|
}
|
|
|
|
func (s *representationSession) validateFull(resp *http.Response) error {
|
|
if resp.ContentLength >= 0 && resp.ContentLength != s.totalSize {
|
|
return representationChanged("resource size changed while downloading: full response has %d bytes, want %d", resp.ContentLength, s.totalSize)
|
|
}
|
|
return s.observeValidator(resp.Header)
|
|
}
|
|
|
|
func (s *representationSession) observeValidator(header http.Header) error {
|
|
got, ok := strongETag(header)
|
|
if !s.hasValidator {
|
|
if ok {
|
|
s.validator = got
|
|
s.hasValidator = true
|
|
}
|
|
return nil
|
|
}
|
|
if !ok {
|
|
return protocolError("response carries no usable validator, so it cannot be tied to the %q the transfer started from", s.validator)
|
|
}
|
|
if got != s.validator {
|
|
return representationChanged("response carries validator %q, want %q", got, s.validator)
|
|
}
|
|
return nil
|
|
}
|