mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-06 20:19:14 +00:00
fix(odm): reject ambiguous native source dot segments (#7263)
* fix(odm): reject ambiguous native source dot segments * docs(odm): align native provider limitations with implementation
This commit is contained in:
@@ -917,6 +917,18 @@ mod tests {
|
||||
assert!(head.sse.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dot_segment_keys_fail_before_any_source_request() {
|
||||
let (endpoint, recorded) = scripted_server(Vec::new()).await;
|
||||
let backend = backend(&endpoint, Credential::SharedKey(vec![7_u8; 32]));
|
||||
for key in [".", "..", "dir/./key", "dir/../key", "\u{fffe}/../key"] {
|
||||
assert!(matches!(backend.head(key).await, Err(SourceError::Unsupported(_))), "HEAD {key:?}");
|
||||
assert!(matches!(backend.get(key, None).await, Err(SourceError::Unsupported(_))), "GET {key:?}");
|
||||
assert!(matches!(backend.tagging(key).await, Err(SourceError::Unsupported(_))), "tags {key:?}");
|
||||
}
|
||||
assert!(recorded.lock().expect("recorder lock").is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sas_credentials_travel_in_the_query_and_never_sign() {
|
||||
let (endpoint, recorded) = scripted_server(vec![ScriptedResponse::new(200, blob_headers(), String::new())]).await;
|
||||
|
||||
@@ -372,6 +372,17 @@ mod tests {
|
||||
]
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dot_segment_keys_fail_before_any_source_request() {
|
||||
let (endpoint, recorded) = scripted_server(Vec::new()).await;
|
||||
let backend = backend(&endpoint);
|
||||
for key in [".", "..", "dir/./key", "dir/../key", "\u{fffe}/../key"] {
|
||||
assert!(matches!(backend.head(key).await, Err(SourceError::Unsupported(_))), "HEAD {key:?}");
|
||||
assert!(matches!(backend.get(key, None).await, Err(SourceError::Unsupported(_))), "GET {key:?}");
|
||||
}
|
||||
assert!(recorded.lock().expect("recorder lock").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn objects_list_maps_items_prefixes_and_the_page_token() {
|
||||
let page = parse_objects_list(LIST_PAGE_ONE).expect("page should parse");
|
||||
|
||||
@@ -116,7 +116,15 @@ impl NativeHttp {
|
||||
.path_segments_mut()
|
||||
.map_err(|_| SourceError::Other("source endpoint cannot carry a path".to_string()))?;
|
||||
path.clear();
|
||||
path.extend(segments);
|
||||
for segment in segments {
|
||||
// URL normalization drops standalone dot segments. Sending
|
||||
// that URL could fetch another object and backfill its bytes
|
||||
// under the originally requested key.
|
||||
if matches!(segment, "." | "..") {
|
||||
return Err(SourceError::Unsupported("source path contains an unsupported dot segment".to_string()));
|
||||
}
|
||||
path.push(segment);
|
||||
}
|
||||
}
|
||||
Ok(url)
|
||||
}
|
||||
@@ -431,4 +439,44 @@ mod tests {
|
||||
assert_eq!(url.as_str(), "https://acct.blob.core.windows.net/container/dir/a%20b%3Fc%23d.txt");
|
||||
assert_eq!(url.query(), None, "a key with '?' must not become a query");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_http_refuses_dot_segments_instead_of_addressing_another_object() {
|
||||
let http = NativeHttp::for_test(Url::parse("https://source.example.com").expect("origin"));
|
||||
for key in [
|
||||
".",
|
||||
"..",
|
||||
"./key",
|
||||
"../key",
|
||||
"dir/./key",
|
||||
"dir/../key",
|
||||
"dir/.",
|
||||
"dir/..",
|
||||
"\u{fffe}/../key",
|
||||
] {
|
||||
let error = http
|
||||
.url(std::iter::once("bucket").chain(key.split('/')))
|
||||
.expect_err("dot segments must not disappear");
|
||||
assert!(matches!(error, SourceError::Unsupported(_)), "{key:?}: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_http_preserves_ordinary_dots_empty_segments_and_literal_escapes() {
|
||||
let http = NativeHttp::for_test(Url::parse("https://source.example.com").expect("origin"));
|
||||
for (key, path) in [
|
||||
("file.txt", "/bucket/file.txt"),
|
||||
(".hidden/.../tail.", "/bucket/.hidden/.../tail."),
|
||||
("/dir//key/", "/bucket//dir//key/"),
|
||||
("%2e/%2E%2E/key", "/bucket/%252e/%252E%252E/key"),
|
||||
("a+b &?#", "/bucket/a+b%20&%3F%23"),
|
||||
] {
|
||||
let url = http
|
||||
.url(std::iter::once("bucket").chain(key.split('/')))
|
||||
.expect("representable key");
|
||||
assert_eq!(url.path(), path, "{key:?}");
|
||||
assert!(url.query().is_none());
|
||||
assert!(url.fragment().is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user