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 Base64URLDecoder;
impl Crack for Decoder<Base64URLDecoder> {
fn new() -> Decoder<Base64URLDecoder> {
Decoder {
name: "Base64 URL",
description: "Modified Base64 for URL variants exist (such as base64url in RFC 4648), where the '+' and '/' characters of standard Base64 are respectively replaced by '-' and '_', so that using URL encoders/decoders is no longer necessary.",
link: "https://en.wikipedia.org/wiki/Base64#URL_applications",
tags: vec!["base64_url", "base64", "url", "decoder", "base"],
popularity: 0.9,
phantom: std::marker::PhantomData,
}
}
fn crack(&self, text: &str, checker: &CheckerTypes) -> CrackResult {
trace!("Trying base64_url with text {:?}", text);
let mut results = CrackResult::new(self, text.to_string());
if (!text.contains('-') && !text.contains('_')) || text.contains('+') || text.contains('/') {
debug!("Base64 URL decoder skipping string (no URL-safe chars or has standard Base64 chars)");
return results;
}
let decoded_text = decode_base64_url_no_error_handling(text);
if decoded_text.is_none() {
debug!("Failed to decode base64_url because Base64URLDecoder::decode_base64_url_no_error_handling returned None");
return results;
}
let decoded_text = decoded_text.unwrap();
if !check_string_success(&decoded_text, text) {
info!(
"Failed to decode base64_url 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_url_no_error_handling(text: &str) -> Option<String> {
let text = text.replace('=', "");
general_purpose::URL_SAFE_NO_PAD
.decode(text.as_bytes()) .ok() .and_then(|bytes| String::from_utf8(bytes).ok()) }
#[cfg(test)]
mod tests {
use super::Base64URLDecoder;
use super::super::base64_decoder::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 base64_url_handles_only_url_safe() {
let base64_url_decoder = Decoder::<Base64URLDecoder>::new();
let base64_decoder = Decoder::<Base64Decoder>::new();
let checker = get_athena_checker();
let url_safe_cases = vec![
"SGVsbG8tV29ybGQ",
"SGVsbG9fV29ybGQ",
"SGVsbG8tV29ybGRfMjAyNA"
];
for input in url_safe_cases {
let url_result = base64_url_decoder.crack(input, &checker);
assert!(url_result.unencrypted_text.is_some(), "Base64URL failed to decode URL-safe string: {}", input);
let std_result = base64_decoder.crack(input, &checker);
assert!(std_result.unencrypted_text.is_none(), "Standard Base64 should not decode URL-safe string: {}", input);
}
let reject_cases = vec![
"SGVsbG8gV29ybGQ",
"SGVsbG8rV29ybGQ",
"SGVsbG8vV29ybGQ",
"SGVsbG8rV29ybGQv",
"",
"😂",
"hello world"
];
for input in reject_cases {
let url_result = base64_url_decoder.crack(input, &checker);
assert!(url_result.unencrypted_text.is_none(), "Base64URL should reject non-URL-safe string: {}", input);
}
}
}