fix(s3): return BadDigest for Content-MD5 mismatch (#6842)

This commit is contained in:
Zhengchao An
2026-08-29 17:00:18 +08:00
committed by GitHub
parent 11c6ee42ea
commit e1ea99ff06
2 changed files with 30 additions and 2 deletions
+15 -2
View File
@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
use crate::compress_index::{Index, TryGetIndex}; use crate::compress_index::{Index, TryGetIndex};
use crate::{EtagResolvable, HashReaderDetector, HashReaderMut}; use crate::{BadDigest, EtagResolvable, HashReaderDetector, HashReaderMut};
use md5::{Digest, Md5}; use md5::{Digest, Md5};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use std::pin::Pin; use std::pin::Pin;
@@ -89,7 +89,13 @@ where
&& *checksum != etag && *checksum != etag
{ {
error!("Checksum mismatch, expected={:?}, actual={:?}", checksum, etag); error!("Checksum mismatch, expected={:?}, actual={:?}", checksum, etag);
return Poll::Ready(Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Checksum mismatch"))); return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
BadDigest {
expected_md5: checksum.clone(),
calculated_md5: etag,
},
)));
} }
} }
} }
@@ -255,6 +261,7 @@ mod tests {
async fn test_etag_reader_checksum_mismatch() { async fn test_etag_reader_checksum_mismatch() {
let data = b"checksum test data"; let data = b"checksum test data";
let wrong_checksum = "deadbeefdeadbeefdeadbeefdeadbeef".to_string(); let wrong_checksum = "deadbeefdeadbeefdeadbeefdeadbeef".to_string();
let calculated_md5 = hex_simd::encode_to_string(Md5::digest(data), hex_simd::AsciiCase::Lower);
let reader = BufReader::new(&data[..]); let reader = BufReader::new(&data[..]);
let mut etag_reader = EtagReader::new(reader, Some(wrong_checksum.clone())); let mut etag_reader = EtagReader::new(reader, Some(wrong_checksum.clone()));
@@ -262,5 +269,11 @@ mod tests {
// Verification failed, should return InvalidData error // Verification failed, should return InvalidData error
let err = etag_reader.read_to_end(&mut buf).await.unwrap_err(); let err = etag_reader.read_to_end(&mut buf).await.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData); assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
let digest = err
.get_ref()
.and_then(|source| source.downcast_ref::<BadDigest>())
.expect("checksum mismatch should preserve the BadDigest type");
assert_eq!(digest.expected_md5, wrong_checksum);
assert_eq!(digest.calculated_md5, calculated_md5);
} }
} }
+15
View File
@@ -621,6 +621,21 @@ mod tests {
assert_eq!(api_error.message, ApiError::error_code_to_message(&S3ErrorCode::BadDigest)); assert_eq!(api_error.message, ApiError::error_code_to_message(&S3ErrorCode::BadDigest));
} }
#[test]
fn content_md5_mismatch_io_errors_map_to_bad_digest() {
let bad_digest = || rustfs_rio::BadDigest {
expected_md5: "expected".to_string(),
calculated_md5: "calculated".to_string(),
};
let api_error = ApiError::from(IoError::new(ErrorKind::InvalidData, bad_digest()));
assert_eq!(api_error.code, S3ErrorCode::BadDigest);
assert_eq!(api_error.message, ApiError::error_code_to_message(&S3ErrorCode::BadDigest));
let api_error = ApiError::from(StorageError::Io(IoError::new(ErrorKind::InvalidData, bad_digest())));
assert_eq!(api_error.code, S3ErrorCode::BadDigest);
assert_eq!(api_error.message, ApiError::error_code_to_message(&S3ErrorCode::BadDigest));
}
#[test] #[test]
fn upload_stream_sha256_mismatch_maps_to_bad_digest() { fn upload_stream_sha256_mismatch_maps_to_bad_digest() {
let api_error = ApiError::from(IoError::other(MockUploadStreamError::Sha256Mismatch)); let api_error = ApiError::from(IoError::other(MockUploadStreamError::Sha256Mismatch));