From e146646665c6bbc97693eb815f684468784202ea Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Thu, 30 Jul 2026 08:37:51 -0700 Subject: [PATCH] fix: honor mpMaxParts across partNumber validation Multipart partNumber validation was inconsistent because some handlers still enforced the hardcoded S3 default while others used the instance-level mpMaxParts setting. This created a split API contract where changing mpMaxParts did not reliably affect all relevant endpoints. This change routes all partNumber upper-bound checks through a shared effective limit helper so deployments that customize mpMaxParts get consistent behavior across GetObject, HeadObject, UploadPart, and UploadPartCopy, while preserving the default S3-compatible maximum when mpMaxParts is unset. --- s3api/controllers/base.go | 8 ++++++++ s3api/controllers/object-get.go | 2 +- s3api/controllers/object-head.go | 2 +- s3api/controllers/object-put.go | 4 ++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index 191cc4e2..03dda5b6 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -84,6 +84,14 @@ func (c S3ApiController) getAclHeaderValue(ctx fiber.Ctx, key string, defaultVal return ctx.Get(key, defaultValues...) } +func (c S3ApiController) effectiveMpMaxParts() int { + if c.mpMaxParts > 0 { + return c.mpMaxParts + } + + return maxPartNumber +} + // Returns MethodNotAllowed for unmatched routes func (c S3ApiController) HandleErrorRoute(err error) Controller { return func(ctx fiber.Ctx) (*Response, error) { diff --git a/s3api/controllers/object-get.go b/s3api/controllers/object-get.go index d418ad88..51f29bd8 100644 --- a/s3api/controllers/object-get.go +++ b/s3api/controllers/object-get.go @@ -451,7 +451,7 @@ func (c S3ApiController) GetObject(ctx fiber.Ctx) (*Response, error) { var partNumber *int32 if ctx.Request().URI().QueryArgs().Has("partNumber") { - if partNumberQuery < minPartNumber || partNumberQuery > maxPartNumber { + if partNumberQuery < minPartNumber || partNumberQuery > int32(c.effectiveMpMaxParts()) { debuglogger.Logf("invalid part number: %d", partNumberQuery) return &Response{ MetaOpts: &MetaOptions{ diff --git a/s3api/controllers/object-head.go b/s3api/controllers/object-head.go index 6e1acba7..5974e638 100644 --- a/s3api/controllers/object-head.go +++ b/s3api/controllers/object-head.go @@ -99,7 +99,7 @@ func (c S3ApiController) HeadObject(ctx fiber.Ctx) (*Response, error) { var partNumber *int32 if ctx.Request().URI().QueryArgs().Has("partNumber") { - if partNumberQuery < minPartNumber || partNumberQuery > maxPartNumber { + if partNumberQuery < minPartNumber || partNumberQuery > int32(c.effectiveMpMaxParts()) { debuglogger.Logf("invalid part number: %d", partNumberQuery) return &Response{ MetaOpts: &MetaOptions{ diff --git a/s3api/controllers/object-put.go b/s3api/controllers/object-put.go index fc381843..12331cd3 100644 --- a/s3api/controllers/object-put.go +++ b/s3api/controllers/object-put.go @@ -255,7 +255,7 @@ func (c S3ApiController) UploadPart(ctx fiber.Ctx) (*Response, error) { }, err } - if partNumber < minPartNumber || partNumber > int32(c.mpMaxParts) { + if partNumber < minPartNumber || partNumber > int32(c.effectiveMpMaxParts()) { debuglogger.Logf("invalid part number: %d", partNumber) return &Response{ MetaOpts: &MetaOptions{ @@ -390,7 +390,7 @@ func (c S3ApiController) UploadPartCopy(ctx fiber.Ctx) (*Response, error) { }, s3err.GetAPIError(s3err.ErrNonEmptyRequestBody) } - if partNumber < minPartNumber || partNumber > maxPartNumber { + if partNumber < minPartNumber || partNumber > int32(c.effectiveMpMaxParts()) { debuglogger.Logf("invalid part number: %d", partNumber) return &Response{ MetaOpts: &MetaOptions{