fix(vfs): V-07 — rfc-4918 destination overwrite semantics and directory conflict check in copy
This commit is contained in:
+74
-7
@@ -1043,17 +1043,30 @@ impl DavFileSystem for SanctumFs {
|
|||||||
return Err(FsError::Forbidden);
|
return Err(FsError::Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schutz der Trägerdatei im Decoy Vault: Überschreiben durch Copy verboten!
|
|
||||||
if let Some(dest) = self.resolve_path(&to_str)? {
|
|
||||||
if self.carrier_node_id == Some(dest.id) {
|
|
||||||
return Err(FsError::Forbidden);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let to_parent = self
|
let to_parent = self
|
||||||
.resolve_path(to_parent_path)?
|
.resolve_path(to_parent_path)?
|
||||||
.ok_or(FsError::NotFound)?;
|
.ok_or(FsError::NotFound)?;
|
||||||
|
|
||||||
|
if !to_parent.is_dir {
|
||||||
|
return Err(FsError::Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC-4918 (V-07): Falls Zieldatei bereits existiert:
|
||||||
|
// - Wenn Verzeichnis: 403 Forbidden
|
||||||
|
// - Wenn Datei: bestehende Datei vor dem Kopieren löschen & shredden
|
||||||
|
if let Some(dest) = self.resolve_path(&to_str)? {
|
||||||
|
if dest.is_dir {
|
||||||
|
return Err(FsError::Forbidden);
|
||||||
|
}
|
||||||
|
// Schutz der Trägerdatei im Decoy Vault: Überschreiben durch Copy verboten!
|
||||||
|
if self.carrier_node_id == Some(dest.id) {
|
||||||
|
return Err(FsError::Forbidden);
|
||||||
|
}
|
||||||
|
self.db
|
||||||
|
.delete_node(dest.id)
|
||||||
|
.map_err(|_| FsError::GeneralFailure)?;
|
||||||
|
}
|
||||||
|
|
||||||
let dest_node = self.create_node(to_parent.id, to_name, false)?;
|
let dest_node = self.create_node(to_parent.id, to_name, false)?;
|
||||||
|
|
||||||
// Kopiere alle Chunks und re-verschlüssele mit neuer node_id (wegen AAD-Bindung!)
|
// Kopiere alle Chunks und re-verschlüssele mit neuer node_id (wegen AAD-Bindung!)
|
||||||
@@ -1568,4 +1581,58 @@ mod tests {
|
|||||||
let end_bytes = read_file.read_bytes(3).await.unwrap();
|
let end_bytes = read_file.read_bytes(3).await.unwrap();
|
||||||
assert_eq!(&end_bytes[..], b"END");
|
assert_eq!(&end_bytes[..], b"END");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_v07_copy_overwrite_existing_file_semantics() {
|
||||||
|
let (fs, _dir) = create_test_fs(true);
|
||||||
|
let src_path = DavPath::new("/source.txt").unwrap();
|
||||||
|
let dest_path = DavPath::new("/dest.txt").unwrap();
|
||||||
|
|
||||||
|
// 1. Erstelle Quelldatei
|
||||||
|
let mut opts1 = OpenOptions::default();
|
||||||
|
opts1.write = true;
|
||||||
|
opts1.create_new = true;
|
||||||
|
let mut src_file = fs.open(&src_path, opts1).await.unwrap();
|
||||||
|
let src_data = b"Freshly copied source data (RFC-4918)";
|
||||||
|
src_file
|
||||||
|
.write_bytes(Bytes::copy_from_slice(src_data))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
src_file.flush().await.unwrap();
|
||||||
|
drop(src_file);
|
||||||
|
|
||||||
|
// 2. Erstelle Zieldatei mit abweichendem Inhalt und Größe
|
||||||
|
let mut opts2 = OpenOptions::default();
|
||||||
|
opts2.write = true;
|
||||||
|
opts2.create_new = true;
|
||||||
|
let mut dest_file = fs.open(&dest_path, opts2).await.unwrap();
|
||||||
|
let old_dest_data = b"Old obsolete destination data that MUST be overwritten";
|
||||||
|
dest_file
|
||||||
|
.write_bytes(Bytes::copy_from_slice(old_dest_data))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
dest_file.flush().await.unwrap();
|
||||||
|
drop(dest_file);
|
||||||
|
|
||||||
|
// 3. Kopiere Quelle auf existierendes Ziel (RFC-4918 Copy Overwrite)
|
||||||
|
fs.copy(&src_path, &dest_path)
|
||||||
|
.await
|
||||||
|
.expect("Copy with overwrite must succeed per RFC-4918");
|
||||||
|
|
||||||
|
// 4. Verifiziere Zielinhalt
|
||||||
|
let mut read_opts = OpenOptions::default();
|
||||||
|
read_opts.read = true;
|
||||||
|
let mut read_dest = fs.open(&dest_path, read_opts).await.unwrap();
|
||||||
|
let read_bytes = read_dest.read_bytes(src_data.len() + 100).await.unwrap();
|
||||||
|
assert_eq!(&read_bytes[..], src_data);
|
||||||
|
|
||||||
|
let dest_node = fs.resolve_path("/dest.txt").unwrap().unwrap();
|
||||||
|
assert_eq!(dest_node.size, src_data.len() as u64);
|
||||||
|
|
||||||
|
// 5. Test: Kopieren auf existierendes Verzeichnis muss mit Forbidden fehlschlagen
|
||||||
|
let dir_path = DavPath::new("/sub_dir").unwrap();
|
||||||
|
fs.create_dir(&dir_path).await.unwrap();
|
||||||
|
let copy_to_dir_res = fs.copy(&src_path, &dir_path).await;
|
||||||
|
assert!(matches!(copy_to_dir_res, Err(FsError::Forbidden)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user