Files
versitygw/s3api/utils/chunk-reader.go
niksis02 2b1e1af89b fix: Adds validation for Content-Length in upload operations.
Fixes #961
Fixes #1248

The gateway should return a `MissingContentLength` error if the `Content-Length` HTTP header is missing for upload operations (`PutObject`, `UploadPart`).

The second fix involves enforcing a maximum object size limit of `5 * 1024 * 1024 * 1024` bytes (5 GB) by validating the value of the `Content-Length` header. If the value exceeds this limit, the gateway should return an `EntityTooLarge` error.
2025-04-30 14:20:28 +04:00

143 lines
4.7 KiB
Go

// Copyright 2024 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 utils
import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/s3err"
)
const (
maxObjSizeLimit = 5 * 1024 * 1024 * 1024 // 5gb
)
type payloadType string
const (
payloadTypeUnsigned payloadType = "UNSIGNED-PAYLOAD"
payloadTypeStreamingUnsignedTrailer payloadType = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"
payloadTypeStreamingSigned payloadType = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
payloadTypeStreamingSignedTrailer payloadType = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"
payloadTypeStreamingEcdsa payloadType = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"
payloadTypeStreamingEcdsaTrailer payloadType = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER"
)
func getPayloadTypeNotSupportedErr(p payloadType) error {
return s3err.APIError{
HTTPStatusCode: http.StatusNotImplemented,
Code: "NotImplemented",
Description: fmt.Sprintf("The chunk encoding algorithm %v is not supported.", p),
}
}
var (
specialValues = map[payloadType]bool{
payloadTypeUnsigned: true,
payloadTypeStreamingUnsignedTrailer: true,
payloadTypeStreamingSigned: true,
payloadTypeStreamingSignedTrailer: true,
payloadTypeStreamingEcdsa: true,
payloadTypeStreamingEcdsaTrailer: true,
}
)
func (pt payloadType) isValid() bool {
return pt == payloadTypeUnsigned ||
pt == payloadTypeStreamingUnsignedTrailer ||
pt == payloadTypeStreamingSigned ||
pt == payloadTypeStreamingSignedTrailer ||
pt == payloadTypeStreamingEcdsa ||
pt == payloadTypeStreamingEcdsaTrailer
}
type checksumType string
const (
checksumTypeCrc32 checksumType = "x-amz-checksum-crc32"
checksumTypeCrc32c checksumType = "x-amz-checksum-crc32c"
checksumTypeSha1 checksumType = "x-amz-checksum-sha1"
checksumTypeSha256 checksumType = "x-amz-checksum-sha256"
checksumTypeCrc64nvme checksumType = "x-amz-checksum-crc64nvme"
)
func (c checksumType) isValid() bool {
return c == checksumTypeCrc32 ||
c == checksumTypeCrc32c ||
c == checksumTypeSha1 ||
c == checksumTypeSha256 ||
c == checksumTypeCrc64nvme
}
// IsSpecialPayload checks for special authorization types
func IsSpecialPayload(str string) bool {
return specialValues[payloadType(str)]
}
// IsChunkEncoding checks for streaming/unsigned authorization types
func IsStreamingPayload(str string) bool {
pt := payloadType(str)
return pt == payloadTypeStreamingUnsignedTrailer ||
pt == payloadTypeStreamingSigned ||
pt == payloadTypeStreamingSignedTrailer
}
func NewChunkReader(ctx *fiber.Ctx, r io.Reader, authdata AuthData, region, secret string, date time.Time, debug bool) (io.Reader, error) {
decContLengthStr := ctx.Get("X-Amz-Decoded-Content-Length")
if decContLengthStr == "" {
return nil, s3err.GetAPIError(s3err.ErrMissingContentLength)
}
decContLength, err := strconv.ParseInt(decContLengthStr, 10, 64)
//TODO: not sure if InvalidRequest should be returned in this case
if err != nil {
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
}
if decContLength > maxObjSizeLimit {
return nil, s3err.GetAPIError(s3err.ErrEntityTooLarge)
}
contentSha256 := payloadType(ctx.Get("X-Amz-Content-Sha256"))
if !contentSha256.isValid() {
//TODO: Add proper APIError
return nil, fmt.Errorf("invalid x-amz-content-sha256: %v", string(contentSha256))
}
checksumType := checksumType(strings.ToLower(ctx.Get("X-Amz-Trailer")))
if contentSha256 != payloadTypeStreamingSigned && !checksumType.isValid() {
return nil, s3err.GetAPIError(s3err.ErrTrailerHeaderNotSupported)
}
switch contentSha256 {
case payloadTypeStreamingUnsignedTrailer:
return NewUnsignedChunkReader(r, checksumType, debug)
case payloadTypeStreamingSignedTrailer:
return NewSignedChunkReader(r, authdata, region, secret, date, checksumType, debug)
case payloadTypeStreamingSigned:
return NewSignedChunkReader(r, authdata, region, secret, date, "", debug)
// return not supported for:
// - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD
// - STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER
default:
return nil, getPayloadTypeNotSupportedErr(contentSha256)
}
}