diff --git a/crates/openshell-bootstrap/src/build.rs b/crates/openshell-bootstrap/src/build.rs index efa040c34a..f0e03fcd89 100644 --- a/crates/openshell-bootstrap/src/build.rs +++ b/crates/openshell-bootstrap/src/build.rs @@ -398,7 +398,9 @@ fn glob_match(pattern: &str, path: &str, is_dir: bool) -> bool { return true; } for (idx, _) in path.match_indices('/') { - if glob_match(rest, &path[idx + 1..], is_dir) { + if let Some(suffix) = path.get(idx + 1..) + && glob_match(rest, suffix, is_dir) + { return true; } } @@ -592,6 +594,7 @@ mod tests { assert!(glob_match("**/*.log", "a/b/c.log", false)); assert!(glob_match("**/*.log", "c.log", false)); assert!(!glob_match("**/*.log", "c.txt", false)); + assert!(glob_match("**/föö.log", "a/b/föö.log", false)); } #[test] diff --git a/crates/openshell-bootstrap/src/metadata.rs b/crates/openshell-bootstrap/src/metadata.rs index fe4c277bd2..8190fac829 100644 --- a/crates/openshell-bootstrap/src/metadata.rs +++ b/crates/openshell-bootstrap/src/metadata.rs @@ -142,7 +142,8 @@ pub fn extract_host_from_ssh_destination(destination: &str) -> String { // Handle user@host format dest.find('@') - .map_or_else(|| dest.to_string(), |at_pos| dest[at_pos + 1..].to_string()) + .and_then(|at_pos| dest.get(at_pos + 1..)) + .map_or_else(|| dest.to_string(), ToString::to_string) } /// Resolve an SSH host alias to the actual hostname or IP address. @@ -457,6 +458,11 @@ mod tests { ); } + #[test] + fn extract_host_user_at_multi_byte_hostname() { + assert_eq!(extract_host_from_ssh_destination("user@mülti"), "mülti"); + } + #[test] fn metadata_roundtrip() { let meta = GatewayMetadata { diff --git a/crates/openshell-router/src/backend.rs b/crates/openshell-router/src/backend.rs index 9fbd538c06..be908055ee 100644 --- a/crates/openshell-router/src/backend.rs +++ b/crates/openshell-router/src/backend.rs @@ -824,8 +824,12 @@ fn build_backend_url(endpoint: &str, path: &str) -> String { let base_path = &base[path_start..]; base_path.starts_with("/v1/") || base_path.ends_with("/v1") }); - if base_path_has_v1_edge_segment && (path == "/v1" || path.starts_with("/v1/")) { - return format!("{base}{}", &path[3..]); + if base_path_has_v1_edge_segment + && let Some(rest) = path + .strip_prefix("/v1") + .filter(|rest| rest.is_empty() || rest.starts_with('/')) + { + return format!("{base}{rest}"); } format!("{base}{path}") diff --git a/crates/openshell-supervisor-network/src/l7/inference.rs b/crates/openshell-supervisor-network/src/l7/inference.rs index e06dffd324..5aefab9e93 100644 --- a/crates/openshell-supervisor-network/src/l7/inference.rs +++ b/crates/openshell-supervisor-network/src/l7/inference.rs @@ -179,7 +179,10 @@ pub fn detect_inference_pattern<'a>( let Some(slash_at) = rest.find('/') else { return false; }; - return slash_at > 0 && rest[slash_at + 1..] == *after; + let Some(after_segment) = rest.get(slash_at + 1..) else { + return false; + }; + return slash_at > 0 && after_segment == after; } path_only == p.path_glob diff --git a/crates/openshell-supervisor-process/src/process.rs b/crates/openshell-supervisor-process/src/process.rs index 322dcadd86..3733c7c7e3 100644 --- a/crates/openshell-supervisor-process/src/process.rs +++ b/crates/openshell-supervisor-process/src/process.rs @@ -1101,11 +1101,8 @@ fn rewrite_passwd_at(path: &Path, uid: &str, gid: &str) -> Result<()> { if line.starts_with("sandbox:") { found = true; let fields: Vec<&str> = line.split(':').collect(); - if fields.len() >= 7 { - format!( - "{}:{}:{}:{}:{}:{}:{}", - fields[0], fields[1], uid, gid, fields[4], fields[5], fields[6] - ) + if let [name, pass, _, _, gecos, home, shell, ..] = fields.as_slice() { + format!("{name}:{pass}:{uid}:{gid}:{gecos}:{home}:{shell}") } else { line.to_string() } @@ -1139,8 +1136,8 @@ fn rewrite_group_at(path: &Path, gid: &str) -> Result<()> { if line.starts_with("sandbox:") { found = true; let fields: Vec<&str> = line.split(':').collect(); - if fields.len() >= 4 { - format!("{}:{}:{}:{}", fields[0], fields[1], gid, fields[3]) + if let [name, pass, _, members, ..] = fields.as_slice() { + format!("{name}:{pass}:{gid}:{members}") } else { line.to_string() } @@ -2302,6 +2299,30 @@ mod tests { assert!(content.contains("sandbox:x:6000:")); } + #[cfg(unix)] + #[test] + fn rewrite_passwd_leaves_malformed_entry_unchanged() { + let dir = tempfile::tempdir().unwrap(); + let passwd = dir.path().join("passwd"); + // Only 3 fields — slice pattern should fall through instead of panic. + std::fs::write(&passwd, "sandbox:x:1000\n").unwrap(); + rewrite_passwd_at(&passwd, "5000", "6000").unwrap(); + let content = std::fs::read_to_string(&passwd).unwrap(); + assert!(content.contains("sandbox:x:1000")); + } + + #[cfg(unix)] + #[test] + fn rewrite_group_leaves_malformed_entry_unchanged() { + let dir = tempfile::tempdir().unwrap(); + let group = dir.path().join("group"); + // Only 2 fields — slice pattern should fall through instead of panic. + std::fs::write(&group, "sandbox:x\n").unwrap(); + rewrite_group_at(&group, "6000").unwrap(); + let content = std::fs::read_to_string(&group).unwrap(); + assert!(content.contains("sandbox:x")); + } + #[cfg(unix)] #[test] fn rewrite_passwd_preserves_other_entries() {