package handler import ( "bufio" "io" ) type ( s3UnsignedChunkReader struct { reader *bufio.Reader trailers map[string]string buffer []byte offset int err error } ) func (c *s3UnsignedChunkReader) Close() (err error) { return nil } func (c *s3UnsignedChunkReader) Read(buf []byte) (num int, err error) { if c.offset > 0 { num = copy(buf, c.buffer[c.offset:]) if num == len(buf) { c.offset += num return num, nil } c.offset = 0 buf = buf[num:] } var size int var b byte for { b, err = c.reader.ReadByte() if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { c.err = err return num, c.err } if b == '\r' { break } // Manually deserialize the size since AWS specified // the chunk size to be of variable width. In particular, // a size of 16 is encoded as `10` while a size of 64 KB // is `10000`. switch { case b >= '0' && b <= '9': size = size<<4 | int(b-'0') case b >= 'a' && b <= 'f': size = size<<4 | int(b-('a'-10)) case b >= 'A' && b <= 'F': size = size<<4 | int(b-('A'-10)) default: c.err = errMalformedChunkedEncoding return num, c.err } if size > maxChunkSize { c.err = errGiantChunk return num, c.err } } if b != '\r' { c.err = errMalformedChunkedEncoding return num, c.err } b, err = c.reader.ReadByte() if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { c.err = err return num, c.err } if b != '\n' { c.err = errMalformedChunkedEncoding return num, c.err } if cap(c.buffer) < size { c.buffer = make([]byte, size) } else { c.buffer = c.buffer[:size] } // Now, we read the payload and compute its SHA-256 hash. _, err = io.ReadFull(c.reader, c.buffer) if err == io.EOF && size != 0 { err = io.ErrUnexpectedEOF } if err != nil && err != io.EOF { c.err = err return num, c.err } // If the chunk size is zero we return io.EOF. As specified by AWS, // only the last chunk is zero-sized. if size == 0 { var k, v string for err == nil { k, err = c.reader.ReadString(':') if err != nil { if err == io.EOF { break } c.err = errMalformedTrailerHeaders return num, c.err } v, err = c.reader.ReadString('\n') if err != nil { c.err = errMalformedTrailerHeaders return num, c.err } c.trailers[k[:len(k)-1]] = v[:len(v)-1] } c.err = io.EOF return num, c.err } b, err = c.reader.ReadByte() if b != '\r' || err != nil { c.err = errMalformedChunkedEncoding return num, c.err } b, err = c.reader.ReadByte() if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { c.err = err return num, c.err } if b != '\n' { c.err = errMalformedChunkedEncoding return num, c.err } c.offset = copy(buf, c.buffer) num += c.offset return num, err } func (c *s3UnsignedChunkReader) TrailerHeaders() map[string]string { return c.trailers } func newUnsignedChunkedReader(body io.Reader) (*s3UnsignedChunkReader, error) { return &s3UnsignedChunkReader{ reader: bufio.NewReader(body), trailers: map[string]string{}, buffer: make([]byte, 64*1024), }, nil }