fix(sync): S-07 — skip invalid windows filenames with files_skipped_invalid counter

This commit is contained in:
2026-09-19 09:31:25 +02:00
parent 1ba67cc9e2
commit 15adf79f48
4 changed files with 147 additions and 3 deletions
+12
View File
@@ -1493,6 +1493,12 @@ fn handle_sync(
stats.files_skipped_symlinks
);
}
if stats.files_skipped_invalid > 0 {
println!(
" • Ungültige Namen: {} übersprungen",
stats.files_skipped_invalid
);
}
if delete {
println!(
" • Zu löschen: {} Dateien/Ordner",
@@ -1526,6 +1532,12 @@ fn handle_sync(
stats.files_skipped_symlinks
);
}
if stats.files_skipped_invalid > 0 {
println!(
" • Ungültige Namen: {} übersprungen",
stats.files_skipped_invalid
);
}
if delete {
println!(
" • Gelöscht: {} verwaiste Dateien/Ordner",
+10
View File
@@ -1,5 +1,15 @@
use anyhow::{bail, Result};
/// Prüft, ob ein Name Path-Traversal-Muster oder Pfadtrennzeichen enthält (S-02).
pub fn is_path_traversal(name: &str) -> bool {
name.is_empty()
|| name == "."
|| name == ".."
|| name.contains('/')
|| name.contains('\\')
|| name.contains('\0')
}
/// Validiert einen Datei- oder Verzeichnisnamen gegen Path-Traversal, Null-Bytes,
/// unzulässige Steuerzeichen und Windows-reservierte Gerätenamen (S-08, R-03).
pub fn validate_node_name(name: &str) -> Result<()> {
+43 -3
View File
@@ -13,7 +13,7 @@ use crate::storage::{Database, NodeRecord};
use crate::ui;
use crate::vfs::is_leak_file;
pub use crate::pathutil::validate_node_name;
pub use crate::pathutil::{is_path_traversal, validate_node_name};
/// Berechnet den SHA-256 Hash einer lokalen Datei für verlässliche Checksummen-Vergleiche (S-10).
fn calc_local_file_sha256(path: &Path) -> Result<String> {
@@ -108,6 +108,7 @@ pub struct SyncStats {
pub files_transferred: usize,
pub files_skipped: usize,
pub files_skipped_symlinks: usize,
pub files_skipped_invalid: usize,
pub files_deleted: usize,
pub bytes_transferred: u64,
pub elapsed: Duration,
@@ -645,7 +646,26 @@ fn collect_and_push_dir(
let entry = entry?;
let path = entry.path();
let file_name = entry.file_name().to_string_lossy().to_string();
validate_node_name(&file_name)?;
if is_path_traversal(&file_name) {
bail!(
"Path traversal Versuch erkannt in Dateinamen: '{}'",
file_name
);
}
if let Err(e) = validate_node_name(&file_name) {
stats.files_scanned += 1;
stats.files_skipped += 1;
stats.files_skipped_invalid += 1;
if !options.quiet {
println!(
" {} Ungültiger Dateiname übersprungen: {} ({})",
ui::yellow("[!]"),
file_name,
e
);
}
continue;
}
let rel_path = path
.strip_prefix(base_dir)
@@ -991,7 +1011,27 @@ fn collect_and_pull_dir(
continue;
}
validate_node_name(&child.name)?;
if is_path_traversal(&child.name) {
bail!(
"Path traversal Versuch erkannt in Knotennamen: '{}'",
child.name
);
}
if let Err(e) = validate_node_name(&child.name) {
stats.files_scanned += 1;
stats.files_skipped += 1;
stats.files_skipped_invalid += 1;
if !options.quiet {
println!(
" {} Ungültiger Dateiname im Tresor übersprungen: {} ({})",
ui::yellow("[!]"),
child.name,
e
);
}
continue;
}
let child_rel = if rel_prefix.is_empty() {
child.name.clone()