package checksum

import (
	
	
	
	
	
	

	v4 
	
	smithyhttp 
)

const (
	contentMD5Header                           = "Content-Md5"
	streamingUnsignedPayloadTrailerPayloadHash = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"
)

// computedInputChecksumsKey is the metadata key for recording the algorithm the
// checksum was computed for and the checksum value.
type computedInputChecksumsKey struct{}

// GetComputedInputChecksums returns the map of checksum algorithm to their
// computed value stored in the middleware Metadata. Returns false if no values
// were stored in the Metadata.
func ( middleware.Metadata) (map[string]string, bool) {
	,  := .Get(computedInputChecksumsKey{}).(map[string]string)
	return , 
}

// SetComputedInputChecksums stores the map of checksum algorithm to their
// computed value in the middleware Metadata. Overwrites any values that
// currently exist in the metadata.
func ( *middleware.Metadata,  map[string]string) {
	.Set(computedInputChecksumsKey{}, )
}

// computeInputPayloadChecksum middleware computes payload checksum
type computeInputPayloadChecksum struct {
	// Enables support for wrapping the serialized input payload with a
	// content-encoding: aws-check wrapper, and including a trailer for the
	// algorithm's checksum value.
	//
	// The checksum will not be computed, nor added as trailing checksum, if
	// the Algorithm's header is already set on the request.
	EnableTrailingChecksum bool

	// States that a checksum is required to be included for the operation. If
	// Input does not specify a checksum, fallback to built in MD5 checksum is
	// used.
	//
	// Replaces smithy-go's ContentChecksum middleware.
	RequireChecksum bool

	// Enables support for computing the SHA256 checksum of input payloads
	// along with the algorithm specified checksum. Prevents downstream
	// middleware handlers (computePayloadSHA256) re-reading the payload.
	//
	// The SHA256 payload hash will only be used for computed for requests
	// that are not TLS, or do not enable trailing checksums.
	//
	// The SHA256 payload hash will not be computed, if the Algorithm's header
	// is already set on the request.
	EnableComputePayloadHash bool

	// Enables support for setting the aws-chunked decoded content length
	// header for the decoded length of the underlying stream. Will only be set
	// when used with trailing checksums, and aws-chunked content-encoding.
	EnableDecodedContentLengthHeader bool

	buildHandlerRun        bool
	deferToFinalizeHandler bool
}

// ID provides the middleware's identifier.
func ( *computeInputPayloadChecksum) () string {
	return "AWSChecksum:ComputeInputPayloadChecksum"
}

type computeInputHeaderChecksumError struct {
	Msg string
	Err error
}

func ( computeInputHeaderChecksumError) () string {
	const  = "compute input header checksum failed"

	if .Err != nil {
		return fmt.Sprintf("%s, %s, %v", , .Msg, .Err)
	}

	return fmt.Sprintf("%s, %s", , .Msg)
}
func ( computeInputHeaderChecksumError) () error { return .Err }

