mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-30 17:00:48 +01:00
A header value that decodes to neither 32-byte hex nor base64 is a malformed argument, not a hash mismatch. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
112 lines
3.4 KiB
Go
112 lines
3.4 KiB
Go
package s3api
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"errors"
|
|
"hash"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
)
|
|
|
|
// getRequestDataReader returns the appropriate reader for the request body.
|
|
// When IAM is disabled, it still processes chunked transfer encoding for
|
|
// authTypeStreamingUnsigned to strip checksum headers and extract the actual data.
|
|
// This fixes issues where chunked data with checksums would be stored incorrectly
|
|
// when IAM is not enabled.
|
|
func getRequestDataReader(s3a *S3ApiServer, r *http.Request) (io.ReadCloser, s3err.ErrorCode) {
|
|
var s3ErrCode s3err.ErrorCode
|
|
dataReader := r.Body
|
|
rAuthType := getRequestAuthType(r)
|
|
if s3a.iam.isEnabled() {
|
|
if rAuthType == authTypeStreamingSigned || rAuthType == authTypeStreamingUnsigned {
|
|
dataReader, s3ErrCode = s3a.iam.newChunkedReader(r)
|
|
}
|
|
} else {
|
|
switch rAuthType {
|
|
case authTypeStreamingSigned:
|
|
s3ErrCode = s3err.ErrAuthNotSetup
|
|
case authTypeStreamingUnsigned:
|
|
// Even when IAM is disabled, we still need to handle chunked transfer encoding
|
|
// to strip checksum headers and process the data correctly
|
|
dataReader, s3ErrCode = s3a.iam.newChunkedReader(r)
|
|
}
|
|
}
|
|
if s3ErrCode != s3err.ErrNone {
|
|
return nil, s3ErrCode
|
|
}
|
|
|
|
expected, valid := expectedContentSha256(r)
|
|
if !valid {
|
|
return nil, s3err.ErrInvalidArgument
|
|
}
|
|
if expected != nil && dataReader != nil {
|
|
if r.ContentLength == 0 {
|
|
// Handlers may never read an empty body, so check it now.
|
|
emptySum := sha256.Sum256(nil)
|
|
if !bytes.Equal(expected, emptySum[:]) {
|
|
return nil, s3err.ErrContentSHA256Mismatch
|
|
}
|
|
return dataReader, s3err.ErrNone
|
|
}
|
|
dataReader = &contentSha256Verifier{reader: dataReader, hasher: sha256.New(), expected: expected}
|
|
}
|
|
return dataReader, s3err.ErrNone
|
|
}
|
|
|
|
// expectedContentSha256 decodes the x-amz-content-sha256 header when it declares
|
|
// a payload hash — hex or base64 per SigV4 — so the body can be checked as it
|
|
// streams. Sentinel values (streaming, unsigned) carry no hash and are skipped;
|
|
// anything that decodes to neither returns valid=false.
|
|
func expectedContentSha256(r *http.Request) (expected []byte, valid bool) {
|
|
v := r.Header.Get("X-Amz-Content-Sha256")
|
|
if v == "" || v == unsignedPayload || v == streamingContentSHA256 ||
|
|
v == streamingContentSHA256Trailer || v == streamingUnsignedPayload {
|
|
return nil, true
|
|
}
|
|
if decoded, err := hex.DecodeString(v); err == nil && len(decoded) == sha256.Size {
|
|
return decoded, true
|
|
}
|
|
if decoded, err := base64.StdEncoding.DecodeString(v); err == nil && len(decoded) == sha256.Size {
|
|
return decoded, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// contentSha256Verifier checks the decoded x-amz-content-sha256 once the stream
|
|
// is exhausted; handlers that never consume the body are unaffected.
|
|
type contentSha256Verifier struct {
|
|
reader io.ReadCloser
|
|
hasher hash.Hash
|
|
expected []byte
|
|
done bool
|
|
pending error
|
|
}
|
|
|
|
func (v *contentSha256Verifier) Read(p []byte) (int, error) {
|
|
if v.pending != nil {
|
|
return 0, v.pending
|
|
}
|
|
n, err := v.reader.Read(p)
|
|
v.hasher.Write(p[:n])
|
|
if err == io.EOF && !v.done {
|
|
v.done = true
|
|
if !bytes.Equal(v.hasher.Sum(nil), v.expected) {
|
|
v.pending = errors.New(s3err.ErrMsgContentSha256Mismatch)
|
|
err = nil
|
|
}
|
|
}
|
|
if n == 0 && v.pending != nil {
|
|
return 0, v.pending
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (v *contentSha256Verifier) Close() error {
|
|
return v.reader.Close()
|
|
}
|