mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 23:26:53 +00:00
Add comprehensive special character handling with validation refactoring and extensive test coverage (#1078)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -452,6 +452,31 @@ fn is_managed_sse(algorithm: &ServerSideEncryption) -> bool {
|
||||
matches!(algorithm.as_str(), "AES256" | "aws:kms")
|
||||
}
|
||||
|
||||
/// Validate object key for control characters and log special characters
|
||||
///
|
||||
/// This function:
|
||||
/// 1. Rejects keys containing control characters (null bytes, newlines, carriage returns)
|
||||
/// 2. Logs debug information for keys containing spaces, plus signs, or percent signs
|
||||
///
|
||||
/// The s3s library handles URL decoding, so keys are already decoded when they reach this function.
|
||||
/// This validation ensures that invalid characters that could cause issues are rejected early.
|
||||
fn validate_object_key(key: &str, operation: &str) -> S3Result<()> {
|
||||
// Validate object key doesn't contain control characters
|
||||
if key.contains(['\0', '\n', '\r']) {
|
||||
return Err(S3Error::with_message(
|
||||
S3ErrorCode::InvalidArgument,
|
||||
format!("Object key contains invalid control characters: {:?}", key),
|
||||
));
|
||||
}
|
||||
|
||||
// Log debug info for keys with special characters to help diagnose encoding issues
|
||||
if key.contains([' ', '+', '%']) {
|
||||
debug!("{} object with special characters in key: {:?}", operation, key);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl FS {
|
||||
pub fn new() -> Self {
|
||||
// let store: ECStore = ECStore::new(address, endpoint_pools).await?;
|
||||
@@ -779,6 +804,10 @@ impl S3 for FS {
|
||||
} => (bucket.to_string(), key.to_string(), version_id.map(|v| v.to_string())),
|
||||
};
|
||||
|
||||
// Validate both source and destination keys
|
||||
validate_object_key(&src_key, "COPY (source)")?;
|
||||
validate_object_key(&key, "COPY (dest)")?;
|
||||
|
||||
// warn!("copy_object {}/{}, to {}/{}", &src_bucket, &src_key, &bucket, &key);
|
||||
|
||||
let mut src_opts = copy_src_opts(&src_bucket, &src_key, &req.headers).map_err(ApiError::from)?;
|
||||
@@ -1230,6 +1259,9 @@ impl S3 for FS {
|
||||
bucket, key, version_id, ..
|
||||
} = req.input.clone();
|
||||
|
||||
// Validate object key
|
||||
validate_object_key(&key, "DELETE")?;
|
||||
|
||||
let replica = req
|
||||
.headers
|
||||
.get(AMZ_BUCKET_REPLICATION_STATUS)
|
||||
@@ -1692,6 +1724,9 @@ impl S3 for FS {
|
||||
..
|
||||
} = req.input.clone();
|
||||
|
||||
// Validate object key
|
||||
validate_object_key(&key, "GET")?;
|
||||
|
||||
// Try to get from cache for small, frequently accessed objects
|
||||
let manager = get_concurrency_manager();
|
||||
// Generate cache key with version support: "{bucket}/{key}" or "{bucket}/{key}?versionId={vid}"
|
||||
@@ -2314,6 +2349,9 @@ impl S3 for FS {
|
||||
..
|
||||
} = req.input.clone();
|
||||
|
||||
// Validate object key
|
||||
validate_object_key(&key, "HEAD")?;
|
||||
|
||||
let part_number = part_number.map(|v| v as usize);
|
||||
|
||||
if let Some(part_num) = part_number {
|
||||
@@ -2575,6 +2613,12 @@ impl S3 for FS {
|
||||
} = req.input;
|
||||
|
||||
let prefix = prefix.unwrap_or_default();
|
||||
|
||||
// Log debug info for prefixes with special characters to help diagnose encoding issues
|
||||
if prefix.contains([' ', '+', '%', '\n', '\r', '\0']) {
|
||||
debug!("LIST objects with special characters in prefix: {:?}", prefix);
|
||||
}
|
||||
|
||||
let max_keys = max_keys.unwrap_or(1000);
|
||||
if max_keys < 0 {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InvalidArgument, "Invalid max keys".to_string()));
|
||||
@@ -2798,6 +2842,9 @@ impl S3 for FS {
|
||||
..
|
||||
} = input;
|
||||
|
||||
// Validate object key
|
||||
validate_object_key(&key, "PUT")?;
|
||||
|
||||
if if_match.is_some() || if_none_match.is_some() {
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
|
||||
Reference in New Issue
Block a user