// HandleBuild handles computing the payload's checksum, in the following cases:
//   - Is HTTP, not HTTPS
//   - RequireChecksum is true, and no checksums were specified via the Input
//   - Trailing checksums are not supported
//
// The build handler must be inserted in the stack before ContentPayloadHash
// and after ComputeContentLength.
func ( *computeInputPayloadChecksum) (
	 context.Context,  middleware.BuildInput,  middleware.BuildHandler,
) (
	 middleware.BuildOutput,  middleware.Metadata,  error,
) {
	.buildHandlerRun = true

	,  := .Request.(*smithyhttp.Request)
	if ! {
		return , , computeInputHeaderChecksumError{
			Msg: fmt.Sprintf("unknown request type %T", ),
		}
	}

	var  Algorithm
	var  string
	defer func() {
		if  == "" ||  == "" ||  != nil {
			return
		}

		// Record the checksum and algorithm that was computed
		SetComputedInputChecksums(&, map[string]string{
			string(): ,
		})
	}()

	// If no algorithm was specified, and the operation requires a checksum,
	// fallback to the legacy content MD5 checksum.
	, ,  = getInputAlgorithm()
	if  != nil {
		return , , 
	} else if ! {
		if .RequireChecksum {
			,  = setMD5Checksum(, )
			if  != nil {
				return , , computeInputHeaderChecksumError{
					Msg: "failed to compute stream's MD5 checksum",
					Err: ,
				}
			}
			 = Algorithm("MD5")
		}
		return .HandleBuild(, )
	}

	// If the checksum header is already set nothing to do.
	 := AlgorithmHTTPHeader()
	if  = .Header.Get();  != "" {
		return .HandleBuild(, )
	}

	 := .EnableComputePayloadHash
	if  := v4.GetPayloadHash();  != "" {
		 = false
	}

	 := .GetStream()
	,  := getRequestStreamLength()
	if  != nil {
		return , , computeInputHeaderChecksumError{
			Msg: "failed to determine stream length",
			Err: ,
		}
	}

	// If trailing checksums are supported, the request is HTTPS, and the
	// stream is not nil or empty, there is nothing to do in the build stage.
	// The checksum will be added to the request as a trailing checksum in the
	// finalize handler.
	//
	// Nil and empty streams will always be handled as a request header,
	// regardless if the operation supports trailing checksums or not.
	if .IsHTTPS() {
		if  != nil &&  != 0 && .EnableTrailingChecksum {
			if .EnableComputePayloadHash {
				// payload hash is set as header in Build middleware handler,
				// ContentSHA256Header.
				 = v4.SetPayloadHash(, streamingUnsignedPayloadTrailerPayloadHash)
			}

			.deferToFinalizeHandler = true
			return .HandleBuild(, )
		}

		// If trailing checksums are not enabled but protocol is still HTTPS
		// disabling computing the payload hash. Downstream middleware  handler
		// (ComputetPayloadHash) will set the payload hash to unsigned payload,
		// if signing was used.
		 = false
	}

	// Only seekable streams are supported for non-trailing checksums, because
	// the stream needs to be rewound before the handler can continue.
	if  != nil && !.IsStreamSeekable() {
		return , , computeInputHeaderChecksumError{
			Msg: "unseekable stream is not supported without TLS and trailing checksum",
		}
	}

	var  string
	, ,  = computeStreamChecksum(
		, , )
	if  != nil {
		return , , computeInputHeaderChecksumError{
			Msg: "failed to compute stream checksum",
			Err: ,
		}
	}

	if  := .RewindStream();  != nil {
		return , , computeInputHeaderChecksumError{
			Msg: "failed to rewind stream",
			Err: ,
		}
	}

	.Header.Set(, )

	if  {
		 = v4.SetPayloadHash(, )
	}

	return .HandleBuild(, )
}

type computeInputTrailingChecksumError struct {
	Msg string
	Err error
}

func ( computeInputTrailingChecksumError) () string {
	const  = "compute input trailing checksum failed"

	if .Err != nil {
		return fmt.Sprintf("%s, %s, %v", , .Msg, .Err)
	}

	return fmt.Sprintf("%s, %s", , .Msg)
}
func ( computeInputTrailingChecksumError) () error { return .Err }

// HandleFinalize handles computing the payload's checksum, in the following cases:
//   - Is HTTPS, not HTTP
//   - A checksum was specified via the Input
//   - Trailing checksums are supported.
//
// The finalize handler must be inserted in the stack before Signing, and after Retry.
func ( *computeInputPayloadChecksum) (
	 context.Context,  middleware.FinalizeInput,  middleware.FinalizeHandler,
) (
	 middleware.FinalizeOutput,  middleware.Metadata,  error,
) {
	if !.deferToFinalizeHandler {
		if !.buildHandlerRun {
			return , , computeInputTrailingChecksumError{
				Msg: "build handler was removed without also removing finalize handler",
			}
		}
		return .HandleFinalize(, )
	}

	,  := .Request.(*smithyhttp.Request)
	if ! {
		return , , computeInputTrailingChecksumError{
			Msg: fmt.Sprintf("unknown request type %T", ),
		}
	}

	// Trailing checksums are only supported when TLS is enabled.
	if !.IsHTTPS() {
		return , , computeInputTrailingChecksumError{
			Msg: "HTTPS required",
		}
	}

	// If no algorithm was specified, there is nothing to do.
	, ,  := getInputAlgorithm()
	if  != nil {
		return , , computeInputTrailingChecksumError{
			Msg: "failed to get algorithm",
			Err: ,
		}
	} else if ! {
		return , , computeInputTrailingChecksumError{
			Msg: "no algorithm specified",
		}
	}

	// If the checksum header is already set before finalize could run, there
	// is nothing to do.
	 := AlgorithmHTTPHeader()
	if .Header.Get() != "" {
		return .HandleFinalize(, )
	}

	 := .GetStream()
	,  := getRequestStreamLength()
	if  != nil {
		return , , computeInputTrailingChecksumError{
			Msg: "failed to determine stream length",
			Err: ,
		}
	}

	if  == nil ||  == 0 {
		// Nil and empty streams are handled by the Build handler. They are not
		// supported by the trailing checksums finalize handler. There is no
		// benefit to sending them as trailers compared to headers.
		return , , computeInputTrailingChecksumError{
			Msg: "nil or empty streams are not supported",
		}
	}

	,  := newComputeChecksumReader(, )
	if  != nil {
		return , , computeInputTrailingChecksumError{
			Msg: "failed to created checksum reader",
			Err: ,
		}
	}

	 := newUnsignedAWSChunkedEncoding(,
		func( *awsChunkedEncodingOptions) {
			.Trailers[AlgorithmHTTPHeader(.Algorithm())] = awsChunkedTrailerValue{
				Get:    .Base64Checksum,
				Length: .Base64ChecksumLength(),
			}
			.StreamLength = 
		})

	for ,  := range .HTTPHeaders() {
		for ,  := range  {
			.Header.Add(, )
		}
	}

	// Setting the stream on the request will create a copy. The content length
	// is not updated until after the request is copied to prevent impacting
	// upstream middleware.
	,  = .SetStream()
	if  != nil {
		return , , computeInputTrailingChecksumError{
			Msg: "failed updating request to trailing checksum wrapped stream",
			Err: ,
		}
	}
	.ContentLength = .EncodedLength()
	.Request = 

	// Add decoded content length header if original stream's content length is known.
	if  != -1 && .EnableDecodedContentLengthHeader {
		.Header.Set(decodedContentLengthHeaderName, strconv.FormatInt(, 10))
	}

	, ,  = .HandleFinalize(, )
	if  == nil {
		,  := .Base64Checksum()
		if  != nil {
			return , , fmt.Errorf("failed to get computed checksum, %w", )
		}

		// Record the checksum and algorithm that was computed
		SetComputedInputChecksums(&, map[string]string{
			string(): ,
		})
	}

	return , , 
}

