From 91009ca8800e4dad1537c03ffc4e9363d3946ee6 Mon Sep 17 00:00:00 2001 From: ffccites <99155080+PDGGK@users.noreply.github.com> Date: Wed, 12 Aug 2026 11:22:59 +1000 Subject: [PATCH] fix(services/upyun): do not panic when creating the service root create_dir sliced the last byte off the absolute path to drop its trailing slash: let path = build_abs_path(&self.root, path); let path = path[..path.len() - 1].to_string(); build_abs_path returns an empty string when the root is / and the path is /, so path.len() - 1 underflows and the call panics with "attempt to subtract with overflow" rather than returning a Result. The root is reachable from the public API: Operator::create_dir only rejects a path that does not end with /, and / does end with /, so op.create_dir("/") on a default-rooted upyun operator panics inside the library. This was the only `len() - 1]` slice left in core/services; the sibling services already use trim_end_matches('/') for this, which is total and identical for a normalized path since normalize_path leaves exactly one trailing slash. Two unit tests. The root one panics against the previous expression; the trailing-slash one passes either way, so it is a control rather than a second copy of the same assertion. --- core/services/upyun/src/core.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/core/services/upyun/src/core.rs b/core/services/upyun/src/core.rs index 6a0aa6b65008..c7b928292e0a 100644 --- a/core/services/upyun/src/core.rs +++ b/core/services/upyun/src/core.rs @@ -68,6 +68,16 @@ pub struct UpyunCore { pub signer: UpyunSigner, } +/// Build the folder key for a directory path, without its trailing slash. +/// +/// `build_abs_path` returns an empty string for the root of a service whose root is `/`, so +/// slicing off the last byte underflows there. `Operator::create_dir` only checks that the path +/// ends with `/`, and `/` does, so the root reaches this code and a library call panics instead of +/// returning an error. `trim_end_matches` is what the sibling services use and is total. +fn folder_path(root: &str, path: &str) -> String { + build_abs_path(root, path).trim_end_matches('/').to_string() +} + impl Debug for UpyunCore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UpyunCore") @@ -292,8 +302,7 @@ impl UpyunCore { } pub async fn create_dir(&self, ctx: &OperationContext, path: &str) -> Result> { - let path = build_abs_path(&self.root, path); - let path = path[..path.len() - 1].to_string(); + let path = folder_path(&self.root, path); let url = format!( "https://v0.api.upyun.com/{}/{}", @@ -654,3 +663,20 @@ mod error { } pub(super) use error::*; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn folder_path_handles_the_service_root() { + // build_abs_path yields "" here; the previous `path[..path.len() - 1]` underflowed. + assert_eq!(folder_path("/", "/"), ""); + } + + #[test] + fn folder_path_drops_the_trailing_slash() { + assert_eq!(folder_path("/", "a/b/"), "a/b"); + assert_eq!(folder_path("/prefix/", "a/"), "prefix/a"); + } +}