185 lines · 5.5 KB · Rust
RawDownload
//! 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<OsString>) -> i32 {    let str_args: Vec<String> = args        .iter()        .skip(1)        .map(|a| a.to_string_lossy().to_string())        .collect();     let mut min_len: usize = 4;    let mut offset_format: Option<char> = None; // 'd', 'o', or 'x'    let mut filenames: Vec<String> = 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::<usize>() {                    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::<usize>() {                    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::<usize>() {                    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<R: Read, W: Write>(    mut reader: R,    min_len: usize,    offset_fmt: Option<char>,    out: &mut W,) -> io::Result<()> {    let mut run_start: Option<usize> = 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<W: Write>(out: &mut W, offset: usize, offset_fmt: Option<char>) -> 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)}