mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 16:48:58 +00:00
@@ -240,7 +240,7 @@ impl HashReader {
|
||||
let er = EtagReader::new(inner, md5hex.clone());
|
||||
inner = Box::new(er);
|
||||
}
|
||||
} else if !diskable_md5 {
|
||||
} else if size != Self::SIZE_PRESERVE_LAYER && !diskable_md5 {
|
||||
let er = EtagReader::new(inner, md5hex.clone());
|
||||
inner = Box::new(er);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,8 @@ pub const AMZ_META_NAME: &str = "X-Amz-Meta-Name";
|
||||
|
||||
pub const AMZ_META_UNENCRYPTED_CONTENT_LENGTH: &str = "X-Amz-Meta-X-Amz-Unencrypted-Content-Length";
|
||||
pub const AMZ_META_UNENCRYPTED_CONTENT_MD5: &str = "X-Amz-Meta-X-Amz-Unencrypted-Content-Md5";
|
||||
pub const RUSTFS_ENCRYPTION: &str = "X-Rustfs-Encryption-";
|
||||
pub const RUSTFS_ENCRYPTION_LOWER: &str = "x-rustfs-encryption-";
|
||||
|
||||
pub const RESERVED_METADATA_PREFIX: &str = "X-RustFS-Internal-";
|
||||
pub const RESERVED_METADATA_PREFIX_LOWER: &str = "x-rustfs-internal-";
|
||||
|
||||
@@ -5746,7 +5746,8 @@ impl S3 for FS {
|
||||
if let Some((key_bytes, base_nonce, _)) = decrypt_managed_encryption_key(&bucket, &key, &fi.user_defined).await? {
|
||||
let part_nonce = derive_part_nonce(base_nonce, part_id);
|
||||
let encrypt_reader = EncryptReader::new(reader, key_bytes, part_nonce);
|
||||
reader = HashReader::new(Box::new(encrypt_reader), -1, actual_size, None, None, false).map_err(ApiError::from)?;
|
||||
reader = HashReader::new(Box::new(encrypt_reader), HashReader::SIZE_PRESERVE_LAYER, actual_size, None, None, false)
|
||||
.map_err(ApiError::from)?;
|
||||
}
|
||||
|
||||
let mut reader = PutObjReader::new(reader);
|
||||
|
||||
@@ -580,7 +580,7 @@ pub(crate) fn parse_object_lock_legal_hold(legal_hold: Option<ObjectLockLegalHol
|
||||
None => String::default(),
|
||||
};
|
||||
let now = OffsetDateTime::now_utc();
|
||||
// This is intentional behavior. Empty string represents "status cleared" which is different from "status never set". Consistent with minio
|
||||
// This is intentional behavior. Empty string represents "status cleared" which is different from "status never set".
|
||||
eval_metadata.insert(AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER.to_string(), status);
|
||||
eval_metadata.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "objectlock-legalhold-timestamp"),
|
||||
|
||||
@@ -33,6 +33,7 @@ use rustfs_utils::http::RUSTFS_BUCKET_REPLICATION_DELETE_MARKER;
|
||||
use rustfs_utils::http::RUSTFS_BUCKET_REPLICATION_REQUEST;
|
||||
use rustfs_utils::http::RUSTFS_BUCKET_REPLICATION_SSEC_CHECKSUM;
|
||||
use rustfs_utils::http::RUSTFS_BUCKET_SOURCE_VERSION_ID;
|
||||
use rustfs_utils::http::RUSTFS_ENCRYPTION_LOWER;
|
||||
use rustfs_utils::path::is_dir_object;
|
||||
use s3s::{S3Result, s3_error};
|
||||
use std::collections::HashMap;
|
||||
@@ -126,19 +127,28 @@ pub async fn get_opts(
|
||||
|
||||
let vid = vid.map(|v| v.as_str().trim().to_owned());
|
||||
|
||||
if let Some(ref id) = vid
|
||||
&& *id != Uuid::nil().to_string()
|
||||
&& let Err(_err) = Uuid::parse_str(id.as_str())
|
||||
{
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
let nil_uuid_str = Uuid::nil().to_string();
|
||||
|
||||
let vid = match vid {
|
||||
Some(ref id) => {
|
||||
if id.eq_ignore_ascii_case("null") {
|
||||
Some(nil_uuid_str.clone())
|
||||
} else {
|
||||
if id.as_str() != nil_uuid_str.as_str() && Uuid::parse_str(id).is_err() {
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
Some(id.clone())
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let mut opts = get_default_opts(headers, HashMap::new(), false)
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
Some(Uuid::nil().to_string())
|
||||
Some(nil_uuid_str)
|
||||
} else {
|
||||
vid
|
||||
}
|
||||
@@ -372,12 +382,18 @@ pub(crate) fn filter_object_metadata(metadata: &HashMap<String, String>) -> Opti
|
||||
if lower_key.starts_with(RESERVED_METADATA_PREFIX_LOWER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip internal encryption metadata
|
||||
if lower_key.starts_with(RUSTFS_ENCRYPTION_LOWER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty object lock values
|
||||
if v.is_empty() && (k == &X_AMZ_OBJECT_LOCK_MODE.to_string() || k == &X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE.to_string()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip encryption metadata placeholders
|
||||
// Skip UNENCRYPTED metadata placeholders
|
||||
if k == AMZ_META_UNENCRYPTED_CONTENT_MD5 || k == AMZ_META_UNENCRYPTED_CONTENT_LENGTH {
|
||||
continue;
|
||||
}
|
||||
@@ -726,6 +742,19 @@ mod tests {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_ops_with_null_version_id() {
|
||||
let headers = create_test_headers();
|
||||
let result = get_opts("test-bucket", "test-object", Some("null".to_string()), None, &headers).await;
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.version_id, Some(Uuid::nil().to_string()));
|
||||
let result = get_opts("test-bucket", "test-object", Some("NULL".to_string()), None, &headers).await;
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.version_id, Some(Uuid::nil().to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_opts_basic() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
Reference in New Issue
Block a user