mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 07:57:01 +00:00
fix:ListObjects and ListObjectV2 correctly handles unordered and delimiter (#1285)
This commit is contained in:
@@ -125,6 +125,7 @@ use rustfs_utils::{
|
|||||||
use rustfs_zip::CompressionFormat;
|
use rustfs_zip::CompressionFormat;
|
||||||
use s3s::header::{X_AMZ_RESTORE, X_AMZ_RESTORE_OUTPUT_PATH};
|
use s3s::header::{X_AMZ_RESTORE, X_AMZ_RESTORE_OUTPUT_PATH};
|
||||||
use s3s::{S3, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, dto::*, s3_error};
|
use s3s::{S3, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, dto::*, s3_error};
|
||||||
|
use serde_urlencoded::from_bytes;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -377,6 +378,12 @@ fn derive_part_nonce(base: [u8; 12], part_number: usize) -> [u8; 12] {
|
|||||||
nonce
|
nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, serde::Deserialize)]
|
||||||
|
struct ListObjectUnorderedQuery {
|
||||||
|
#[serde(rename = "allow-unordered")]
|
||||||
|
allow_unordered: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
struct InMemoryAsyncReader {
|
struct InMemoryAsyncReader {
|
||||||
cursor: std::io::Cursor<Vec<u8>>,
|
cursor: std::io::Cursor<Vec<u8>>,
|
||||||
}
|
}
|
||||||
@@ -495,6 +502,37 @@ fn validate_object_key(key: &str, operation: &str) -> S3Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate that 'allow-unordered' parameter is not used with a delimiter
|
||||||
|
///
|
||||||
|
/// This function:
|
||||||
|
/// 1. Checks if a delimiter is specified in the ListObjects request
|
||||||
|
/// 2. Parses the query string to check for the 'allow-unordered' parameter
|
||||||
|
/// 3. Rejects the request if both 'delimiter' and 'allow-unordered=true' are present
|
||||||
|
///
|
||||||
|
/// According to S3 compatibility requirements, unordered listing cannot be combined with
|
||||||
|
/// hierarchical directory traversal (delimited listing). This validation ensures
|
||||||
|
/// conflicting parameters are caught before processing the request.
|
||||||
|
fn validate_list_object_unordered_with_delimiter(delimiter: Option<&Delimiter>, query_string: Option<&str>) -> S3Result<()> {
|
||||||
|
if delimiter.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(query) = query_string else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(params) = from_bytes::<ListObjectUnorderedQuery>(query.as_bytes()) {
|
||||||
|
if params.allow_unordered.as_deref() == Some("true") {
|
||||||
|
return Err(S3Error::with_message(
|
||||||
|
S3ErrorCode::InvalidArgument,
|
||||||
|
"The allow-unordered parameter cannot be used when delimiter is specified.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl FS {
|
impl FS {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// let store: ECStore = ECStore::new(address, endpoint_pools).await?;
|
// let store: ECStore = ECStore::new(address, endpoint_pools).await?;
|
||||||
@@ -2818,6 +2856,9 @@ impl S3 for FS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let delimiter = delimiter.filter(|v| !v.is_empty());
|
let delimiter = delimiter.filter(|v| !v.is_empty());
|
||||||
|
|
||||||
|
validate_list_object_unordered_with_delimiter(delimiter.as_ref(), req.uri.query())?;
|
||||||
|
|
||||||
let start_after = start_after.filter(|v| !v.is_empty());
|
let start_after = start_after.filter(|v| !v.is_empty());
|
||||||
|
|
||||||
let continuation_token = continuation_token.filter(|v| !v.is_empty());
|
let continuation_token = continuation_token.filter(|v| !v.is_empty());
|
||||||
@@ -6115,6 +6156,29 @@ mod tests {
|
|||||||
set_buffer_profile_enabled(false);
|
set_buffer_profile_enabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_list_object_unordered_with_delimiter() {
|
||||||
|
// [1] Normal case: No delimiter specified.
|
||||||
|
assert!(validate_list_object_unordered_with_delimiter(None, Some("allow-unordered=true")).is_ok());
|
||||||
|
|
||||||
|
let delim_str = "/".to_string();
|
||||||
|
let delimiter_some: Option<&Delimiter> = Some(&delim_str);
|
||||||
|
// [2] Normal case: Delimiter is present, but 'allow-unordered' is explicitly set to false.
|
||||||
|
assert!(validate_list_object_unordered_with_delimiter(delimiter_some, Some("allow-unordered=false")).is_ok());
|
||||||
|
|
||||||
|
let query_conflict = Some("allow-unordered=true");
|
||||||
|
// [3] Conflict case: Both delimiter and 'allow-unordered=true' are present.
|
||||||
|
assert!(validate_list_object_unordered_with_delimiter(delimiter_some, query_conflict).is_err());
|
||||||
|
|
||||||
|
let complex_query = Some("allow-unordered=true&abc=123");
|
||||||
|
// [4] Complex query: The validation should still trigger if 'allow-unordered=true' is part of a multi-parameter query.
|
||||||
|
assert!(validate_list_object_unordered_with_delimiter(delimiter_some, complex_query).is_err());
|
||||||
|
|
||||||
|
let complex_query_without_unordered = Some("abc=123&queryType=test");
|
||||||
|
// [5] Multi-parameter query without conflict: If other parameters exist but 'allow-unordered' is missing,
|
||||||
|
assert!(validate_list_object_unordered_with_delimiter(delimiter_some, complex_query_without_unordered).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
// Note: S3Request structure is complex and requires many fields.
|
// Note: S3Request structure is complex and requires many fields.
|
||||||
// For real testing, we would need proper integration test setup.
|
// For real testing, we would need proper integration test setup.
|
||||||
// Removing this test as it requires too much S3 infrastructure setup.
|
// Removing this test as it requires too much S3 infrastructure setup.
|
||||||
|
|||||||
Reference in New Issue
Block a user