Blame vendor/github.com/aws/aws-sdk-go/service/s3/body_hash.go

Packit 63bb0d
package s3
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"bytes"
Packit 63bb0d
	"crypto/md5"
Packit 63bb0d
	"crypto/sha256"
Packit 63bb0d
	"encoding/base64"
Packit 63bb0d
	"encoding/hex"
Packit 63bb0d
	"fmt"
Packit 63bb0d
	"hash"
Packit 63bb0d
	"io"
Packit 63bb0d
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws/awserr"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/aws/request"
Packit 63bb0d
	"github.com/aws/aws-sdk-go/internal/sdkio"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
const (
Packit 63bb0d
	contentMD5Header    = "Content-Md5"
Packit 63bb0d
	contentSha256Header = "X-Amz-Content-Sha256"
Packit 63bb0d
	amzTeHeader         = "X-Amz-Te"
Packit 63bb0d
	amzTxEncodingHeader = "X-Amz-Transfer-Encoding"
Packit 63bb0d
Packit 63bb0d
	appendMD5TxEncoding = "append-md5"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// contentMD5 computes and sets the HTTP Content-MD5 header for requests that
Packit 63bb0d
// require it.
Packit 63bb0d
func contentMD5(r *request.Request) {
Packit 63bb0d
	h := md5.New()
Packit 63bb0d
Packit 63bb0d
	if !aws.IsReaderSeekable(r.Body) {
Packit 63bb0d
		if r.Config.Logger != nil {
Packit 63bb0d
			r.Config.Logger.Log(fmt.Sprintf(
Packit 63bb0d
				"Unable to compute Content-MD5 for unseekable body, S3.%s",
Packit 63bb0d
				r.Operation.Name))
Packit 63bb0d
		}
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if _, err := copySeekableBody(h, r.Body); err != nil {
Packit 63bb0d
		r.Error = awserr.New("ContentMD5", "failed to compute body MD5", err)
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// encode the md5 checksum in base64 and set the request header.
Packit 63bb0d
	v := base64.StdEncoding.EncodeToString(h.Sum(nil))
Packit 63bb0d
	r.HTTPRequest.Header.Set(contentMD5Header, v)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// computeBodyHashes will add Content MD5 and Content Sha256 hashes to the
Packit 63bb0d
// request. If the body is not seekable or S3DisableContentMD5Validation set
Packit 63bb0d
// this handler will be ignored.
Packit 63bb0d
func computeBodyHashes(r *request.Request) {
Packit 63bb0d
	if aws.BoolValue(r.Config.S3DisableContentMD5Validation) {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
	if r.IsPresigned() {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
	if r.Error != nil || !aws.IsReaderSeekable(r.Body) {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	var md5Hash, sha256Hash hash.Hash
Packit 63bb0d
	hashers := make([]io.Writer, 0, 2)
Packit 63bb0d
Packit 63bb0d
	// Determine upfront which hashes can be set without overriding user
Packit 63bb0d
	// provide header data.
Packit 63bb0d
	if v := r.HTTPRequest.Header.Get(contentMD5Header); len(v) == 0 {
Packit 63bb0d
		md5Hash = md5.New()
Packit 63bb0d
		hashers = append(hashers, md5Hash)
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if v := r.HTTPRequest.Header.Get(contentSha256Header); len(v) == 0 {
Packit 63bb0d
		sha256Hash = sha256.New()
Packit 63bb0d
		hashers = append(hashers, sha256Hash)
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Create the destination writer based on the hashes that are not already
Packit 63bb0d
	// provided by the user.
Packit 63bb0d
	var dst io.Writer
Packit 63bb0d
	switch len(hashers) {
Packit 63bb0d
	case 0:
Packit 63bb0d
		return
Packit 63bb0d
	case 1:
Packit 63bb0d
		dst = hashers[0]
Packit 63bb0d
	default:
Packit 63bb0d
		dst = io.MultiWriter(hashers...)
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if _, err := copySeekableBody(dst, r.Body); err != nil {
Packit 63bb0d
		r.Error = awserr.New("BodyHashError", "failed to compute body hashes", err)
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// For the hashes created, set the associated headers that the user did not
Packit 63bb0d
	// already provide.
Packit 63bb0d
	if md5Hash != nil {
Packit 63bb0d
		sum := make([]byte, md5.Size)
Packit 63bb0d
		encoded := make([]byte, md5Base64EncLen)
Packit 63bb0d
Packit 63bb0d
		base64.StdEncoding.Encode(encoded, md5Hash.Sum(sum[0:0]))
Packit 63bb0d
		r.HTTPRequest.Header[contentMD5Header] = []string{string(encoded)}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if sha256Hash != nil {
Packit 63bb0d
		encoded := make([]byte, sha256HexEncLen)
Packit 63bb0d
		sum := make([]byte, sha256.Size)
Packit 63bb0d
Packit 63bb0d
		hex.Encode(encoded, sha256Hash.Sum(sum[0:0]))
Packit 63bb0d
		r.HTTPRequest.Header[contentSha256Header] = []string{string(encoded)}
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
const (
Packit 63bb0d
	md5Base64EncLen = (md5.Size + 2) / 3 * 4 // base64.StdEncoding.EncodedLen
Packit 63bb0d
	sha256HexEncLen = sha256.Size * 2        // hex.EncodedLen
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
func copySeekableBody(dst io.Writer, src io.ReadSeeker) (int64, error) {
Packit 63bb0d
	curPos, err := src.Seek(0, sdkio.SeekCurrent)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return 0, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// hash the body.  seek back to the first position after reading to reset
Packit 63bb0d
	// the body for transmission.  copy errors may be assumed to be from the
Packit 63bb0d
	// body.
Packit 63bb0d
	n, err := io.Copy(dst, src)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return n, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	_, err = src.Seek(curPos, sdkio.SeekStart)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return n, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return n, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// Adds the x-amz-te: append_md5 header to the request. This requests the service
Packit 63bb0d
// responds with a trailing MD5 checksum.
Packit 63bb0d
//
Packit 63bb0d
// Will not ask for append MD5 if disabled, the request is presigned or,
Packit 63bb0d
// or the API operation does not support content MD5 validation.
Packit 63bb0d
func askForTxEncodingAppendMD5(r *request.Request) {
Packit 63bb0d
	if aws.BoolValue(r.Config.S3DisableContentMD5Validation) {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
	if r.IsPresigned() {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
	r.HTTPRequest.Header.Set(amzTeHeader, appendMD5TxEncoding)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func useMD5ValidationReader(r *request.Request) {
Packit 63bb0d
	if r.Error != nil {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if v := r.HTTPResponse.Header.Get(amzTxEncodingHeader); v != appendMD5TxEncoding {
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	var bodyReader *io.ReadCloser
Packit 63bb0d
	var contentLen int64
Packit 63bb0d
	switch tv := r.Data.(type) {
Packit 63bb0d
	case *GetObjectOutput:
Packit 63bb0d
		bodyReader = &tv.Body
Packit 63bb0d
		contentLen = aws.Int64Value(tv.ContentLength)
Packit 63bb0d
		// Update ContentLength hiden the trailing MD5 checksum.
Packit 63bb0d
		tv.ContentLength = aws.Int64(contentLen - md5.Size)
Packit 63bb0d
		tv.ContentRange = aws.String(r.HTTPResponse.Header.Get("X-Amz-Content-Range"))
Packit 63bb0d
	default:
Packit 63bb0d
		r.Error = awserr.New("ChecksumValidationError",
Packit 63bb0d
			fmt.Sprintf("%s: %s header received on unsupported API, %s",
Packit 63bb0d
				amzTxEncodingHeader, appendMD5TxEncoding, r.Operation.Name,
Packit 63bb0d
			), nil)
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if contentLen < md5.Size {
Packit 63bb0d
		r.Error = awserr.New("ChecksumValidationError",
Packit 63bb0d
			fmt.Sprintf("invalid Content-Length %d for %s %s",
Packit 63bb0d
				contentLen, appendMD5TxEncoding, amzTxEncodingHeader,
Packit 63bb0d
			), nil)
Packit 63bb0d
		return
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// Wrap and swap the response body reader with the validation reader.
Packit 63bb0d
	*bodyReader = newMD5ValidationReader(*bodyReader, contentLen-md5.Size)
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
type md5ValidationReader struct {
Packit 63bb0d
	rawReader io.ReadCloser
Packit 63bb0d
	payload   io.Reader
Packit 63bb0d
	hash      hash.Hash
Packit 63bb0d
Packit 63bb0d
	payloadLen int64
Packit 63bb0d
	read       int64
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func newMD5ValidationReader(reader io.ReadCloser, payloadLen int64) *md5ValidationReader {
Packit 63bb0d
	h := md5.New()
Packit 63bb0d
	return &md5ValidationReader{
Packit 63bb0d
		rawReader:  reader,
Packit 63bb0d
		payload:    io.TeeReader(&io.LimitedReader{R: reader, N: payloadLen}, h),
Packit 63bb0d
		hash:       h,
Packit 63bb0d
		payloadLen: payloadLen,
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (v *md5ValidationReader) Read(p []byte) (n int, err error) {
Packit 63bb0d
	n, err = v.payload.Read(p)
Packit 63bb0d
	if err != nil && err != io.EOF {
Packit 63bb0d
		return n, err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	v.read += int64(n)
Packit 63bb0d
Packit 63bb0d
	if err == io.EOF {
Packit 63bb0d
		if v.read != v.payloadLen {
Packit 63bb0d
			return n, io.ErrUnexpectedEOF
Packit 63bb0d
		}
Packit 63bb0d
		expectSum := make([]byte, md5.Size)
Packit 63bb0d
		actualSum := make([]byte, md5.Size)
Packit 63bb0d
		if _, sumReadErr := io.ReadFull(v.rawReader, expectSum); sumReadErr != nil {
Packit 63bb0d
			return n, sumReadErr
Packit 63bb0d
		}
Packit 63bb0d
		actualSum = v.hash.Sum(actualSum[0:0])
Packit 63bb0d
		if !bytes.Equal(expectSum, actualSum) {
Packit 63bb0d
			return n, awserr.New("InvalidChecksum",
Packit 63bb0d
				fmt.Sprintf("expected MD5 checksum %s, got %s",
Packit 63bb0d
					hex.EncodeToString(expectSum),
Packit 63bb0d
					hex.EncodeToString(actualSum),
Packit 63bb0d
				),
Packit 63bb0d
				nil)
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return n, err
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func (v *md5ValidationReader) Close() error {
Packit 63bb0d
	return v.rawReader.Close()
Packit 63bb0d
}