fix: support asterisk read preconditions

Fixes #2185

Add asterisk handling for `If-Match` and `If-None-Match` read preconditions across `GetObject`, `HeadObject`, `CopyObject`, and `UploadPartCopy`.

`If-Match`: `*` now matches any `ETag`, while `If-None-Match`: `*` returns `304 Not Modified`.
This commit is contained in:
niksis02
2026-06-16 21:36:45 +04:00
parent 4d391cabc8
commit 6df901ba42
5 changed files with 55 additions and 2 deletions
+3 -2
View File
@@ -643,6 +643,7 @@ type PreConditions struct {
// - if-modified-since
// - if-unmodified-since
// if-match and if-none-match are ETag comparisons
// if-match "*" matches any ETag, and if-none-match "*" matches no ETag
// if-modified-since and if-unmodified-since are last modifed time comparisons
func EvaluatePreconditions(etag string, modTime time.Time, preconditions PreConditions) error {
if preconditions.IfMatch == nil && preconditions.IfNoneMatch == nil && preconditions.IfModSince == nil && preconditions.IfUnmodeSince == nil {
@@ -654,10 +655,10 @@ func EvaluatePreconditions(etag string, modTime time.Time, preconditions PreCond
// convert all conditions to *bool to evaluate the conditions
var ifMatch, ifNoneMatch, ifModSince, ifUnmodeSince *bool
if preconditions.IfMatch != nil {
ifMatch = getBoolPtr(*preconditions.IfMatch == etag)
ifMatch = getBoolPtr(*preconditions.IfMatch == "*" || *preconditions.IfMatch == etag)
}
if preconditions.IfNoneMatch != nil {
ifNoneMatch = getBoolPtr(*preconditions.IfNoneMatch != etag)
ifNoneMatch = getBoolPtr(*preconditions.IfNoneMatch != "*" && *preconditions.IfNoneMatch != etag)
}
if preconditions.IfModSince != nil {
ifModSince = getBoolPtr(preconditions.IfModSince.UTC().Before(modTime.UTC()))
+13
View File
@@ -1208,6 +1208,19 @@ func CopyObject_conditional_reads(s *S3Conf) error {
{nil, &etagTrimmed, nil, &before, errCond},
{nil, &etagTrimmed, nil, &after, errMod},
{nil, &etagTrimmed, nil, nil, errMod},
// if-match and if-none-match with asterisk
{getPtr("*"), nil, nil, nil, nil},
{getPtr("*"), nil, &after, nil, errMod},
{getPtr("*"), getPtr("invalid_etag"), nil, nil, nil},
{getPtr("*"), etag, nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, &before, errMod},
{nil, getPtr("*"), nil, nil, errMod},
{nil, getPtr("*"), &before, nil, errMod},
{nil, getPtr("*"), nil, &after, errMod},
{nil, getPtr("*"), nil, &before, errCond},
{getPtr("invalid_etag"), getPtr("*"), nil, nil, errCond},
} {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CopyObject(ctx, &s3.CopyObjectInput{
+13
View File
@@ -539,6 +539,19 @@ func GetObject_conditional_reads(s *S3Conf) error {
{nil, &etagTrimmed, nil, &before, errCond},
{nil, &etagTrimmed, nil, &after, errMod},
{nil, &etagTrimmed, nil, nil, errMod},
// if-match and if-none-match with asterisk
{getPtr("*"), nil, nil, nil, nil},
{getPtr("*"), nil, &after, nil, errMod},
{getPtr("*"), getPtr("invalid_etag"), nil, nil, nil},
{getPtr("*"), etag, nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, &before, errMod},
{nil, getPtr("*"), nil, nil, errMod},
{nil, getPtr("*"), &before, nil, errMod},
{nil, getPtr("*"), nil, &after, errMod},
{nil, getPtr("*"), nil, &before, errCond},
{getPtr("invalid_etag"), getPtr("*"), nil, nil, errCond},
} {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.GetObject(ctx, &s3.GetObjectInput{
+13
View File
@@ -534,6 +534,19 @@ func HeadObject_conditional_reads(s *S3Conf) error {
{nil, &etagTrimmed, nil, &before, errCond},
{nil, &etagTrimmed, nil, &after, errMod},
{nil, &etagTrimmed, nil, nil, errMod},
// if-match and if-none-match with asterisk
{getPtr("*"), nil, nil, nil, nil},
{getPtr("*"), nil, &after, nil, errMod},
{getPtr("*"), getPtr("invalid_etag"), nil, nil, nil},
{getPtr("*"), etag, nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, &before, errMod},
{nil, getPtr("*"), nil, nil, errMod},
{nil, getPtr("*"), &before, nil, errMod},
{nil, getPtr("*"), nil, &after, errMod},
{nil, getPtr("*"), nil, &before, errCond},
{getPtr("invalid_etag"), getPtr("*"), nil, nil, errCond},
} {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
+13
View File
@@ -804,6 +804,19 @@ func UploadPartCopy_conditional_reads(s *S3Conf) error {
{nil, &etagTrimmed, nil, &before, errCond},
{nil, &etagTrimmed, nil, &after, errMod},
{nil, &etagTrimmed, nil, nil, errMod},
// if-match and if-none-match with asterisk
{getPtr("*"), nil, nil, nil, nil},
{getPtr("*"), nil, &after, nil, errMod},
{getPtr("*"), getPtr("invalid_etag"), nil, nil, nil},
{getPtr("*"), etag, nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, nil, errMod},
{getPtr("*"), getPtr("*"), nil, &before, errMod},
{nil, getPtr("*"), nil, nil, errMod},
{nil, getPtr("*"), &before, nil, errMod},
{nil, getPtr("*"), nil, &after, errMod},
{nil, getPtr("*"), nil, &before, errCond},
{getPtr("invalid_etag"), getPtr("*"), nil, nil, errCond},
} {
mpKey := "mp-key"
mp, err := createMp(s3client, bucket, mpKey)