Skip to content

Commit 68b8718

Browse files
committed
Let S3Mock accept * and "*" for conditional requests
S3 API accepts * for conditional requests on all APIs. All APIs but PutObject succeed with "*" as well. Fixes #2371
1 parent ab65952 commit 68b8718

File tree

3 files changed

+27
-10
lines changed

3 files changed

+27
-10
lines changed

integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/GetPutDeleteObjectIT.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
944944
fun testGetObject_successWithMatchingWildcardEtag(testInfo: TestInfo) {
945945
val (bucketName, putObjectResponse) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
946946
val eTag = putObjectResponse.eTag()
947-
val matchingEtag = "\"*\""
947+
val matchingEtag = WILDCARD_ETAG
948948

949949
s3Client.getObject {
950950
it.bucket(bucketName)
@@ -977,14 +977,13 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
977977
}
978978

979979
@Test
980-
@S3VerifiedFailure(year = 2025,
981-
reason = "S3 returns: A header you provided implies functionality that is not implemented.")
980+
@S3VerifiedSuccess(year = 2025)
982981
fun `PUT object fails with non matching wildcard etag`(testInfo: TestInfo) {
983982
val uploadFile = File(UPLOAD_FILE_NAME)
984983
val expectedEtag = FileInputStream(uploadFile).let {
985984
"\"${DigestUtil.hexDigest(it)}\""
986985
}
987-
val nonMatchingEtag = "\"*\""
986+
val nonMatchingEtag = WILDCARD_ETAG
988987

989988
val (bucketName, putObjectResponse) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
990989
putObjectResponse.eTag().also {
@@ -1059,7 +1058,7 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
10591058
"\"${DigestUtil.hexDigest(it)}\""
10601059
}
10611060

1062-
val matchingEtag = "\"*\""
1061+
val matchingEtag = WILDCARD_ETAG
10631062

10641063
val (bucketName, putObjectResponse) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
10651064
val eTag = putObjectResponse.eTag().also {
@@ -1220,7 +1219,7 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
12201219
"\"${DigestUtil.hexDigest(it)}\""
12211220
}
12221221

1223-
val nonMatchingEtag = "\"*\""
1222+
val nonMatchingEtag = WILDCARD_ETAG
12241223

12251224
val (bucketName, putObjectResponse) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
12261225
putObjectResponse.eTag().also {

integration-tests/src/test/kotlin/com/adobe/testing/s3mock/its/S3TestBase.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import software.amazon.awssdk.services.s3.S3Client
3939
import software.amazon.awssdk.services.s3.S3Configuration
4040
import software.amazon.awssdk.services.s3.internal.crt.S3CrtAsyncClient
4141
import software.amazon.awssdk.services.s3.model.Bucket
42+
import software.amazon.awssdk.services.s3.model.BucketType
4243
import software.amazon.awssdk.services.s3.model.ChecksumAlgorithm
4344
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse
4445
import software.amazon.awssdk.services.s3.model.EncodingType
@@ -271,6 +272,21 @@ internal abstract class S3TestBase {
271272
return bucketName
272273
}
273274

275+
fun givenDirectoryBucket(bucketName: String = randomName): String {
276+
_s3Client.createBucket {
277+
it.bucket(bucketName)
278+
it.createBucketConfiguration {
279+
it.bucket {
280+
it.type(BucketType.DIRECTORY)
281+
}
282+
}
283+
}
284+
val bucketCreated = _s3Client.waiter().waitUntilBucketExists { it.bucket(bucketName) }
285+
val bucketCreatedResponse = bucketCreated.matched().response().get()
286+
assertThat(bucketCreatedResponse).isNotNull
287+
return bucketName
288+
}
289+
274290
fun givenObject(bucketName: String, key: String, fileName: String? = null): PutObjectResponse {
275291
val uploadFile = File(fileName ?: key)
276292
return _s3Client.putObject({
@@ -575,6 +591,7 @@ internal abstract class S3TestBase {
575591
}
576592

577593
companion object {
594+
const val WILDCARD_ETAG = "*"
578595
val INITIAL_BUCKET_NAMES: Collection<String> = listOf("bucket-a", "bucket-b")
579596
val LOG: Logger = LoggerFactory.getLogger(this::class.java)
580597
const val TEST_ENC_KEY_ID = "valid-test-key-id"

server/src/main/java/com/adobe/testing/s3mock/service/ObjectService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
import com.adobe.testing.s3mock.S3Exception;
3030
import com.adobe.testing.s3mock.dto.AccessControlPolicy;
31-
import com.adobe.testing.s3mock.dto.Checksum;
3231
import com.adobe.testing.s3mock.dto.ChecksumAlgorithm;
3332
import com.adobe.testing.s3mock.dto.ChecksumType;
3433
import com.adobe.testing.s3mock.dto.Delete;
@@ -49,7 +48,6 @@
4948
import java.nio.file.Files;
5049
import java.nio.file.Path;
5150
import java.time.Instant;
52-
import java.time.temporal.ChronoUnit;
5351
import java.util.ArrayList;
5452
import java.util.List;
5553
import java.util.Map;
@@ -58,6 +56,7 @@
5856

5957
public class ObjectService extends ServiceBase {
6058
static final String WILDCARD_ETAG = "\"*\"";
59+
static final String WILDCARD = "*";
6160
private static final Logger LOG = LoggerFactory.getLogger(ObjectService.class);
6261
private final BucketStore bucketStore;
6362
private final ObjectStore objectStore;
@@ -369,7 +368,7 @@ public void verifyObjectMatching(
369368

370369
var setMatch = match != null && !match.isEmpty();
371370
if (setMatch) {
372-
if (match.contains(WILDCARD_ETAG)) {
371+
if (match.contains(WILDCARD_ETAG) || match.contains(WILDCARD)) {
373372
//request cares only that the object exists
374373
return;
375374
} else if (!match.contains(etag)) {
@@ -378,7 +377,9 @@ public void verifyObjectMatching(
378377
}
379378

380379
var setNoneMatch = noneMatch != null && !noneMatch.isEmpty();
381-
if (setNoneMatch && (noneMatch.contains(WILDCARD_ETAG) || noneMatch.contains(etag))) {
380+
if (setNoneMatch
381+
&& (noneMatch.contains(WILDCARD_ETAG) || noneMatch.contains(WILDCARD) || noneMatch.contains(etag))
382+
) {
382383
//request cares only that the object DOES NOT exist.
383384
throw NOT_MODIFIED;
384385
}

0 commit comments

Comments
 (0)