From 5e51b706d54a26470f33d1342f4666d5aab921fc Mon Sep 17 00:00:00 2001 From: Andreas Grois Date: Sun, 9 Oct 2022 00:06:35 +0200 Subject: Initial Commit: PasswordMaker itself. It's compiling, and the public interface is semi-OK now. The internals are still a bit gory, but they'll likely see an iteartion later on anyhow. --- src/passwordmaker/leet.rs | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/passwordmaker/leet.rs (limited to 'src/passwordmaker/leet.rs') diff --git a/src/passwordmaker/leet.rs b/src/passwordmaker/leet.rs new file mode 100644 index 0000000..858b2ad --- /dev/null +++ b/src/passwordmaker/leet.rs @@ -0,0 +1,100 @@ +use crate::LeetLevel; + +pub(crate) struct LeetReplacementTable{ + lookup_table : &'static [&'static str; 26], +} + +enum CharOrSlice{ + Char(char), + Slice(&'static str) +} + +impl LeetReplacementTable { + /// Gets the appropriate leet replacement table for a given leet level. + pub(crate) fn get(leet_level : &LeetLevel) -> LeetReplacementTable { + let lookup_table = match leet_level { + LeetLevel::One => &["4", "b", "c", "d", "3", "f", "g", "h", "i", "j", "k", "1", "m", "n", "0", "p", "9", "r", "s", "7", "u", "v", "w", "x", "y", "z"], + LeetLevel::Two => &["4", "b", "c", "d", "3", "f", "g", "h", "1", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "y", "2"], + LeetLevel::Three => &["4", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"], + LeetLevel::Four => &["@", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"], + LeetLevel::Five => &["@", "|3", "c", "d", "3", "f", "6", "#", "!", "7", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"], + LeetLevel::Six => &["@", "|3", "c", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"], + LeetLevel::Seven => &["@", "|3", "[", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "^^", "^/", "0", "|*", "9", "|2", "5", "7", "(_)", "\\/", "\\/\\/", "><", "'/", "2"], + LeetLevel::Eight => &["@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|(", "1", "|\\/|", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"], + LeetLevel::Nine => &["@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|{", "|_", "/\\/\\", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"], + }; + LeetReplacementTable { lookup_table } + } + + /// Applies this replacement table to an input string slice. + /// Needs an intermediate allocation. + pub(super) fn leetify(&self, input: &str) -> String{ + //PasswordMaker Pro is converting input to lower-case before leet is applied. + //We must apply to_lowercase on the whole input. PasswordMaker Pro is properly treating Final_Sigma, what we cannot do if we just + //iterate on a per-char basis. + input.to_lowercase().chars() + .map(|c| self.conditionally_replace(c)) + .fold(String::with_capacity(input.len()), |mut result, c| { + match c { + CharOrSlice::Char(c) => result.push(c), + CharOrSlice::Slice(s) => result.push_str(s), + }; + result + }) + } + + fn conditionally_replace(&self, character : char) -> CharOrSlice { + match (character as usize).checked_sub(0x61).and_then(|index| self.lookup_table.get(index)) { + Some(s) => CharOrSlice::Slice(s), + None => CharOrSlice::Char(character), + } + } +} + +#[cfg(test)] +mod leet_tests{ + use super::*; + use strum::IntoEnumIterator; + fn get_icelandic_test_string() -> &'static str { + "Kæmi ný Öxi hér, ykist þjófum nú bæði víl og ádrepa." //yes, I know, the upper case Ö is wrong, but it's there to test a property. + } + fn get_icelandic_test_result(level : LeetLevel) -> &'static str { + match level { + LeetLevel::One => "kæmi ný öxi hér, ykis7 þjófum nú bæði ví1 0g ádr3p4.", + LeetLevel::Two => "kæm1 ný öx1 hér, yk157 þjófum nú bæð1 ví1 0g ádr3p4.", + LeetLevel::Three => "kæm' ný öx' hér, '/k'57 þjófum nú 8æð' ví1 06 ádr3p4.", + LeetLevel::Four => "kæm' ný öx' hér, '/k'57 þjófum nú 8æð' ví1 06 ádr3p@.", + LeetLevel::Five => "|<æm! ný öx! #é|2, '/|@.", + LeetLevel::Six => "|<æm! ný öx! #é|2, '/|@.", + LeetLevel::Seven => "|<æ^^! ^/ý ö> "|(æ|\\/|! |\\|ý ö)(! |-|é|2, '/|(!$| þ_|ó|=|_||\\/| |\\|ú 8æð! \\/í1 ()6 á|)|2&|>@.", + LeetLevel::Nine => "|{æ/\\/\\! |\\|ý ö)(! |-|é|2, '/|{!$| þ_|ó|=|_|/\\/\\ |\\|ú 8æð! \\/í|_ ()6 á|)|2&|>@.", + } + } + + /// Runs a simple icelandic test sentence as found on the web through the leetifier for all levels. + #[test] + fn leet_test_icelandic(){ + for leet_level in LeetLevel::iter(){ + let result = LeetReplacementTable::get(&leet_level).leetify(get_icelandic_test_string()); + let expected = get_icelandic_test_result(leet_level); + assert_eq!(result, expected); + } + } + + fn get_greek_test_string() -> &'static str { + "ΕΤΥΜΟΛΟΓΙΚΌ ΛΕΞΙΚΌ ΤΗΣ ΕΛΛΗΝΙΚΉΣ ΓΛΏΣΣΑΣ" + } + fn get_greek_test_result(_level : LeetLevel) -> &'static str { + "ετυμολογικό λεξικό της ελληνικής γλώσσας" + } + + #[test] + fn leet_test_greek(){ + for leet_level in LeetLevel::iter(){ + let result = LeetReplacementTable::get(&leet_level).leetify(get_greek_test_string()); + let expected = get_greek_test_result(leet_level); + assert_eq!(result, expected); + } + } +} \ No newline at end of file -- cgit v1.2.3