diff --git a/doc/xml/release/2025/2.55.0.xml b/doc/xml/release/2025/2.55.0.xml
index 27b252941..df8748dee 100644
--- a/doc/xml/release/2025/2.55.0.xml
+++ b/doc/xml/release/2025/2.55.0.xml
@@ -131,6 +131,17 @@
Use lz4 for protocol compression.
+
+
+
+
+
+
+
+
+ Calculate content-md5 on S3 only when required.
+
+
diff --git a/src/storage/s3/storage.c b/src/storage/s3/storage.c
index 9c41c2ec1..a4e3c12e0 100644
--- a/src/storage/s3/storage.c
+++ b/src/storage/s3/storage.c
@@ -452,6 +452,7 @@ storageS3RequestAsync(StorageS3 *const this, const String *const verb, const Str
FUNCTION_LOG_PARAM(HTTP_HEADER, param.header);
FUNCTION_LOG_PARAM(HTTP_QUERY, param.query);
FUNCTION_LOG_PARAM(BUFFER, param.content);
+ FUNCTION_LOG_PARAM(BOOL, param.contentMd5);
FUNCTION_LOG_PARAM(BOOL, param.sseKms);
FUNCTION_LOG_PARAM(BOOL, param.sseC);
FUNCTION_LOG_PARAM(BOOL, param.tag);
@@ -473,9 +474,11 @@ storageS3RequestAsync(StorageS3 *const this, const String *const verb, const Str
requestHeader, HTTP_HEADER_CONTENT_LENGTH_STR,
param.content == NULL || bufEmpty(param.content) ? ZERO_STR : strNewFmt("%zu", bufUsed(param.content)));
- // Calculate content-md5 header if there is content
- if (param.content != NULL)
+ // Calculate content-md5 header when required
+ if (param.contentMd5)
{
+ ASSERT(param.content != NULL && !bufEmpty(param.content));
+
httpHeaderAdd(
requestHeader, HTTP_HEADER_CONTENT_MD5_STR,
strNewEncode(encodingBase64, cryptoHashOne(hashTypeMd5, param.content)));
@@ -607,6 +610,7 @@ storageS3Request(StorageS3 *const this, const String *const verb, const String *
FUNCTION_LOG_PARAM(HTTP_HEADER, param.header);
FUNCTION_LOG_PARAM(HTTP_QUERY, param.query);
FUNCTION_LOG_PARAM(BUFFER, param.content);
+ FUNCTION_LOG_PARAM(BOOL, param.contentMd5);
FUNCTION_LOG_PARAM(BOOL, param.allowMissing);
FUNCTION_LOG_PARAM(BOOL, param.contentIo);
FUNCTION_LOG_PARAM(BOOL, param.sseKms);
@@ -615,8 +619,8 @@ storageS3Request(StorageS3 *const this, const String *const verb, const String *
FUNCTION_LOG_END();
HttpRequest *const request = storageS3RequestAsyncP(
- this, verb, path, .header = param.header, .query = param.query, .content = param.content, .sseKms = param.sseKms,
- .sseC = param.sseC, .tag = param.tag);
+ this, verb, path, .header = param.header, .query = param.query, .content = param.content, .contentMd5 = param.contentMd5,
+ .sseKms = param.sseKms, .sseC = param.sseC, .tag = param.tag);
HttpResponse *const result = storageS3ResponseP(
request, .allowMissing = param.allowMissing, .contentIo = param.contentIo);
@@ -1041,7 +1045,8 @@ storageS3PathRemoveInternal(StorageS3 *const this, HttpRequest *const request, X
HttpQuery *const query = httpQueryAdd(httpQueryNewP(), S3_QUERY_DELETE_STR, EMPTY_STR);
Buffer *const content = xmlDocumentBuf(xml);
- result = storageS3RequestAsyncP(this, HTTP_VERB_POST_STR, FSLASH_STR, .query = query, .content = content);
+ result = storageS3RequestAsyncP(
+ this, HTTP_VERB_POST_STR, FSLASH_STR, .query = query, .content = content, .contentMd5 = true);
httpQueryFree(query);
bufFree(content);
diff --git a/src/storage/s3/storage.intern.h b/src/storage/s3/storage.intern.h
index 24be5bce9..710e45b70 100644
--- a/src/storage/s3/storage.intern.h
+++ b/src/storage/s3/storage.intern.h
@@ -22,6 +22,7 @@ typedef struct StorageS3RequestAsyncParam
const HttpHeader *header; // Headers
const HttpQuery *query; // Query parameters
const Buffer *content; // Request content
+ bool contentMd5; // MD5 content checksum required?
bool sseKms; // Enable server-side encryption?
bool sseC; // Enable server-side encryption with customer-provided keys?
bool tag; // Add tags when available?
@@ -52,6 +53,7 @@ typedef struct StorageS3RequestParam
const HttpHeader *header; // Headers
const HttpQuery *query; // Query parameters
const Buffer *content; // Request content
+ bool contentMd5; // MD5 content checksum required?
bool allowMissing; // Allow missing files (caller can check response code)
bool contentIo; // Is IoRead interface required to read content?
bool sseKms; // Enable server-side encryption?
diff --git a/test/src/module/storage/s3Test.c b/test/src/module/storage/s3Test.c
index 5c26566df..49b022524 100644
--- a/test/src/module/storage/s3Test.c
+++ b/test/src/module/storage/s3Test.c
@@ -34,6 +34,7 @@ typedef struct TestRequestParam
const char *token;
const char *tag;
bool requesterPays;
+ bool contentMd5;
} TestRequestParam;
#define testRequestP(write, s3, verb, path, ...) \
@@ -68,7 +69,7 @@ testRequest(IoWrite *write, Storage *s3, const char *verb, const char *path, Tes
"authorization:AWS4-HMAC-SHA256 Credential=%s/\?\?\?\?\?\?\?\?/us-east-1/s3/aws4_request,SignedHeaders=",
param.accessKey == NULL ? strZ(driver->accessKey) : param.accessKey);
- if (param.content != NULL)
+ if (param.contentMd5)
strCatZ(request, "content-md5;");
strCatZ(request, "host;");
@@ -105,7 +106,7 @@ testRequest(IoWrite *write, Storage *s3, const char *verb, const char *path, Tes
strCatFmt(request, "content-length:%zu\r\n", param.content != NULL ? strlen(param.content) : 0);
// Add md5
- if (param.content != NULL)
+ if (param.contentMd5)
{
strCatFmt(
request, "content-md5:%s\r\n", strZ(strNewEncode(encodingBase64, cryptoHashOne(hashTypeMd5, BUFSTRZ(param.content)))));
@@ -869,7 +870,6 @@ testRun(void)
"*** Request Headers ***:\n"
"authorization: \n"
"content-length: 205\n"
- "content-md5: 37smUM6Ah2/EjZbp420dPw==\n"
"host: bucket.s3.amazonaws.com\n"
"x-amz-content-sha256: 0838a79dfbddc2128d28fb4fa8d605e0a8e6d1355094000f39b6eb3feff4641f\n"
"x-amz-date: \n"
@@ -1317,7 +1317,7 @@ testRun(void)
"");
testRequestP(
- service, s3, HTTP_VERB_POST, "/bucket/?delete=",
+ service, s3, HTTP_VERB_POST, "/bucket/?delete=", .contentMd5 = true,
.content =
"\n"
"true"
@@ -1378,7 +1378,7 @@ testRun(void)
"");
testRequestP(
- service, s3, HTTP_VERB_POST, "/bucket/?delete=",
+ service, s3, HTTP_VERB_POST, "/bucket/?delete=", .contentMd5 = true,
.content =
"\n"
"true"
@@ -1388,7 +1388,7 @@ testRun(void)
testResponseP(service);
testRequestP(
- service, s3, HTTP_VERB_POST, "/bucket/?delete=",
+ service, s3, HTTP_VERB_POST, "/bucket/?delete=", .contentMd5 = true,
.content =
"\n"
"true"
@@ -1417,7 +1417,7 @@ testRun(void)
"");
testRequestP(
- service, s3, HTTP_VERB_POST, "/bucket/?delete=",
+ service, s3, HTTP_VERB_POST, "/bucket/?delete=", .contentMd5 = true,
.content =
"\n"
"true"