Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,15 @@ impl Linker {
anyhow::bail!("Destination path must be relative: {}", dest_path);
}

let components: Vec<_> = path.components().collect();

// SECURITY: Reject empty destinations like "" or "." that do not identify a concrete file.
if !components.iter().any(|c| matches!(c, Component::Normal(_))) {
// Optimization: iterate directly to avoid unnecessary Vec allocation.
if !path.components().any(|c| matches!(c, Component::Normal(_))) {
anyhow::bail!("Destination path must not be empty: {}", dest_path);
}

// SECURITY: Reject traversal, root, and drive-prefixed components.
if components.iter().any(|c| {
// Optimization: iterate directly to avoid unnecessary Vec allocation.
if path.components().any(|c| {
matches!(
c,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
Expand Down Expand Up @@ -845,11 +845,15 @@ impl Linker {
Err(_) => continue,
};

let rel_str = rel_path
.components()
.map(|component| component.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/");
// Performance: generating a platform-agnostic relative path for glob matching.
// On Unix, this is zero-allocation (Cow::Borrowed).
// On Windows, it performs exactly one allocation for the backslash replacement.
let rel_os_str = rel_path.to_string_lossy();
let rel_str = if std::path::MAIN_SEPARATOR == '/' {
rel_os_str
} else {
std::borrow::Cow::Owned(rel_os_str.replace(std::path::MAIN_SEPARATOR, "/"))
};

if rel_str.is_empty() {
continue;
Expand Down
Loading