@ -175,6 +175,7 @@ func newSignV4ChunkedReader(req *http.Request) (io.Reader, APIErrorCode) {
seedSignature : seedSignature ,
seedSignature : seedSignature ,
seedDate : seedDate ,
seedDate : seedDate ,
chunkSHA256Writer : sha256 . New ( ) ,
chunkSHA256Writer : sha256 . New ( ) ,
state : readChunkHeader ,
} , ErrNone
} , ErrNone
}
}
@ -184,7 +185,8 @@ type s3ChunkedReader struct {
reader * bufio . Reader
reader * bufio . Reader
seedSignature string
seedSignature string
seedDate time . Time
seedDate time . Time
dataChunkRead bool
state chunkState
lastChunk bool
chunkSignature string
chunkSignature string
chunkSHA256Writer hash . Hash // Calculates sha256 of chunk data.
chunkSHA256Writer hash . Hash // Calculates sha256 of chunk data.
n uint64 // Unread bytes in chunk
n uint64 // Unread bytes in chunk
@ -207,99 +209,125 @@ func (cr *s3ChunkedReader) readS3ChunkHeader() {
if cr . n == 0 {
if cr . n == 0 {
cr . err = io . EOF
cr . err = io . EOF
}
}
// is the data part already read?, set this to false.
cr . dataChunkRead = false
// Reset sha256 hasher for a fresh start.
cr . chunkSHA256Writer . Reset ( )
// Save the incoming chunk signature.
// Save the incoming chunk signature.
cr . chunkSignature = string ( hexChunkSignature )
cr . chunkSignature = string ( hexChunkSignature )
}
}
// Validate if the underlying buffer has chunk header.
type chunkState int
func ( cr * s3ChunkedReader ) s3ChunkHeaderAvailable ( ) bool {
n := cr . reader . Buffered ( )
const (
if n > 0 {
readChunkHeader chunkState = iota
// Peek without seeking to look for trailing '\n'.
readChunkTrailer
peek , _ := cr . reader . Peek ( n )
readChunk
return bytes . IndexByte ( peek , '\n' ) >= 0
verifyChunk
)
func ( cs chunkState ) String ( ) string {
stateString := ""
switch cs {
case readChunkHeader :
stateString = "readChunkHeader"
case readChunkTrailer :
stateString = "readChunkTrailer"
case readChunk :
stateString = "readChunk"
case verifyChunk :
stateString = "verifyChunk"
}
}
return false
return stateString
}
}
// Read - implements `io.Reader`, which transparently decodes
// Read - implements `io.Reader`, which transparently decodes
// the incoming AWS Signature V4 streaming signature.
// the incoming AWS Signature V4 streaming signature.
func ( cr * s3ChunkedReader ) Read ( buf [ ] byte ) ( n int , err error ) {
func ( cr * s3ChunkedReader ) Read ( buf [ ] byte ) ( n int , err error ) {
for cr . err == nil {
for {
if cr . n == 0 {
switch cr . state {
// For no chunk header available, we don't have to
case readChunkHeader :
// proceed to read again.
cr . readS3ChunkHeader ( )
if n > 0 && ! cr . s3ChunkHeaderAvailable ( ) {
// If we're at the end of a chunk.
// We've read enough. Don't potentially block
if cr . n == 0 && cr . err == io . EOF {
// reading a new chunk header.
cr . state = readChunkTrailer
break
cr . lastChunk = true
continue
}
}
// If the chunk has been read, proceed to validate the rolling signature.
if cr . err != nil {
if cr . dataChunkRead {
return 0 , cr . err
// Calculate the hashed chunk.
}
hashedChunk := hex . EncodeToString ( cr . chunkSHA256Writer . Sum ( nil ) )
cr . state = readChunk
// Calculate the chunk signature.
case readChunkTrailer :
newSignature := getChunkSignature ( cr . seedSignature , cr . seedDate , hashedChunk )
cr . err = readCRLF ( cr . reader )
if cr . chunkSignature != newSignature {
if cr . err != nil {
// Chunk signature doesn't match we return signature does not match.
return 0 , errMalformedEncoding
cr . err = errSignatureMismatch
}
break
cr . state = verifyChunk
case readChunk :
// There is no more space left in the request buffer.
if len ( buf ) == 0 {
return n , nil
}
rbuf := buf
// The request buffer is larger than the current chunk size.
// Read only the current chunk from the underlying reader.
if uint64 ( len ( rbuf ) ) > cr . n {
rbuf = rbuf [ : cr . n ]
}
var n0 int
n0 , cr . err = cr . reader . Read ( rbuf )
if cr . err != nil {
// We have lesser than chunk size advertised in chunkHeader, this is 'unexpected'.
if cr . err == io . EOF {
cr . err = io . ErrUnexpectedEOF
}
}
// Newly calculated signature becomes the seed for the next chunk
return 0 , cr . err
// this follows the chaining.
}
cr . seedSignature = newSignature
// Calculate sha256.
cr . chunkSHA256Writer . Write ( rbuf [ : n0 ] )
// Update the bytes read into request buffer so far.
n += n0
buf = buf [ n0 : ]
// Update bytes to be read of the current chunk before verifying chunk's signature.
cr . n -= uint64 ( n0 )
// If we're at the end of a chunk.
if cr . n == 0 {
cr . state = readChunkTrailer
continue
}
case verifyChunk :
// Calculate the hashed chunk.
hashedChunk := hex . EncodeToString ( cr . chunkSHA256Writer . Sum ( nil ) )
// Calculate the chunk signature.
newSignature := getChunkSignature ( cr . seedSignature , cr . seedDate , hashedChunk )
if cr . chunkSignature != newSignature {
// Chunk signature doesn't match we return signature does not match.
cr . err = errSignatureMismatch
return 0 , cr . err
}
// Newly calculated signature becomes the seed for the next chunk
// this follows the chaining.
cr . seedSignature = newSignature
cr . chunkSHA256Writer . Reset ( )
cr . state = readChunkHeader
if cr . lastChunk {
return n , nil
}
}
// Proceed to read the next chunk header.
cr . readS3ChunkHeader ( )
continue
}
// With requested buffer of zero length, no need to read further.
if len ( buf ) == 0 {
break
}
rbuf := buf
// Make sure to read only the specified payload size, stagger
// the rest for subsequent requests.
if uint64 ( len ( rbuf ) ) > cr . n {
rbuf = rbuf [ : cr . n ]
}
var n0 int
n0 , cr . err = cr . reader . Read ( rbuf )
// Calculate sha256.
cr . chunkSHA256Writer . Write ( rbuf [ : n0 ] )
// Set since we have read the chunk read.
cr . dataChunkRead = true
n += n0
buf = buf [ n0 : ]
// Decrements the 'cr.n' for future reads.
cr . n -= uint64 ( n0 )
// If we're at the end of a chunk.
if cr . n == 0 && cr . err == nil {
// Read the next two bytes to verify if they are "\r\n".
cr . err = checkCRLF ( cr . reader )
}
}
}
}
// Return number of bytes read, and error if any.
return n , cr . err
}
}
// check CRLF - check if reader only has '\r\n' CRLF character.
// readCRLF - check if reader only has '\r\n' CRLF character.
// returns malformed encoding if it doesn't.
// returns malformed encoding if it doesn't.
func checkCRLF ( reader io . Reader ) ( err error ) {
func readCRLF ( reader io . Reader ) error {
var buf = make ( [ ] byte , 2 )
buf := make ( [ ] byte , 2 )
if _ , err = io . ReadFull ( reader , buf [ : 2 ] ) ; err == nil {
_ , err := io . ReadFull ( reader , buf [ : 2 ] )
if buf [ 0 ] != '\r' || buf [ 1 ] != '\n' {
if err != nil {
err = errMalformedEncoding
return err
}
}
if buf [ 0 ] != '\r' || buf [ 1 ] != '\n' {
return errMalformedEncoding
}
}
return err
return nil
}
}
// Read a line of bytes (up to \n) from b.
// Read a line of bytes (up to \n) from b.