use crate::constants::{
ASCII_LOWER, EFFECTIVE_TLDS, HOMOGLYPHS, IDNA_FILTER_REGEX, KEYBOARD_LAYOUTS, VOWELS,
};
use std::collections::HashSet;
use std::fmt;
use idna::punycode;
use itertools::Itertools;
include!(concat!(env!("OUT_DIR"), "/data.rs"));
type Result<T> = std::result::Result<T, PermutationError>;
#[derive(Default, Debug)]
pub struct Domain<'a> {
pub fqdn: &'a str,
tld: String,
domain: String,
}
#[deprecated(
since = "0.1.0",
note = "Prone to be removed in the future, does not currently provide any context."
)]
#[derive(Copy, Clone, Debug)]
pub struct PermutationError;
impl fmt::Display for PermutationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "")
}
}
impl<'a> Domain<'a> {
pub fn new(fqdn: &'a str) -> Result<Domain<'a>> {
match EFFECTIVE_TLDS.parse_domain(fqdn) {
Ok(parsed_domain) => {
let parts = parsed_domain
.root()
.unwrap()
.split('.')
.collect::<Vec<&str>>();
let len = parts.len();
if len < 2 {
return Err(PermutationError);
}
let tld = parts[len - 1].to_string();
let domain = String::from(parts[len - 2]);
Ok(Domain { fqdn, tld, domain })
}
Err(_) => Err(PermutationError),
}
}
pub fn all(&self) -> impl Iterator<Item = String> + '_ {
self.addition()
.chain(self.bitsquatting())
.chain(self.hyphentation())
.chain(self.insertion())
.chain(self.omission())
.chain(self.repetition())
.chain(self.replacement())
.chain(self.subdomain())
.chain(self.transposition())
.chain(self.vowel_swap())
.chain(self.keyword())
.chain(self.tld())
.chain(self.homoglyph())
}
pub fn addition(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(
ASCII_LOWER
.iter()
.map(move |c| format!("{}{}.{}", self.domain, c.to_string(), self.tld)),
)
}
pub fn bitsquatting(&self) -> impl Iterator<Item = String> + '_ {
let permutations = self
.fqdn
.chars()
.flat_map(move |c| {
(0..8).filter_map(move |mask_index| {
let mask = 1 << mask_index;
let squatted_char: u8 = mask ^ (c as u8);
if (squatted_char >= 48 && squatted_char <= 57)
|| (squatted_char >= 97 && squatted_char <= 122)
|| squatted_char == 45
{
Some((1..self.fqdn.len()).map(move |idx| {
let mut permutation = self.fqdn.to_string();
permutation.insert(idx, squatted_char as char);
permutation
}))
} else {
None
}
})
})
.flatten();
Domain::filter_domains(permutations)
}
pub fn homoglyph(&self) -> impl Iterator<Item = String> + '_ {
let mut result_first_pass: HashSet<String> = HashSet::new();
let mut result_second_pass: HashSet<String> = HashSet::new();
let fqdn = self.fqdn.to_string().chars().collect::<Vec<char>>();
for ws in 1..self.fqdn.len() {
for i in 0..(self.fqdn.len() - ws) + 1 {
let win: String = fqdn[i..i + ws].iter().collect();
let mut j = 0;
while j < ws {
let c: char = win.chars().nth(j).unwrap();
if HOMOGLYPHS.contains_key(&c) {
for glyph in HOMOGLYPHS.get(&c) {
let _glyph = glyph.chars().collect::<Vec<char>>();
for g in _glyph {
let new_win = win.replace(c, &g.to_string());
result_first_pass.insert(format!(
"{}{}{}",
&self.fqdn[..i],
&new_win,
&self.fqdn[i + ws..]
));
}
}
}
j += 1;
}
}
}
for domain in result_first_pass.iter() {
let _domain = domain.chars().collect::<Vec<char>>();
for ws in 1..fqdn.len() {
for i in 0..(fqdn.len() - ws) + 1 {
let win: String = _domain[i..i + ws].iter().collect();
let mut j = 0;
while j < ws {
let c: char = win.chars().nth(j).unwrap();
if HOMOGLYPHS.contains_key(&c) {
for glyph in HOMOGLYPHS.get(&c) {
let _glyph = glyph.chars().collect::<Vec<char>>();
for g in _glyph {
let new_win = win.replace(c, &g.to_string());
result_second_pass.insert(format!(
"{}{}{}",
&self.fqdn[..i],
&new_win,
&self.fqdn[i + ws..]
));
}
}
}
j += 1;
}
}
}
}
Domain::filter_domains((&result_first_pass | &result_second_pass).into_iter())
}
pub fn hyphentation(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(self.fqdn.chars().skip(1).enumerate().map(move |(i, _)| {
let mut permutation = self.fqdn.to_string();
permutation.insert(i, '-');
permutation
}))
}
pub fn insertion(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(
self.fqdn
.chars()
.skip(1)
.take(self.fqdn.len() - 2)
.enumerate()
.flat_map(move |(i, c)| {
KEYBOARD_LAYOUTS.iter().filter_map(move |layout| {
layout
.get(&c)
.map(move |keyboard_chars| {
keyboard_chars.chars().map(move |keyboard_char| {
let mut permutation = self.fqdn.to_string();
permutation.insert(i, keyboard_char);
permutation
})
})
})
})
.flatten(),
)
}
pub fn omission(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(self.fqdn.chars().enumerate().map(move |(i, _)| {
let mut permutation = self.fqdn.to_string();
permutation.remove(i);
permutation
}))
}
pub fn repetition(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(self.fqdn.chars().enumerate().filter_map(move |(i, c)| {
if c.is_alphabetic() {
Some(format!("{}{}{}", &self.fqdn[..=i], c, &self.fqdn[i + 1..]))
} else {
None
}
}))
}
pub fn replacement(&self) -> impl Iterator<Item = String> + '_ {
Self::filter_domains(
self.fqdn
.chars()
.skip(1)
.take(self.fqdn.len() - 2)
.enumerate()
.flat_map(move |(i, c)| {
KEYBOARD_LAYOUTS.iter().filter_map(move |layout| {
layout.get(&c).map(move |keyboard_chars| {
keyboard_chars.chars().map(move |keyboard_char| {
format!(
"{}{}{}",
&self.fqdn[..i],
keyboard_char,
&self.fqdn[i + 1..]
)
})
})
})
})
.flatten(),
)
}
pub fn subdomain(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(
self.fqdn
.chars()
.take(self.fqdn.len() - 3)
.enumerate()
.tuple_windows()
.filter_map(move |((_, c1), (i2, c2))| {
if ['-', '.'].iter().all(|x| [c1, c2].contains(x)) {
None
} else {
Some(format!("{}.{}", &self.fqdn[..i2], &self.fqdn[i2..]))
}
}),
)
}
pub fn transposition(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(self.fqdn.chars().enumerate().tuple_windows().filter_map(
move |((i1, c1), (i2, c2))| {
if c1 == c2 {
None
} else {
Some(format!(
"{}{}{}{}",
&self.fqdn[..i1],
c2,
c1,
&self.fqdn[i2 + 1..]
))
}
},
))
}
pub fn vowel_swap(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(
self.fqdn
.chars()
.enumerate()
.filter_map(move |(i, c)| {
if VOWELS.contains(&c) {
Some(VOWELS.iter().filter_map(move |vowel| {
if *vowel == c {
None
} else {
Some(format!(
"{}{}{}",
&self.fqdn[..i],
vowel,
&self.fqdn[i + 1..]
))
}
}))
} else {
None
}
})
.flatten(),
)
}
pub fn keyword(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(KEYWORDS.iter().flat_map(move |keyword| {
vec![
format!("{}-{}.{}", &self.domain, keyword, &self.tld),
format!("{}{}.{}", &self.domain, keyword, &self.tld),
format!("{}-{}.{}", keyword, &self.domain, &self.tld),
format!("{}{}.{}", keyword, &self.domain, &self.tld),
]
}))
}
pub fn tld(&self) -> impl Iterator<Item = String> + '_ {
Domain::filter_domains(
TLDS.iter()
.map(move |tld| format!("{}.{}", &self.domain, tld)),
)
}
pub fn filter_domains<T>(permutations: T) -> impl Iterator<Item = String>
where
T: Iterator<Item = String>,
{
permutations
.filter(|x| is_valid_punycode(x) && IDNA_FILTER_REGEX.is_match(x).unwrap_or(false))
}
}
fn is_valid_punycode(x: &str) -> bool {
x.replace("xn--", "")
.split('.')
.all(|part| punycode::decode(part).is_some())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = d.all().collect();
assert!(permutations.len() > 0);
}
#[test]
fn test_addition_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.addition().collect());
assert_eq!(permutations.len(), ASCII_LOWER.len());
}
#[test]
fn test_bitsquatting_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.bitsquatting().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_homoglyph_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.homoglyph().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_hyphenation_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.hyphentation().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_insertion_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.insertion().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_omission_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.omission().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_repetition_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.repetition().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_replacement_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.replacement().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_subdomain_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.subdomain().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_transposition_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.transposition().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_vowel_swap_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.vowel_swap().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_keyword_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.keyword().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_tld_mode() {
let d = Domain::new("www.example.com").unwrap();
let permutations: Vec<_> = dbg!(d.tld().collect());
assert!(permutations.len() > 0);
}
#[test]
fn test_domain_idna_filtering() {
let valid_idns = vec![
String::from("i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd"),
String::from("4dbcagdahymbxekheh6e0a7fei0b"),
String::from("rpublique-numrique-bwbm"),
String::from("fiqs8s"),
String::from("google.com.acadmie-franaise-npb1a"),
String::from("acadmie-franaise-npb1a-google.com"),
String::from("acadmie-franaise-npb1a"),
String::from("google.com"),
String::from("phishdeck.com"),
String::from("xn--wgbl6a.icom.museum"),
String::from("xn--80aaxgrpt.icom.museum"),
];
let filtered_domains: Vec<String> =
Domain::filter_domains(valid_idns.into_iter()).collect();
dbg!(&filtered_domains);
assert_eq!(filtered_domains.len(), 5);
}
}