1use clap::{arg, command, ArgMatches};
2use std::process;
3use std::{env, path::PathBuf};
4
5use ansi_term::Colour;
6mod utils;
7
8use utils::{file, search};
9
10use crate::utils::{print_hexdump_output, PatternType};
11
12pub fn setup_args() -> ArgMatches {
13 let integer_validator = |value: &str| match value.parse::<usize>() {
14 Ok(_) => Ok(()),
15 Err(_) => Err(String::from("the value needs to be a valid integer")),
16 };
17
18 command!()
19 .arg(
20 arg!(<PATTERN> "Ascii strings should be passed inside quotes like so '\"This is a string\"'
21Escaping quotes '\"This is a \\\"quoted string\\\"\"'
22All of these byte sequence are valid: f9b4ca, F9B4CA and f9B4Ca"
23 ),
24 )
25 .arg(arg!(<FILE> ... "The filepath"))
26 .arg(
27 arg!(-f <filetype> ... "Filter the search by the file extensions.
28Examples of input: jpg, mp3, exe")
29 .required(false),
30 )
31 .arg(
32 arg!(-c <context_bytes_size> "Defines the number of bytes that will be printed in each line.")
33 .required(false)
34 .default_value("16")
35 .validator(integer_validator),
36 )
37 .arg(
38 arg!(-p --"print-only" "Prints only the filename that contais the match.")
39 .id("print_only")
40 .requires("PATTERN"),
41 )
42 .arg(
43 arg!(-o --"print-offset" "Prints only the offsets of the match.")
44 .id("print_offset")
45 .requires("PATTERN"),
46 )
47 .arg(
48 arg!(-s --"skip-bytes" <n> "Skip n bytes before searching.")
49 .id("skip_bytes")
50 .required(false)
51 .default_value("0")
52 .validator(integer_validator),
53 )
54 .arg_required_else_help(true)
55 .get_matches()
56}
57
58pub fn run(args: ArgMatches) {
59 let filetypes: Vec<&str> = args.values_of("filetype").unwrap_or_default().collect();
60
61 let filepaths: Vec<PathBuf> = args.values_of_t("FILE").unwrap();
62 let files: Vec<PathBuf> = if filetypes.is_empty() {
63 file::get_all_files_from_paths(filepaths)
64 } else {
65 file::filter_filetypes(file::get_all_files_from_paths(filepaths), &filetypes)
66 };
67
68 let pattern: Vec<u8> = match PatternType::from(args.value_of("PATTERN").unwrap()) {
69 PatternType::Str(pattern) => pattern.into_bytes(),
70
71 PatternType::HexStr(pattern) => hex::decode(pattern).unwrap_or_else(|error| {
72 eprintln!("Error: {} in byte sequence!", error);
73 process::exit(1);
74 }),
75 };
76
77 let context_bytes_size: usize = args.value_of("context_bytes_size").unwrap().parse().unwrap();
78 let skip_bytes: u64 = args.value_of("skip_bytes").unwrap().parse().unwrap();
79
80 for filename in files {
81 let mut searcher = search::Searcher::new(&pattern, context_bytes_size, skip_bytes);
82 let filename = filename.to_str().unwrap();
83
84 let result = searcher.search_in_file(filename).unwrap_or_else(|error| {
85 eprintln!("{}: {}", filename, error);
86 process::exit(1);
87 });
88
89 if !result.is_empty() {
90 println!("{}", Colour::Purple.paint(filename));
91 }
92
93 if args.is_present("print_offset") {
94 result
95 .iter()
96 .map(|_match| _match.offset)
97 .for_each(|offset| println!("0x{:08X}", offset));
98 return;
99 }
100
101 if !args.is_present("print_only") {
102 print_hexdump_output(&result, context_bytes_size);
103 }
104 }
105}