diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java index 191bb4b8bad0..9820993a2aa9 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java @@ -58,6 +58,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -1167,10 +1169,29 @@ void uploadFileInS3( TransferManager transferManager = TransferManagerBuilder.standard().withS3Client(connection).build(); final ObjectMetadata objectMetadata = new ObjectMetadata(); + + // Set content length + objectMetadata.setContentLength(payload.length); + // Only add content type if the user has mentioned it in the body if (multipartFormDataDTO.getType() != null) { objectMetadata.setContentType(multipartFormDataDTO.getType()); } + + // Calculate and set Content-MD5 header for Object Lock compliance + try { + MessageDigest md5Digest = MessageDigest.getInstance("MD5"); + byte[] md5Hash = md5Digest.digest(payload); + String md5Base64 = Base64.getEncoder().encodeToString(md5Hash); + objectMetadata.setContentMD5(md5Base64); + log.debug("Set Content-MD5 header for S3 upload: {}", md5Base64); + } catch (NoSuchAlgorithmException e) { + log.warn( + "Failed to calculate MD5 checksum for S3 upload. Object Lock enabled buckets may reject this upload.", + e); + // Continue with upload without MD5 header - let AWS handle the error if Object Lock is enabled + } + transferManager .upload(bucketName, path, inputStream, objectMetadata) .waitForUploadResult(); diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java b/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java index cbeb5ec9e51f..65a080a2bbfc 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java @@ -45,6 +45,8 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -1584,4 +1586,47 @@ public void verify_sanitizeGenerateCRUDPageTemplateInfo_addsInfoToReplaceTemplat .block(); assertEquals(userSelectedBucketName, mappedColumnsAndTableName.get("templateBucket")); } + + @Test + public void testContentMD5CalculationForObjectLock() throws NoSuchAlgorithmException { + // Test that MD5 calculation works correctly for Object Lock compliance + String testContent = "Hello, World!"; + byte[] payload = testContent.getBytes(); + + // Calculate MD5 hash using the same logic as in uploadFileInS3 + MessageDigest md5Digest = MessageDigest.getInstance("MD5"); + byte[] md5Hash = md5Digest.digest(payload); + String md5Base64 = java.util.Base64.getEncoder().encodeToString(md5Hash); + + // Verify MD5 calculation is correct + assertNotNull(md5Base64); + assertTrue(md5Base64.length() > 0); + + // For "Hello, World!" the MD5 hash should be deterministic + // Expected MD5 for "Hello, World!" is 65a8e27d8879283831b664bd8b7f0ad4 + String expectedMd5Hex = "65a8e27d8879283831b664bd8b7f0ad4"; + String actualMd5Hex = bytesToHex(md5Hash); + assertEquals(expectedMd5Hex, actualMd5Hex); + + // Verify base64 encoding + String expectedBase64 = java.util.Base64.getEncoder().encodeToString(hexToBytes(expectedMd5Hex)); + assertEquals(expectedBase64, md5Base64); + } + + private String bytesToHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02x", b)); + } + return result.toString(); + } + + private byte[] hexToBytes(String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } }