use crate::prelude::*;
use content_inspector::{inspect, ContentType};
use std::fs::File;
use std::io::{BufReader, Read};
pub struct BinaryDetector {
max_sample_size: usize,
binary_threshold: f64,
}
impl Default for BinaryDetector {
fn default() -> Self {
Self {
max_sample_size: 8192, binary_threshold: 0.3, }
}
}
impl BinaryDetector {
pub fn new(max_sample_size: usize, binary_threshold: f64) -> Self {
Self {
max_sample_size,
binary_threshold,
}
}
pub fn is_binary<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
let path = path.as_ref();
if self.is_binary_by_extension(path) {
return Ok(true);
}
if let Ok(is_binary_by_signature) = self.check_binary_signature_only(path) {
if is_binary_by_signature {
return Ok(true);
}
}
if let Ok(content_type) = self.detect_by_content_inspector(path) {
match content_type {
ContentType::BINARY => return Ok(true),
ContentType::UTF_8
| ContentType::UTF_8_BOM
| ContentType::UTF_16LE
| ContentType::UTF_16BE
| ContentType::UTF_32LE
| ContentType::UTF_32BE => return Ok(false),
}
}
self.is_binary_by_content_analysis(path)
}
fn is_binary_by_extension(&self, path: &Path) -> bool {
if let Some(extension) = path.extension() {
if let Some(ext_str) = extension.to_str() {
let ext_lower = ext_str.to_lowercase();
return BINARY_EXTENSIONS.contains(&ext_lower.as_str());
}
}
false
}
fn check_binary_signature_only(&self, path: &Path) -> Result<bool> {
let file = File::open(path).with_context(|| {
format!(
"Failed to open file for signature check: {}",
path.display()
)
})?;
let mut reader = BufReader::new(file);
let mut buffer = vec![0; 32];
let bytes_read = reader.read(&mut buffer).with_context(|| {
format!(
"Failed to read file for signature check: {}",
path.display()
)
})?;
if bytes_read == 0 {
return Ok(false); }
buffer.truncate(bytes_read);
Ok(self.has_binary_signature(&buffer))
}
fn detect_by_content_inspector(&self, path: &Path) -> Result<ContentType> {
let mut file = File::open(path).with_context(|| {
format!(
"Failed to open file for binary detection: {}",
path.display()
)
})?;
let mut buffer = vec![0; self.max_sample_size];
let bytes_read = file.read(&mut buffer).with_context(|| {
format!(
"Failed to read file for binary detection: {}",
path.display()
)
})?;
buffer.truncate(bytes_read);
Ok(inspect(&buffer))
}
fn is_binary_by_content_analysis(&self, path: &Path) -> Result<bool> {
let file = File::open(path).with_context(|| {
format!(
"Failed to open file for content analysis: {}",
path.display()
)
})?;
let mut reader = BufReader::new(file);
let mut buffer = vec![0; self.max_sample_size];
let bytes_read = reader.read(&mut buffer).with_context(|| {
format!(
"Failed to read file for content analysis: {}",
path.display()
)
})?;
if bytes_read == 0 {
return Ok(false); }
buffer.truncate(bytes_read);
if self.has_binary_signature(&buffer) {
return Ok(true);
}
if buffer.contains(&0) {
return Ok(true);
}
if let Ok(text) = std::str::from_utf8(&buffer) {
let non_printable_count = text
.chars()
.filter(|&c| c.is_control() && !matches!(c, '\t' | '\n' | '\r'))
.count();
let non_printable_ratio = non_printable_count as f64 / text.chars().count() as f64;
return Ok(non_printable_ratio > self.binary_threshold);
}
let non_printable_count = buffer
.iter()
.filter(|&&byte| !is_printable_ascii(byte) && !is_valid_utf8_start(byte))
.count();
let non_printable_ratio = non_printable_count as f64 / bytes_read as f64;
Ok(non_printable_ratio > self.binary_threshold)
}
fn has_binary_signature(&self, buffer: &[u8]) -> bool {
if buffer.len() < 2 {
return false;
}
if self.is_protocol_buffer(buffer) {
return true;
}
match &buffer[0..2] {
[0x78, 0x01] | [0x78, 0x9c] | [0x78, 0xda] => true,
[0x89, 0x50] if buffer.len() >= 8 && &buffer[0..8] == b"\x89PNG\r\n\x1a\n" => true,
[0xff, 0xd8] => true,
[0x25, 0x50] if buffer.len() >= 4 && &buffer[0..4] == b"%PDF" => true,
[0x50, 0x4b] => true,
[0x7f, 0x45] if buffer.len() >= 4 && &buffer[0..4] == b"\x7fELF" => true,
[0x4d, 0x5a] => true,
[0xfe, 0xed] | [0xfe, 0xec] | [0xce, 0xfa] | [0xcf, 0xfa] => true,
_ if buffer.len() >= 262 && &buffer[257..262] == b"ustar" => true,
[0x1f, 0x8b] => true,
[0x42, 0x5a] if buffer.len() >= 3 && buffer[2] == 0x68 => true,
[0xfd, 0x37] if buffer.len() >= 6 && &buffer[0..6] == b"\xfd7zXZ\x00" => true,
[0x37, 0x7a] if buffer.len() >= 6 && &buffer[0..6] == b"7z\xbc\xaf\x27\x1c" => true,
[0xca, 0xfe] => true,
[0x64, 0x65] if buffer.len() >= 8 && &buffer[0..8] == b"dex\n035\0" => true,
_ => false,
}
}
fn is_protocol_buffer(&self, buffer: &[u8]) -> bool {
if buffer.len() < 8 {
return false;
}
let ascii_count = buffer
.iter()
.take(64)
.filter(|&&b| b >= 0x20 && b <= 0x7E)
.count();
let ascii_ratio = ascii_count as f64 / buffer.len().min(64) as f64;
let is_valid_utf8 = std::str::from_utf8(buffer).is_ok();
if ascii_ratio > 0.7 || is_valid_utf8 {
return false; }
let mut valid_proto_sequences = 0;
let mut i = 0;
while i < buffer.len().min(32) {
let byte = buffer[i];
let wire_type = byte & 0x07;
let field_number = byte >> 3;
if matches!(wire_type, 0 | 1 | 2 | 5) && field_number > 0 && field_number < 32 {
match wire_type {
0 => {
if i + 1 < buffer.len() {
let next = buffer[i + 1];
if next < 0x80 || (next & 0x80 != 0 && next & 0x7F < 0x40) {
valid_proto_sequences += 1;
i += 2; continue;
}
}
}
2 => {
if i + 1 < buffer.len() {
let length = buffer[i + 1];
if length > 0
&& length < 64
&& (i + 2 + length as usize) <= buffer.len()
{
valid_proto_sequences += 2; i += 2 + length as usize; continue;
}
}
}
1 | 5 => {
valid_proto_sequences += 1;
i += if wire_type == 1 { 9 } else { 5 }; continue;
}
_ => {}
}
}
i += 1;
}
let has_null_bytes = buffer[..buffer.len().min(64)].contains(&0);
let non_ascii_count = buffer
.iter()
.take(64)
.filter(|&&b| b > 0x7E || (b < 0x20 && !matches!(b, 0x09 | 0x0A | 0x0D)))
.count();
let non_ascii_ratio = non_ascii_count as f64 / buffer.len().min(64) as f64;
valid_proto_sequences >= 2 && (has_null_bytes || non_ascii_ratio > 0.2)
}
pub fn is_text_file<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
Ok(!self.is_binary(path)?)
}
pub fn get_binary_reason<P: AsRef<Path>>(&self, path: P) -> Result<Option<String>> {
let path = path.as_ref();
if self.is_binary_by_extension(path) {
return Ok(Some("Binary file extension".to_string()));
}
if let Ok(content_type) = self.detect_by_content_inspector(path) {
match content_type {
ContentType::BINARY => {
return Ok(Some("Content inspection detected binary".to_string()))
}
_ => {}
}
}
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut buffer = vec![0; self.max_sample_size];
let bytes_read = reader.read(&mut buffer)?;
if bytes_read == 0 {
return Ok(None);
}
buffer.truncate(bytes_read);
if self.has_binary_signature(&buffer) {
if self.is_protocol_buffer(&buffer) {
return Ok(Some("Detected as Protocol Buffer binary data".to_string()));
}
let signature_desc = match &buffer[0..2.min(buffer.len())] {
[0x78, 0x01] | [0x78, 0x9c] | [0x78, 0xda] => "zlib compressed data",
[0x89, 0x50] => "PNG image",
[0xff, 0xd8] => "JPEG image",
[0x25, 0x50] => "PDF document",
[0x50, 0x4b] => "ZIP archive",
[0x7f, 0x45] => "ELF executable",
[0x4d, 0x5a] => "Windows executable",
[0xfe, 0xed] | [0xfe, 0xec] | [0xce, 0xfa] | [0xcf, 0xfa] => "Mach-O executable",
[0x1f, 0x8b] => "GZIP compressed",
[0x42, 0x5a] => "BZIP2 compressed",
_ => "binary file signature",
};
return Ok(Some(format!("Detected as {}", signature_desc)));
}
if buffer.contains(&0) {
return Ok(Some("Contains null bytes".to_string()));
}
let non_printable_count = buffer
.iter()
.filter(|&&byte| !is_printable_ascii(byte) && !is_valid_utf8_start(byte))
.count();
let non_printable_ratio = non_printable_count as f64 / bytes_read as f64;
if non_printable_ratio > self.binary_threshold {
return Ok(Some(format!(
"High ratio of non-printable characters: {:.1}%",
non_printable_ratio * 100.0
)));
}
Ok(None)
}
}
fn is_printable_ascii(byte: u8) -> bool {
matches!(byte, 0x20..=0x7E | 0x09 | 0x0A | 0x0D) }
fn is_valid_utf8_start(byte: u8) -> bool {
byte < 0x80 || (byte >= 0xC0 && byte < 0xF8)
}
const BINARY_EXTENSIONS: &[&str] = &[
"exe",
"dll",
"so",
"dylib",
"app",
"deb",
"rpm",
"msi",
"dmg",
"run",
"bin",
"com",
"bat",
"cmd",
"scr",
"sys",
"drv",
"cpl",
"ocx",
"axl",
"elf",
"zip",
"tar",
"gz",
"bz2",
"xz",
"7z",
"rar",
"cab",
"ace",
"arc",
"arj",
"lzh",
"lha",
"zoo",
"cpio",
"shar",
"lz",
"lzma",
"tgz",
"tbz",
"tbz2",
"txz",
"tlz",
"Z",
"deb",
"rpm",
"pkg",
"apk",
"snap",
"flatpak",
"appimage",
"pb",
"pbf",
"jpg",
"jpeg",
"png",
"gif",
"bmp",
"tiff",
"tif",
"webp",
"svg",
"ico",
"cur",
"psd",
"ai",
"eps",
"raw",
"cr2",
"nef",
"orf",
"sr2",
"dng",
"exr",
"hdr",
"tga",
"pcx",
"ppm",
"pgm",
"pbm",
"xbm",
"xpm",
"jp2",
"jpx",
"j2k",
"heic",
"heif",
"avif",
"jxl",
"wbmp",
"emf",
"wmf",
"cgm",
"pic",
"pict",
"mp4",
"avi",
"mkv",
"mov",
"wmv",
"flv",
"webm",
"m4v",
"3gp",
"3g2",
"mpg",
"mpeg",
"asf",
"rm",
"rmvb",
"vob",
"mts",
"m2ts",
"f4v",
"ogv",
"divx",
"xvid",
"mxf",
"amv",
"drc",
"gif",
"gifv",
"mng",
"qt",
"yuv",
"roq",
"svi",
"viv",
"vp8",
"vp9",
"av1",
"mp3",
"wav",
"flac",
"aac",
"ogg",
"m4a",
"wma",
"aiff",
"au",
"ra",
"opus",
"ape",
"ac3",
"dts",
"amr",
"awb",
"gsm",
"m4r",
"mpc",
"tta",
"wv",
"8svx",
"cda",
"mid",
"midi",
"rmi",
"kar",
"mod",
"s3m",
"xm",
"it",
"669",
"amf",
"ams",
"dbm",
"dmf",
"dsm",
"far",
"mdl",
"med",
"mtm",
"okt",
"ptm",
"stm",
"ult",
"umx",
"mt2",
"psm",
"pdf",
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"odt",
"ods",
"odp",
"rtf",
"pages",
"numbers",
"key",
"pub",
"vsd",
"vwsb",
"mpp",
"one",
"wps",
"wpd",
"sxw",
"sxc",
"sxi",
"sxd",
"stw",
"stc",
"sti",
"std",
"odf",
"otg",
"otp",
"ots",
"ott",
"odm",
"oth",
"odb",
"oxt",
"db",
"sqlite",
"sqlite3",
"mdb",
"accdb",
"dbf",
"fdb",
"gdb",
"nsf",
"odb",
"pdb",
"wdb",
"bak",
"dmp",
"frm",
"myd",
"myi",
"ibd",
"fpt",
"cdx",
"idx",
"ndx",
"mdx",
"fox",
"prg",
"fxp",
"dbc",
"dct",
"o",
"obj",
"lib",
"a",
"pdb",
"ilk",
"exp",
"map",
"res",
"rc",
"tlb",
"manifest",
"cache",
"tmp",
"temp",
"bak",
"swp",
"swo",
"orig",
"rej",
"class",
"jar",
"war",
"ear",
"jmod",
"jimage",
"hprof",
"heap",
"jfr",
"pdb",
"mdb",
"nupkg",
"snupkg",
"vsix",
"msi",
"msm",
"msp",
"mst",
"wixlib",
"wixobj",
"wixpdb",
"cab",
"msu",
"exe",
"dll",
"winmd",
"avro",
"parquet",
"orc",
"arrow",
"cbor",
"msgpack",
"bson",
"pickle",
"pkl",
"p",
"joblib",
"npy",
"npz",
"iso",
"img",
"vdi",
"vmdk",
"qcow2",
"vhd",
"vhdx",
"hdd",
"parallels",
"pvm",
"ova",
"ovf",
"box",
"vagrant",
"docker",
"tar",
"tgz",
"pak",
"wad",
"vpk",
"gcf",
"ncf",
"xnb",
"unity3d",
"unitypackage",
"fbx",
"dae",
"obj",
"3ds",
"max",
"blend",
"ma",
"mb",
"c4d",
"lwo",
"lws",
"mesh",
"x3d",
"ply",
"stl",
"off",
"ase",
"mdl",
"md2",
"md3",
"dwg",
"dxf",
"step",
"stp",
"iges",
"igs",
"catpart",
"catproduct",
"prt",
"asm",
"par",
"psm",
"x_t",
"x_b",
"sat",
"3dm",
"rvt",
"rfa",
"ifc",
"skp",
"kmz",
"kml",
"hdf5",
"h5",
"nc",
"cdf",
"fits",
"fts",
"mat",
"sav",
"dta",
"por",
"sas7bdat",
"xpt",
"rda",
"rds",
"feather",
"arrow",
"zarr",
"blosc",
"ttf",
"otf",
"woff",
"woff2",
"eot",
"fon",
"fnt",
"pfb",
"pfm",
"afm",
"bdf",
"pcf",
"snf",
"gsf",
"t1",
"t42",
"cff",
"cid",
"dfont",
"suit",
"dat",
"blk",
"rev",
"wallet",
"keystore",
"p12",
"pfx",
"jks",
"bks",
"apk",
"ipa",
"xap",
"appx",
"msix",
"aab",
"dex",
"odex",
"vdex",
"art",
"oat",
"hex",
"bin",
"elf",
"axf",
"out",
"uf2",
"hex",
"bin",
"dat",
"dump",
"raw",
"rom",
"firmware",
"bios",
"efi",
"uefi",
"swap",
"hiberfil",
"pagefile",
"core",
"crashdump",
"mdmp",
"dmp",
"lock",
"pid",
"sock",
"fifo",
"pipe",
"device",
"special",
];
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::TempDir;
#[test]
fn test_binary_extension_detection() {
let detector = BinaryDetector::default();
assert!(detector.is_binary_by_extension(Path::new("test.exe")));
assert!(detector.is_binary_by_extension(Path::new("test.jpg")));
assert!(detector.is_binary_by_extension(Path::new("test.pdf")));
assert!(detector.is_binary_by_extension(Path::new("TEST.EXE")));
assert!(!detector.is_binary_by_extension(Path::new("test.txt")));
assert!(!detector.is_binary_by_extension(Path::new("test.rs")));
assert!(!detector.is_binary_by_extension(Path::new("test.py")));
assert!(!detector.is_binary_by_extension(Path::new("Makefile")));
}
#[test]
fn test_text_file_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let text_file = temp_dir.path().join("test.txt");
let mut file = File::create(&text_file)?;
writeln!(file, "This is a text file with some content.")?;
writeln!(
file,
"It has multiple lines and should be detected as text."
)?;
assert!(detector.is_text_file(&text_file)?);
assert!(!detector.is_binary(&text_file)?);
Ok(())
}
#[test]
fn test_binary_file_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let binary_file = temp_dir.path().join("test.bin");
let mut file = File::create(&binary_file)?;
file.write_all(&[0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD])?;
assert!(!detector.is_text_file(&binary_file)?);
assert!(detector.is_binary(&binary_file)?);
Ok(())
}
#[test]
fn test_empty_file_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let empty_file = temp_dir.path().join("empty.txt");
File::create(&empty_file)?;
assert!(detector.is_text_file(&empty_file)?);
assert!(!detector.is_binary(&empty_file)?);
Ok(())
}
#[test]
fn test_utf8_file_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let utf8_file = temp_dir.path().join("unicode.txt");
let mut file = File::create(&utf8_file)?;
writeln!(file, "Hello, 世界! 🌍")?;
writeln!(
file,
"This file contains émojis and unicode characters: 日本語"
)?;
drop(file);
assert!(detector.is_text_file(&utf8_file)?);
assert!(!detector.is_binary(&utf8_file)?);
Ok(())
}
#[test]
fn test_binary_reason() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let exe_file = temp_dir.path().join("test.exe");
File::create(&exe_file)?;
let reason = detector.get_binary_reason(&exe_file)?;
assert!(reason.is_some());
assert!(reason.unwrap().contains("extension"));
let binary_file = temp_dir.path().join("test.bin"); let mut file = File::create(&binary_file)?;
file.write_all(b"some text\x00more text")?;
drop(file); let reason = detector.get_binary_reason(&binary_file)?;
assert!(reason.is_some());
let reason_str = reason.unwrap();
assert!(
reason_str.contains("extension")
|| reason_str.contains("null")
|| reason_str.contains("binary")
);
let text_file = temp_dir.path().join("test.txt");
let mut file = File::create(&text_file)?;
writeln!(file, "This is just text")?;
let reason = detector.get_binary_reason(&text_file)?;
assert!(reason.is_none());
Ok(())
}
#[test]
fn test_printable_ascii() {
assert!(is_printable_ascii(b' ')); assert!(is_printable_ascii(b'A')); assert!(is_printable_ascii(b'0')); assert!(is_printable_ascii(b'~')); assert!(is_printable_ascii(b'\t')); assert!(is_printable_ascii(b'\n')); assert!(is_printable_ascii(b'\r'));
assert!(!is_printable_ascii(0x00)); assert!(!is_printable_ascii(0x01)); assert!(!is_printable_ascii(0x7F)); assert!(!is_printable_ascii(0x80)); assert!(!is_printable_ascii(0xFF)); }
#[test]
fn test_utf8_start_detection() {
assert!(is_valid_utf8_start(0x41)); assert!(is_valid_utf8_start(0xC2)); assert!(is_valid_utf8_start(0xE0)); assert!(is_valid_utf8_start(0xF0));
assert!(!is_valid_utf8_start(0x80)); assert!(!is_valid_utf8_start(0xBF)); assert!(!is_valid_utf8_start(0xF8)); assert!(!is_valid_utf8_start(0xFF)); }
#[test]
fn test_protocol_buffer_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let protobuf_data = vec![
0x08, 0x96, 0x01, 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x03, 0x66, 0x6f, 0x6f, 0x20, 0x42, ];
let protobuf_file = temp_dir.path().join("test.pb");
let mut file = File::create(&protobuf_file)?;
file.write_all(&protobuf_data)?;
drop(file);
assert!(detector.is_binary(&protobuf_file)?);
assert!(!detector.is_text_file(&protobuf_file)?);
let reason = detector.get_binary_reason(&protobuf_file)?;
assert!(reason.is_some());
let reason_str = reason.unwrap();
assert!(
reason_str.contains("Protocol Buffer")
|| reason_str.contains("binary")
|| reason_str.contains("extension")
);
Ok(())
}
#[test]
fn test_protocol_buffer_patterns() {
let detector = BinaryDetector::default();
let protobuf_patterns = vec![
0x08, 0x96, 0x01, 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, 0x20, 0x42,
];
assert!(detector.is_protocol_buffer(&protobuf_patterns));
let text_data = b"This is plain text content";
assert!(!detector.is_protocol_buffer(text_data));
assert!(!detector.is_protocol_buffer(&[]));
assert!(!detector.is_protocol_buffer(&[0x01]));
}
#[test]
fn test_protobuf_vs_text_distinction() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let pseudo_protobuf_text = temp_dir.path().join("pseudo.txt");
let mut file = File::create(&pseudo_protobuf_text)?;
writeln!(
file,
"This file contains some bytes like \\x08 and \\x12 but is text"
)?;
drop(file);
assert!(detector.is_text_file(&pseudo_protobuf_text)?);
assert!(!detector.is_binary(&pseudo_protobuf_text)?);
let real_protobuf = temp_dir.path().join("real.pb");
let protobuf_data = vec![
0x08, 0x96, 0x01, 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x2a, 0x2a, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, ];
let mut file = File::create(&real_protobuf)?;
file.write_all(&protobuf_data)?;
drop(file);
assert!(detector.is_binary(&real_protobuf)?);
assert!(!detector.is_text_file(&real_protobuf)?);
Ok(())
}
#[test]
fn test_pb_file_detection() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let pb_file = temp_dir.path().join("test.pb");
let mut file = File::create(&pb_file)?;
file.write_all(b"some protobuf binary data")?;
drop(file);
assert!(
detector.is_binary(&pb_file)?,
".pb file should be detected as binary"
);
assert!(
!detector.is_text_file(&pb_file)?,
".pb file should NOT be detected as text"
);
let reason = detector.get_binary_reason(&pb_file)?;
assert!(reason.is_some(), ".pb file should have a binary reason");
assert!(
reason.unwrap().contains("extension"),
"Reason should mention extension"
);
Ok(())
}
#[test]
fn test_discovery_phase_simulation() -> Result<()> {
let temp_dir = TempDir::new()?;
let detector = BinaryDetector::default();
let pb_file = temp_dir.path().join("test.pb");
let txt_file = temp_dir.path().join("test.txt");
let rs_file = temp_dir.path().join("test.rs");
let mut file = File::create(&pb_file)?;
file.write_all(b"oldstring should not be processed in binary files")?;
drop(file);
let mut file = File::create(&txt_file)?;
file.write_all(b"This contains oldstring and should be processed")?;
drop(file);
let mut file = File::create(&rs_file)?;
file.write_all(b"fn main() { println!(\"oldstring\"); }")?;
drop(file);
let search_string = "oldstring";
let pb_is_text = detector.is_text_file(&pb_file)?;
crate::outln!("pb_file is_text_file: {}", pb_is_text);
if pb_is_text {
let contains_string = std::fs::read_to_string(&pb_file)?.contains(search_string);
crate::outln!(
"ERROR: .pb file classified as text and contains search string: {}",
contains_string
);
assert!(false, ".pb file should not be classified as text!");
} else {
crate::outln!(
"OK: .pb file correctly classified as binary, will not be processed for content"
);
}
assert!(detector.is_text_file(&txt_file)?, "txt file should be text");
assert!(detector.is_text_file(&rs_file)?, "rs file should be text");
Ok(())
}
}