use crate::checkers::CheckerTypes;
use crate::decoders::interface::check_string_success;
use base64::{engine::general_purpose, Engine as _};
use super::crack_results::CrackResult;
use super::interface::Crack;
use super::interface::Decoder;
use log::{debug, info, trace};
pub struct Base64Decoder;
impl Crack for Decoder<Base64Decoder> {
fn new() -> Decoder<Base64Decoder> {
Decoder {
name: "Base64",
description: "Base64 is a group of binary-to-text encoding schemes that represent binary data in ASCII string format. Supports both standard Base64 (with +/) and URL-safe Base64 (with -_) variants.",
link: "https://en.wikipedia.org/wiki/Base64",
tags: vec!["base64", "base64_url", "url", "decoder", "base"],
popularity: 1.0,
phantom: std::marker::PhantomData,
}
}
fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
trace!("Trying Base64 with text {:?}", text);
let mut results = CrackResult::new(self, text.to_string());
let uses_standard_chars = text.contains('+') || text.contains('=') || text.contains('/');
let decoded_text = if uses_standard_chars {
debug!("Using standard Base64 decoder");
decode_base64_no_error_handling(text)
} else {
debug!("Using URL-safe Base64 decoder");
decode_base64_url_no_error_handling(text)
};
if decoded_text.is_none() {
debug!("Base64 decode failed");
return results;
}
let decoded_text = decoded_text.unwrap();
if !check_string_success(&decoded_text, text) {
info!(
"Failed to decode base64 because check_string_success returned false on string {}",
decoded_text
);
return results;
}
let checker_result = checker.check(&decoded_text);
results.unencrypted_text = Some(vec![decoded_text]);
results.update_checker(&checker_result);
results
}
fn get_tags(&self) -> &Vec<&str> {
&self.tags
}
fn get_name(&self) -> &str {
self.name
}
}
fn decode_base64_no_error_handling(text: &str) -> Option<String> {
let text = text.replace('=', "");
general_purpose::STANDARD_NO_PAD
.decode(text.as_bytes())
.ok()
.map(|inner| String::from_utf8(inner).ok())?
}
fn decode_base64_url_no_error_handling(text: &str) -> Option<String> {
let text = text.replace('=', "");
general_purpose::URL_SAFE_NO_PAD
.decode(text.as_bytes())
.ok()
.map(|inner| String::from_utf8(inner).ok())?
}
#[cfg(test)]
mod tests {
use super::Base64Decoder;
use crate::{
checkers::{
athena::Athena,
checker_type::{Check, Checker},
CheckerTypes,
},
decoders::interface::{Crack, Decoder},
};
fn get_athena_checker() -> CheckerTypes {
let athena_checker = Checker::<Athena>::new();
CheckerTypes::CheckAthena(athena_checker)
}
#[test]
fn successful_standard_decoding() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let result = base64_decoder.crack("aGVsbG8gd29ybGQ=", &get_athena_checker());
let decoded_str = &result
.unencrypted_text
.expect("No unencrypted text for base64");
assert_eq!(decoded_str[0], "hello world");
}
#[test]
fn successful_url_safe_decoding() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let test_cases = vec![
("SGVsbG8tV29ybGQ", "Hello-World"),
("SGVsbG9fV29ybGQ", "Hello_World"),
(
"aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8_ZXhhbXBsZT10ZXN0",
"https://www.google.com/?example=test",
),
];
for (input, expected) in test_cases {
let result = base64_decoder.crack(input, &get_athena_checker());
let decoded_str = &result
.unencrypted_text
.unwrap_or_else(|| panic!("Failed to decode URL-safe string: {}", input));
assert_eq!(decoded_str[0], expected);
}
}
#[test]
fn base64_decode_empty_string() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let result = base64_decoder
.crack("", &get_athena_checker())
.unencrypted_text;
assert!(result.is_none());
}
#[test]
fn base64_decode_handles_panics() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let result = base64_decoder
.crack(
"hello my name is panicky mc panic face!",
&get_athena_checker(),
)
.unencrypted_text;
assert!(
result.is_none(),
"Decode_base64 should return None for invalid input"
);
}
#[test]
fn base64_handle_panic_if_emoji() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let result = base64_decoder
.crack("😂", &get_athena_checker())
.unencrypted_text;
assert!(
result.is_none(),
"Decode_base64 should return None for emoji input"
);
}
#[test]
fn base64_decode_triple() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let input =
"VVRKc2QyRkhWalZKUjJ4NlNVaE9ka2xIV21oak0xRnpTVWhTYjJGWVRXZGhXRTFuV1ROS2FHVnVhMmc9";
let mut current = input.to_string();
for _ in 0..3 {
let result = base64_decoder.crack(¤t, &get_athena_checker());
assert!(
result.unencrypted_text.is_some(),
"Failed to decode base64 layer"
);
current = result.unencrypted_text.unwrap()[0].clone();
assert!(!current.is_empty(), "Decoded to empty string");
assert!(current.is_ascii(), "Decoded to non-ASCII content");
}
assert!(!current.trim().is_empty(), "Final decoded text is empty");
println!("Final decoded text: {:?}", current);
}
#[test]
fn test_mixed_encoding_variants() {
let base64_decoder = Decoder::<Base64Decoder>::new();
let checker = get_athena_checker();
let standard_result = base64_decoder.crack("SGVsbG8+Pz8/Cg==", &checker);
assert!(
standard_result.unencrypted_text.is_some(),
"Failed to decode standard Base64"
);
let url_safe_result = base64_decoder.crack("SGVsbG8-Pz8_Cg", &checker);
assert!(
url_safe_result.unencrypted_text.is_some(),
"Failed to decode URL-safe Base64"
);
}
}