func ( context.Context) (Algorithm, bool, error) {
	 := getContextInputAlgorithm()
	if  == "" {
		return "", false, nil
	}

	,  := ParseAlgorithm()
	if  != nil {
		return "", false, fmt.Errorf(
			"failed to parse algorithm, %w", )
	}

	return , true, nil
}

func ( Algorithm,  io.Reader,  bool) (
	 string,  string,  error,
) {
	,  := NewAlgorithmHash()
	if  != nil {
		return "", "", fmt.Errorf(
			"failed to get hasher for checksum algorithm, %w", )
	}

	var  hash.Hash
	var  io.Writer = 

	// Compute payload hash for the protocol. To prevent another handler
	// (computePayloadSHA256) re-reading body also compute the SHA256 for
	// request signing. If configured checksum algorithm is SHA256, don't
	// double wrap stream with another SHA256 hasher.
	if  &&  != AlgorithmSHA256 {
		 = sha256.New()
		 = io.MultiWriter(, )
	}

	if  != nil {
		if _,  = io.Copy(, );  != nil {
			return "", "", fmt.Errorf(
				"failed to read stream to compute hash, %w", )
		}
	}

	 = string(base64EncodeHashSum())
	if  {
		if  != AlgorithmSHA256 {
			 = string(hexEncodeHashSum())
		} else {
			 = string(hexEncodeHashSum())
		}
	}

	return , , nil
}

func ( *smithyhttp.Request) (int64, error) {
	if  := .ContentLength;  > 0 {
		return , nil
	}

	if , ,  := .StreamLength();  != nil {
		return 0, fmt.Errorf("failed getting request stream's length, %w", )
	} else if  {
		return , nil
	}

	return -1, nil
}

// setMD5Checksum computes the MD5 of the request payload and sets it to the
// Content-MD5 header. Returning the MD5 base64 encoded string or error.
//
// If the MD5 is already set as the Content-MD5 header, that value will be
// returned, and nothing else will be done.
//
// If the payload is empty, no MD5 will be computed. No error will be returned.
// Empty payloads do not have an MD5 value.
//
// Replaces the smithy-go middleware for httpChecksum trait.
func ( context.Context,  *smithyhttp.Request) (string, error) {
	if  := .Header.Get(contentMD5Header); len() != 0 {
		return , nil
	}
	 := .GetStream()
	if  == nil {
		return "", nil
	}

	if !.IsStreamSeekable() {
		return "", fmt.Errorf(
			"unseekable stream is not supported for computing md5 checksum")
	}

	,  := computeMD5Checksum()
	if  != nil {
		return "", 
	}
	if  := .RewindStream();  != nil {
		return "", fmt.Errorf("failed to rewind stream after computing MD5 checksum, %w", )
	}
	// set the 'Content-MD5' header
	.Header.Set(contentMD5Header, string())
	return string(), nil
}