package checksum
import (
)
const (
crlf = "\r\n"
defaultChunkLength = 1024 * 64
awsTrailerHeaderName = "x-amz-trailer"
decodedContentLengthHeaderName = "x-amz-decoded-content-length"
contentEncodingHeaderName = "content-encoding"
awsChunkedContentEncodingHeaderValue = "aws-chunked"
trailerKeyValueSeparator = ":"
)
var (
crlfBytes = []byte(crlf)
finalChunkBytes = []byte("0" + crlf)
)
type awsChunkedEncodingOptions struct {
StreamLength int64
Trailers map[string]awsChunkedTrailerValue
ChunkLength int
}
type awsChunkedTrailerValue struct {
Get func() (string, error)
Length int
}
type awsChunkedEncoding struct {
options awsChunkedEncodingOptions
encodedStream io.Reader
trailerEncodedLength int
}
func (
io.Reader,
...func(*awsChunkedEncodingOptions),
) *awsChunkedEncoding {
:= awsChunkedEncodingOptions{
Trailers: map[string]awsChunkedTrailerValue{},
StreamLength: -1,
ChunkLength: -1,
}
for , := range {
(&)
}
var io.Reader
if .ChunkLength != -1 || .StreamLength == -1 {
if .ChunkLength == -1 {
.ChunkLength = defaultChunkLength
}
= newBufferedAWSChunkReader(, .ChunkLength)
} else {
= newUnsignedChunkReader(, .StreamLength)
}
:= newAWSChunkedTrailerReader(.Trailers)
return &awsChunkedEncoding{
options: ,
encodedStream: io.MultiReader(,
,
bytes.NewBuffer(crlfBytes),
),
trailerEncodedLength: .EncodedLength(),
}
}
func ( *awsChunkedEncoding) () int64 {
var int64
if .options.StreamLength == -1 || .trailerEncodedLength == -1 {
return -1
}
if .options.StreamLength != 0 {
if .options.ChunkLength == -1 {
+= getUnsignedChunkBytesLength(.options.StreamLength)
} else {
:= .options.StreamLength / int64(.options.ChunkLength)
+= * getUnsignedChunkBytesLength(int64(.options.ChunkLength))
if := .options.StreamLength % int64(.options.ChunkLength); != 0 {
+= getUnsignedChunkBytesLength()
}
}
}
+= int64(len(finalChunkBytes))
+= int64(.trailerEncodedLength)
+= int64(len(crlf))
return
}
func ( int64) int64 {
:= strconv.FormatInt(, 16)
return int64(len()) + int64(len(crlf)) + + int64(len(crlf))
}
func ( *awsChunkedEncoding) () map[string][]string {
:= map[string][]string{
contentEncodingHeaderName: {
awsChunkedContentEncodingHeaderValue,
},
}
if len(.options.Trailers) != 0 {
:= make([]string, 0, len(.options.Trailers))
for := range .options.Trailers {
= append(, strings.ToLower())
}
[awsTrailerHeaderName] =
}
return
}
func ( *awsChunkedEncoding) ( []byte) ( int, error) {
return .encodedStream.Read()
}
type awsChunkedTrailerReader struct {
reader *bytes.Buffer
trailers map[string]awsChunkedTrailerValue
trailerEncodedLength int
}
func ( map[string]awsChunkedTrailerValue) *awsChunkedTrailerReader {
return &awsChunkedTrailerReader{
trailers: ,
trailerEncodedLength: trailerEncodedLength(),
}
}
func ( map[string]awsChunkedTrailerValue) ( int) {
for , := range {
+= len() + len(trailerKeyValueSeparator)
:= .Length
if == -1 {
return -1
}
+= + len(crlf)
}
return
}
func ( *awsChunkedTrailerReader) () ( int) {
return .trailerEncodedLength
}
func ( *awsChunkedTrailerReader) ( []byte) (int, error) {
if .trailerEncodedLength == 0 {
return 0, io.EOF
}
if .reader == nil {
:= .trailerEncodedLength
if .trailerEncodedLength == -1 {
= 0
}
.reader = bytes.NewBuffer(make([]byte, 0, ))
for , := range .trailers {
.reader.WriteString()
.reader.WriteString(trailerKeyValueSeparator)
, := .Get()
if != nil {
return 0, fmt.Errorf("failed to get trailer value, %w", )
}
.reader.WriteString()
.reader.WriteString(crlf)
}
}
return .reader.Read()
}
func ( io.Reader, int64) io.Reader {
if == -1 {
return newBufferedAWSChunkReader(, defaultChunkLength)
}
var bytes.Buffer
if == 0 {
.Write(finalChunkBytes)
return &
}
.WriteString(crlf)
.Write(finalChunkBytes)
var bytes.Buffer
.WriteString(strconv.FormatInt(, 16))
.WriteString(crlf)
return io.MultiReader(
&,
,
&,
)
}
type bufferedAWSChunkReader struct {
reader io.Reader
chunkSize int
chunkSizeStr string
headerBuffer *bytes.Buffer
chunkBuffer *bytes.Buffer
multiReader io.Reader
multiReaderLen int
endChunkDone bool
}
func ( io.Reader, int) *bufferedAWSChunkReader {
return &bufferedAWSChunkReader{
reader: ,
chunkSize: ,
chunkSizeStr: strconv.FormatInt(int64(), 16),
headerBuffer: bytes.NewBuffer(make([]byte, 0, 64)),
chunkBuffer: bytes.NewBuffer(make([]byte, 0, +len(crlf))),
}
}
func ( *bufferedAWSChunkReader) ( []byte) ( int, error) {
if .multiReaderLen == 0 && .endChunkDone {
return 0, io.EOF
}
if .multiReader == nil || .multiReaderLen == 0 {
.multiReader, .multiReaderLen, = .newMultiReader()
if != nil {
return 0,
}
}
, = .multiReader.Read()
.multiReaderLen -=
if == io.EOF && !.endChunkDone {
= nil
}
return ,
}
func ( *bufferedAWSChunkReader) () (io.Reader, int, error) {
, := io.Copy(.chunkBuffer, io.LimitReader(.reader, int64(.chunkSize)))
if != nil {
return nil, 0,
}
if == 0 {
.headerBuffer.Reset()
.headerBuffer.WriteString("0")
.headerBuffer.WriteString(crlf)
.endChunkDone = true
return .headerBuffer, .headerBuffer.Len(), nil
}
.chunkBuffer.WriteString(crlf)
:= .chunkSizeStr
if int() != .chunkSize {
= strconv.FormatInt(, 16)
}
.headerBuffer.Reset()
.headerBuffer.WriteString()
.headerBuffer.WriteString(crlf)
return io.MultiReader(
.headerBuffer,
.chunkBuffer,
), .headerBuffer.Len() + .chunkBuffer.Len(), nil
}