//! strings -- find printable ASCII strings in binary data use std::ffi::OsString; use std::fs::File; use std::io::{self, Read, Write}; const READ_BUFFER_BYTES: usize = 8 * 1024; const MAX_MIN_LENGTH: usize = 1024 * 1024; pub fn main(args: Vec) -> i32 { let str_args: Vec = args .iter() .skip(1) .map(|a| a.to_string_lossy().to_string()) .collect(); let mut min_len: usize = 4; let mut offset_format: Option = None; // 'd', 'o', or 'x' let mut filenames: Vec = Vec::new(); let mut i = 0; while i < str_args.len() { match str_args[i].as_str() { "-n" => { i += 1; if i >= str_args.len() { eprintln!("strings: option '-n' requires an argument"); return 1; } match str_args[i].parse::() { Ok(n) if (1..=MAX_MIN_LENGTH).contains(&n) => min_len = n, _ => { eprintln!("strings: invalid minimum string length '{}'", str_args[i]); return 1; } } } s if s.starts_with("-n") => { let val = &s[2..]; match val.parse::() { Ok(n) if (1..=MAX_MIN_LENGTH).contains(&n) => min_len = n, _ => { eprintln!("strings: invalid minimum string length '{}'", val); return 1; } } } "-t" => { i += 1; if i >= str_args.len() { eprintln!("strings: option '-t' requires an argument"); return 1; } match str_args[i].as_str() { "d" | "o" | "x" => offset_format = Some(str_args[i].chars().next().unwrap()), _ => { eprintln!("strings: invalid radix for -t: '{}'", str_args[i]); return 1; } } } s if s.starts_with('-') && s.len() > 1 => { // Try parsing as -N (numeric min length, GNU extension) if let Ok(n) = s[1..].parse::() { if (1..=MAX_MIN_LENGTH).contains(&n) { min_len = n; } else { eprintln!("strings: invalid minimum string length '{}'", &s[1..]); return 1; } } else { eprintln!("strings: unknown option '{}'", s); return 1; } } _ => filenames.push(str_args[i].clone()), } i += 1; } let stdout = io::stdout(); let mut out = stdout.lock(); if filenames.is_empty() { if let Err(e) = extract_strings(io::stdin().lock(), min_len, offset_format, &mut out) { eprintln!("strings: stdin: {}", e); return 1; } } else { for filename in &filenames { match File::open(filename) .and_then(|f| extract_strings(f, min_len, offset_format, &mut out)) { Ok(()) => {} Err(e) => { eprintln!("strings: {}: {}", filename, e); return 1; } }; } } match out.flush() { Ok(()) => 0, Err(e) => { eprintln!("strings: stdout: {}", e); 1 } } } fn extract_strings( mut reader: R, min_len: usize, offset_fmt: Option, out: &mut W, ) -> io::Result<()> { let mut run_start: Option = None; let mut run = Vec::new(); let mut emitted = false; let mut offset = 0; let mut buffer = [0; READ_BUFFER_BYTES]; loop { let bytes_read = reader.read(&mut buffer)?; if bytes_read == 0 { break; } for &b in &buffer[..bytes_read] { if is_printable_ascii(b) { if run_start.is_none() { run_start = Some(offset); } if emitted { out.write_all(&[b])?; } else { run.push(b); if run.len() == min_len { emit_prefix(out, run_start.unwrap_or(0), offset_fmt)?; out.write_all(&run)?; run.clear(); emitted = true; } } } else { if emitted { writeln!(out)?; } run.clear(); run_start = None; emitted = false; } offset += 1; } } if emitted { writeln!(out)?; } Ok(()) } fn emit_prefix(out: &mut W, offset: usize, offset_fmt: Option) -> io::Result<()> { if let Some(fmt) = offset_fmt { match fmt { 'd' => { write!(out, "{:7} ", offset)?; } 'o' => { write!(out, "{:7o} ", offset)?; } 'x' => { write!(out, "{:7x} ", offset)?; } _ => {} } } Ok(()) } fn is_printable_ascii(b: u8) -> bool { // Printable ASCII: space (0x20) through tilde (0x7E), plus tab (0x09) b == b'\t' || (b >= 0x20 && b <= 0x7E) }