Skip to content
Merged

4.10.0 #2657

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Whenever a 3rd party library is updated, S3Mock will update it's MINOR version.
* [Planned changes](#planned-changes)
* [CURRENT - 4.x - THIS VERSION IS UNDER ACTIVE DEVELOPMENT](#current---4x---this-version-is-under-active-development)
* [4.11.0 - PLANNED](#4110---planned)
* [4.10.0 - PLANNED](#4100---planned)
* [4.10.0](#4100)
* [4.9.1](#491)
* [4.9.0](#490)
* [4.8.0](#480)
Expand Down Expand Up @@ -155,7 +155,7 @@ Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
## 4.11.0 - PLANNED
Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.

**This is the last currently planned minor release of 4.x.**
**This is currently the last planned minor release of 4.x.**

* Features and fixes
* TBD
Expand All @@ -164,23 +164,42 @@ Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
* Version updates (deliverable dependencies)
* Update to Spring Boot 3.5.8
* Planned release November 20th 2025
* TBD: link to milestone
* https://github.com/spring-projects/spring-boot/milestone/401
* Version updates (build dependencies)
* TBD

## 4.10.0 - PLANNED
## 4.10.0
Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.

* Features and fixes
* TBD
* Return correct error body on invalid ranges (fixes #2732)
* Accept unquoted etags in if-match/if-none-match headers (fixes #2665)
* Refactorings
* TBD
* Drop commons-lang3 dependency and replace its usages with core Java (fixes #2735)
* Version updates (deliverable dependencies)
* Update to Spring Boot 3.5.7
* Planned release October 23rd 2025
* https://github.com/spring-projects/spring-boot/milestone/399
* Bump spring-boot.version from 3.5.6 to 3.5.7
* Bump aws-v2.version from 2.33.12 to 2.37.2
* Bump aws.version from 1.12.791 to 1.12.793
* Bump alpine from 3.22.1 to 3.22.2 in /docker
* Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0
* Version updates (build dependencies)
* TBD
* Bump kotlin.version from 2.2.20 to 2.2.21
* Bump aws.sdk.kotlin:s3-jvm from 1.5.41 to 1.5.73
* Bump digital.pragmatech.testing:spring-test-profiler from 0.0.12 to 0.0.13
* Bump org.mockito.kotlin:mockito-kotlin from 6.0.0 to 6.1.0
* Bump org.xmlunit:xmlunit-assertj3 from 2.10.4 to 2.11.0
* Bump org.codehaus.mojo:exec-maven-plugin from 3.5.1 to 3.6.2
* Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.11.3 to 3.12.0
* Bump org.apache.maven.plugins:maven-compiler-plugin from 3.14.0 to 3.14.1
* Bump org.apache.maven.plugins:maven-dependency-plugin from 3.8.1 to 3.9.0
* Bump org.apache.maven.plugins:maven-enforcer-plugin from 3.6.1 to 3.6.2
* Bump com.puppycrawl.tools:checkstyle from 11.0.1 to 12.1.1
* Bump org.jacoco:jacoco-maven-plugin from 0.8.13 to 0.8.14
* Bump github/codeql-action from 3.30.3 to 4.31.2
* Bump actions/dependency-review-action from 4.7.3 to 4.8.1
* Bump ossf/scorecard-action from 2.4.2 to 2.4.3
* Bump actions/stale from 10.0.0 to 10.1.0
* Bump actions/upload-artifact from 4.6.2 to 5.0.0

## 4.9.1
Version 4.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,23 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
}
}

@Test
@S3VerifiedTodo
fun `GET object succeeds with unquoted if-match=true`(testInfo: TestInfo) {
val (bucketName, putObjectResponse) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
val matchingEtag = putObjectResponse.eTag()
val unquotedEtag = matchingEtag.substring(1, matchingEtag.length - 1)
s3Client
.getObject {
it.bucket(bucketName)
it.key(UPLOAD_FILE_NAME)
it.ifMatch(unquotedEtag)
}.use {
assertThat(it.response().eTag()).isEqualTo(matchingEtag)
assertThat(it.response().contentLength()).isEqualTo(UPLOAD_FILE_LENGTH)
}
}

@Test
@S3VerifiedSuccess(year = 2025)
fun `GET object succeeds with if-match=true and if-unmodified-since=false`(testInfo: TestInfo) {
Expand Down Expand Up @@ -1407,6 +1424,23 @@ internal class GetPutDeleteObjectIT : S3TestBase() {
.hasMessageContaining("Service: S3, Status Code: 304")
}

@Test
@S3VerifiedTodo
fun `GET object fails with unquoted if-none-match=false`(testInfo: TestInfo) {
val (bucketName, putObjectResponse) = givenBucketAndObject(testInfo, UPLOAD_FILE_NAME)
val matchingEtag = putObjectResponse.eTag()
val unquotedEtag = matchingEtag.substring(1, matchingEtag.length - 1)

assertThatThrownBy {
s3Client.getObject {
it.bucket(bucketName)
it.key(UPLOAD_FILE_NAME)
it.ifNoneMatch(unquotedEtag)
}
}.isInstanceOf(S3Exception::class.java)
.hasMessageContaining("Service: S3, Status Code: 304")
}

@Test
@S3VerifiedSuccess(year = 2025)
fun `GET object succeeds with if-modified-since=true`(testInfo: TestInfo) {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
<kotlin.compiler.languageVersion>2.2</kotlin.compiler.languageVersion>
<kotlin-coroutines.version>1.10.2</kotlin-coroutines.version>

<aws-v2.version>2.33.12</aws-v2.version>
<aws-v2.version>2.37.2</aws-v2.version>

<aws.version>1.12.793</aws.version>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,16 @@ public void verifyObjectMatching(

var setMatch = match != null && !match.isEmpty();
if (setMatch) {
if (match.contains(WILDCARD_ETAG) || match.contains(WILDCARD) || match.contains(etag)) {
var unquotedEtag = etag.replace("\"", "");
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unquotedEtag is computed even when it might not be needed. Consider computing it only when the initial checks with the quoted etag fail, to avoid unnecessary string operations when the quoted etag matches directly.

Copilot uses AI. Check for mistakes.
if (match.contains(WILDCARD_ETAG)
|| match.contains(WILDCARD)
|| match.contains(etag)
|| match.contains(unquotedEtag)
) {
// request cares only that the object exists or that the etag matches.
LOG.debug("Object {} exists", s3ObjectMetadata.key());
return;
} else if (!match.contains(etag)) {
} else if (!match.contains(unquotedEtag) && !match.contains(etag)) {
LOG.debug("Object {} does not match etag {}", s3ObjectMetadata.key(), etag);
throw PRECONDITION_FAILED;
}
Expand All @@ -388,7 +393,12 @@ public void verifyObjectMatching(

var setNoneMatch = noneMatch != null && !noneMatch.isEmpty();
if (setNoneMatch) {
if (noneMatch.contains(WILDCARD_ETAG) || noneMatch.contains(WILDCARD) || noneMatch.contains(etag)) {
var unquotedEtag = etag.replace("\"", "");
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for creating unquotedEtag is duplicated on lines 371 and 396. Consider extracting this to a helper method or computing it once at the beginning of the method (line 358-359 area) to avoid duplication.

Copilot uses AI. Check for mistakes.
if (noneMatch.contains(WILDCARD_ETAG)
|| noneMatch.contains(WILDCARD)
|| noneMatch.contains(etag)
|| noneMatch.contains(unquotedEtag)
) {
// request cares only that the object etag does not match.
LOG.debug("Object {} has an ETag {} that matches one of the 'noneMatch' values", s3ObjectMetadata.key(), etag);
throw NOT_MODIFIED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import software.amazon.awssdk.http.auth.aws.internal.signer.chunkedencoding.Trai
import software.amazon.awssdk.http.auth.aws.internal.signer.io.ChecksumInputStream
import software.amazon.awssdk.http.auth.aws.internal.signer.util.ChecksumUtil
import software.amazon.awssdk.http.auth.aws.internal.signer.util.SignerUtils
import software.amazon.awssdk.http.auth.spi.signer.PayloadChecksumStore
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity
import java.io.File
import java.io.InputStream
Expand Down Expand Up @@ -85,7 +86,12 @@ object ChecksumTestUtil {
mutableSetOf(sdkChecksum)
)

val checksumTrailer: TrailerProvider = ChecksumTrailerProvider(sdkChecksum, checksumHeaderName)
val checksumTrailer: TrailerProvider = ChecksumTrailerProvider(
sdkChecksum,
checksumHeaderName,
checksumAlgorithm,
PayloadChecksumStore.create()
)

builder.inputStream(checksumInputStream).addTrailer(checksumTrailer)
}
Expand Down