happy_cracking/crypto/
autodecode.rs1use anyhow::Result;
2use clap::Subcommand;
3
4use super::{base32, base58, base64, binary, hex, morse, url};
5
6#[derive(Subcommand)]
7pub enum AutoDecodeAction {
8 #[command(about = "Automatically detect and decode")]
9 Decode {
10 #[arg(help = "Input to decode")]
11 input: String,
12 #[arg(short, long, help = "Recursively decode", default_value = "false")]
13 recursive: bool,
14 #[arg(short, long, help = "Maximum recursion depth", default_value = "5")]
15 max_depth: usize,
16 },
17}
18
19pub fn run(action: AutoDecodeAction) -> Result<()> {
20 match action {
21 AutoDecodeAction::Decode {
22 input,
23 recursive,
24 max_depth,
25 } => {
26 if recursive {
27 decode_recursive(&input, 0, max_depth);
28 } else {
29 let results = detect_and_decode(&input);
30 if results.is_empty() {
31 println!("No encoding detected");
32 } else {
33 for (encoding, decoded) in results {
34 println!("[{}] {}", encoding, decoded);
35 }
36 }
37 }
38 }
39 }
40 Ok(())
41}
42
43pub fn detect_and_decode(input: &str) -> Vec<(&'static str, String)> {
44 let input = input.trim();
45 let mut results = Vec::new();
46
47 if looks_like_base64(input)
49 && let Ok(decoded) = base64::decode(input)
50 && is_printable(&decoded)
51 {
52 results.push(("Base64", decoded));
53 }
54
55 if looks_like_base32(input)
57 && let Ok(decoded) = base32::decode(input)
58 && is_printable(&decoded)
59 {
60 results.push(("Base32", decoded));
61 }
62
63 if looks_like_base58(input)
65 && let Ok(decoded) = base58::decode(input)
66 && is_printable(&decoded)
67 {
68 results.push(("Base58", decoded));
69 }
70
71 if looks_like_hex(input)
73 && let Ok(decoded) = hex::decode(input)
74 && is_printable(&decoded)
75 {
76 results.push(("Hex", decoded));
77 }
78
79 if input.contains('%')
81 && let Ok(decoded) = url::decode(input)
82 && decoded != input
83 {
84 results.push(("URL", decoded));
85 }
86
87 if looks_like_binary(input)
89 && let Ok(decoded) = binary::decode(input)
90 && is_printable(&decoded)
91 {
92 results.push(("Binary", decoded));
93 }
94
95 if looks_like_morse(input)
97 && let Ok(decoded) = morse::decode(input)
98 {
99 results.push(("Morse", decoded));
100 }
101
102 results
103}
104
105fn decode_recursive(input: &str, depth: usize, max_depth: usize) {
106 let indent = " ".repeat(depth);
107
108 if depth >= max_depth {
109 println!("{}[Max depth reached]", indent);
110 return;
111 }
112
113 let results = detect_and_decode(input);
114
115 if results.is_empty() {
116 if depth == 0 {
117 println!("No encoding detected");
118 }
119 return;
120 }
121
122 for (encoding, decoded) in results {
123 println!("{}[{}] {}", indent, encoding, decoded);
124
125 if decoded != input && !decoded.is_empty() {
127 decode_recursive(&decoded, depth + 1, max_depth);
128 }
129 }
130}
131
132fn is_printable(s: &str) -> bool {
133 s.chars()
134 .all(|c| !c.is_control() || c == '\n' || c == '\t' || c == '\r')
135}
136
137fn looks_like_base64(s: &str) -> bool {
138 if s.len() < 2 {
139 return false;
140 }
141 let valid_chars = s
142 .chars()
143 .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=');
144 valid_chars && (s.len().is_multiple_of(4) || s.ends_with('='))
145}
146
147fn looks_like_base32(s: &str) -> bool {
148 if s.len() < 2 {
149 return false;
150 }
151 let valid_chars = s
152 .chars()
153 .all(|c| c.is_ascii_alphabetic() || matches!(c, '2'..='7' | '='));
154 valid_chars && s.len().is_multiple_of(8)
155}
156
157fn looks_like_base58(s: &str) -> bool {
158 if s.len() < 2 {
159 return false;
160 }
161 s.chars()
163 .all(|c| c.is_ascii_alphanumeric() && !matches!(c, '0' | 'O' | 'I' | 'l'))
164}
165
166fn looks_like_hex(s: &str) -> bool {
167 s.len() >= 2 && s.len().is_multiple_of(2) && s.chars().all(|c| c.is_ascii_hexdigit())
168}
169
170fn looks_like_binary(s: &str) -> bool {
171 let non_ws_count = s.chars().filter(|c| !c.is_whitespace()).count();
172 non_ws_count >= 8 && s.chars().all(|c| c == '0' || c == '1' || c.is_whitespace())
173}
174
175fn looks_like_morse(s: &str) -> bool {
176 s.len() >= 2
177 && s.chars()
178 .all(|c| c == '.' || c == '-' || c == ' ' || c == '/')
179}