diff --git a/crates/fff-core/src/types.rs b/crates/fff-core/src/types.rs index 45d0578f2..88f2adc53 100644 --- a/crates/fff-core/src/types.rs +++ b/crates/fff-core/src/types.rs @@ -565,7 +565,7 @@ impl FileItem { return; } - let Ok(mut file) = std::fs::OpenOptions::new() + let Ok(file) = std::fs::OpenOptions::new() .write(false) .read(true) .open(path) @@ -573,6 +573,8 @@ impl FileItem { tracing::error!(path = ?path.display(), "Failed to open indexed file"); return; }; + // only classify the first MAX_FFFILE_SIZE bytes, never read large files to EOF + let mut file = file.take(MAX_FFFILE_SIZE); loop { match file.read(chunk) { @@ -584,6 +586,7 @@ impl FileItem { Ok(n) => { if detect_binary_content(&chunk[..n]) { self.set_binary(true); + break; } } } @@ -1146,3 +1149,36 @@ mod content_cache_budget_tests { assert_eq!(budget.max_file_size, default.max_file_size); } } + +#[cfg(test)] +mod detect_binary_tests { + use super::*; + + fn classify(content: &[u8]) -> bool { + let root = tempfile::tempdir().unwrap(); + let path = root.path().join("f"); + std::fs::write(&path, content).unwrap(); + let (item, _) = FileItem::new(path.clone(), root.path(), None); + item.detect_binary_per_byte(&path, &mut [0u8; BINARY_CLASSIFICATION_CHUNK_SIZE]); + item.is_binary() + } + + #[test] + fn detects_nul_in_small_file() { + let mut content = vec![b'a'; BINARY_CLASSIFICATION_CHUNK_SIZE * 3]; + content[10] = 0; + assert!(classify(&content)); + assert!(!classify(b"plain text\n")); + } + + #[test] + fn reads_only_first_max_size_bytes() { + let mut content = vec![b'a'; MAX_FFFILE_SIZE as usize + 1]; + content[0] = 0; + assert!(classify(&content)); + + content[0] = b'a'; + content[MAX_FFFILE_SIZE as usize] = 0; + assert!(!classify(&content)); + } +}