mirror of
https://github.com/versity/versitygw.git
synced 2026-07-02 16:54:25 +00:00
12fb5a6594
Fixes #2179 Treat a present but empty `Content-MD5` header as an invalid digest instead of handling it as if the header were absent. This makes PUT operations return `InvalidDigest` for empty `Content-MD5` values while preserving existing behavior for missing headers.
128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package middlewares
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/versity/versitygw/s3api/utils"
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
// VerifyChecksums parses, validates, and calculates the
|
|
// Content-MD5 and x-amz-checksum-* headers.
|
|
// Additionally, it ensures that the request body is not empty
|
|
// for actions that require a non-empty body. For large data actions(PutObject, UploadPart),
|
|
// it wraps the body reader to handle Content-MD5:
|
|
// the x-amz-checksum-* headers are explicitly processed by the backend.
|
|
func VerifyChecksums(streamBody bool, requireBody bool, requireChecksum bool) fiber.Handler {
|
|
return func(ctx fiber.Ctx) error {
|
|
md5Header := ctx.Request().Header.Peek("Content-Md5")
|
|
hasMD5Header := md5Header != nil
|
|
md5sum := string(md5Header)
|
|
|
|
if hasMD5Header && md5sum == "" {
|
|
return s3err.GetInvalidDigestErr(md5sum)
|
|
}
|
|
|
|
if streamBody {
|
|
// for large data actions(PutObject, UploadPart)
|
|
// only stack the md5 reader,as x-amz-checksum-*
|
|
// calculation is explicitly handled in back-end
|
|
if !hasMD5Header {
|
|
return nil
|
|
}
|
|
|
|
if !isValidMD5(md5sum) {
|
|
return s3err.GetInvalidDigestErr(md5sum)
|
|
}
|
|
|
|
var err error
|
|
wrapBodyReader(ctx, func(r io.Reader) io.Reader {
|
|
r, err = utils.NewHashReader(r, md5sum, utils.HashTypeContentMD5)
|
|
return r
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
body := ctx.BodyRaw()
|
|
if requireBody && len(body) == 0 {
|
|
return s3err.GetAPIError(s3err.ErrMissingRequestBody)
|
|
}
|
|
|
|
var rdr io.Reader
|
|
var err error
|
|
if md5sum != "" {
|
|
if !isValidMD5(md5sum) {
|
|
return s3err.GetInvalidDigestErr(md5sum)
|
|
}
|
|
|
|
rdr, err = utils.NewHashReader(bytes.NewReader(body), md5sum, utils.HashTypeContentMD5)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// parse and validate checksum headers
|
|
algo, checksums, err := utils.ParseChecksumHeadersAndSdkAlgo(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if algo != "" {
|
|
r, err := utils.NewHashReader(bytes.NewReader(body), checksums[algo], utils.HashType(strings.ToLower(string(algo))))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if rdr != nil {
|
|
// combine both md5 and the checksum readers
|
|
rdr = io.MultiReader(rdr, r)
|
|
} else {
|
|
rdr = r
|
|
}
|
|
}
|
|
|
|
if rdr == nil && requireChecksum {
|
|
return s3err.GetAPIError(s3err.ErrChecksumRequired)
|
|
}
|
|
|
|
if rdr != nil {
|
|
_, err = io.Copy(io.Discard, rdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func isValidMD5(s string) bool {
|
|
decoded, err := base64.StdEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return len(decoded) == 16
|
|
}
|