feat: Guará 0.1.0 — recuperação de arquivos (núcleo, CLI e interface gráfica)

Núcleo em Rust somente leitura: NTFS, FAT, exFAT, partições MBR/GPT e perdidas,
carving por estrutura, fusão sem duplicatas, confiança por arquivo, imagem
forense, pausar/retomar. CLI `guara`, UI web/Tauri, CI e releases por tag.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-18 15:02:19 -03:00
commit 765cb0d4a2
57 files changed
+8621

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
//! Recuperação de ponta a ponta sobre imagens reais (FAT32, NTFS, ext4).
mod common;
use common::*;
use guara_core::*;
fn check(fs: &str, only_carvable: bool) {
let Some(dir) = fixtures() else { return };
let img = dir.join(format!("{fs}.img"));
let before = sha256_file(&img);
let res = scan_path(&img, &ScanOptions::default());
let out = tempfile::tempdir().unwrap();
let rep = recover_all(&img, &res.found, out.path());
assert!(rep.failed.is_empty(), "falhas: {:?}", rep.failed);
let got = hashes_in(out.path());
for (hash, path) in expected(&dir, fs) {
let carvable = [".jpg", ".png", ".pdf", ".docx", ".db"]
.iter()
.any(|e| path.ends_with(e));
if only_carvable && !carvable {
continue;
}
assert!(
got.contains_key(&hash),
"[{fs}] não recuperado bit a bit: {path}"
);
}
// Os dois métodos juntos não podem listar o mesmo conteúdo duas vezes.
let mut seen = std::collections::HashSet::new();
for f in &res.found {
if let Some(o) = f.first_offset() {
assert!(seen.insert(o), "[{fs}] achado duplicado no offset {o}");
}
}
assert_eq!(before, sha256_file(&img), "a origem foi modificada!");
}
#[test]
fn fat32_recupera_apagados_com_nome_e_pasta() {
check("fat32", false);
let Some(dir) = fixtures() else { return };
let res = scan_path(&dir.join("fat32.img"), &ScanOptions::default());
assert!(res
.found
.iter()
.any(|f| f.dir.as_deref() == Some("Documentos/Trabalho") && f.name == "relatorio.pdf"));
assert!(
res.found.iter().all(|f| f.name != "mantida.jpg"),
"arquivo existente não deve ser listado"
);
}
#[test]
fn ntfs_recupera_apagados() {
check("ntfs", false);
}
#[test]
fn ext4_recupera_por_assinatura() {
check("ext4", true);
}
#[test]
fn varredura_rapida_nao_faz_carving() {
let Some(dir) = fixtures() else { return };
let res = scan_path(
&dir.join("ext4.img"),
&ScanOptions {
mode: ScanMode::Quick,
..Default::default()
},
);
assert!(res.found.is_empty());
let res = scan_path(
&dir.join("ntfs.img"),
&ScanOptions {
mode: ScanMode::Quick,
..Default::default()
},
);
assert_eq!(
res.found
.iter()
.filter(|f| f.origin == Origin::Ntfs)
.count(),
7
);
}