1use crate::dictionary::Dictionary;
2use crate::rule::rule_result::RuleResult;
3use crate::rule::{DictionaryRuleTrait, PasswordData, Rule};
4use std::collections::HashMap;
5
6pub(crate) const ERROR_CODE: &str = "ILLEGAL_WORD";
7const ERROR_CODE_REVERSED: &str = "ILLEGAL_WORD_REVERSED";
8
9pub struct DictionarySubstringRule<D: Dictionary> {
31 dictionary: D,
32 match_backwards: bool,
33}
34
35impl<D: Dictionary> DictionarySubstringRule<D> {
36 pub fn new(dictionary: D, match_backwards: bool) -> Self {
37 Self {
38 dictionary,
39 match_backwards,
40 }
41 }
42 pub fn from_dictionary(dictionary: D) -> Self {
43 Self {
44 dictionary,
45 match_backwards: false,
46 }
47 }
48 fn do_word_search(&self, text: &str) -> Option<String> {
49 for i in 1..=text.len() {
50 let mut j = 0usize;
51 while j + i <= text.len() {
52 let s = &text[j..j + i];
53 if self.dictionary.search(s) {
54 return Some(s.to_string());
55 }
56 j += 1;
57 }
58 }
59 None
60 }
61 fn create_rule_result_detail_parameters(&self, matching_word: &str) -> HashMap<String, String> {
62 let mut map = HashMap::with_capacity(1);
63 map.insert("matchingWord".to_string(), matching_word.to_string());
64 map
65 }
66}
67
68impl<D: Dictionary> Rule for DictionarySubstringRule<D> {
69 fn validate(&self, password_data: &PasswordData) -> RuleResult {
70 let mut result = RuleResult::default();
71 let text = password_data.password();
72 let matching_word = self.do_word_search(text);
73 if let Some(m) = matching_word {
74 result.add_error(
75 ERROR_CODE,
76 Some(self.create_rule_result_detail_parameters(&m)),
77 )
78 }
79 if self.match_backwards && text.len() > 1 {
80 let text = text.chars().rev().collect::<String>();
81 let matching_word = self.do_word_search(&text);
82 if let Some(m) = matching_word {
83 result.add_error(
84 ERROR_CODE_REVERSED,
85 Some(self.create_rule_result_detail_parameters(&m)),
86 )
87 }
88 }
89 result
90 }
91 fn as_dictionary_rule(&self) -> Option<&dyn DictionaryRuleTrait> {
92 Some(self)
93 }
94}
95impl<D: Dictionary> DictionaryRuleTrait for DictionarySubstringRule<D> {
96 fn dictionary(&self) -> &dyn Dictionary {
97 &self.dictionary
98 }
99}
100#[cfg(test)]
101mod tests {
102 use crate::dictionary::word_lists::sort::SliceSort;
103 use crate::dictionary::word_lists::word_list_dictionary::WordListDictionary;
104 use crate::dictionary::word_lists::{ArrayWordList, create_from_read};
105 use crate::rule::PasswordData;
106 use crate::rule::dictionary_substring::{
107 DictionarySubstringRule, ERROR_CODE, ERROR_CODE_REVERSED,
108 };
109 use crate::test::{RulePasswordTestItem, check_messages, check_passwords};
110
111 #[test]
112 fn test_passwords() {
113 let test_cases: Vec<RulePasswordTestItem> = vec![
114 RulePasswordTestItem(
116 create_rule(),
117 PasswordData::with_password("p4t3t#7wd5gew".to_string()),
118 vec![],
119 ),
120 RulePasswordTestItem(
122 create_rule(),
123 PasswordData::with_password("p4tlancely5gew".to_string()),
124 vec![ERROR_CODE],
125 ),
126 RulePasswordTestItem(
128 create_rule(),
129 PasswordData::with_password("p4tylecnal5gew".to_string()),
130 vec![],
131 ),
132 RulePasswordTestItem(
134 create_rule(),
135 PasswordData::with_password("p4tlAnCeLy5gew".to_string()),
136 vec![],
137 ),
138 RulePasswordTestItem(
140 create_rule(),
141 PasswordData::with_password("p4tyLeCnAl5gew".to_string()),
142 vec![],
143 ),
144 RulePasswordTestItem(
146 create_backward_rule(),
147 PasswordData::with_password("p4t3t#7wd5gew".to_string()),
148 vec![],
149 ),
150 RulePasswordTestItem(
152 create_backward_rule(),
153 PasswordData::with_password("p4tlancely5gew".to_string()),
154 vec![ERROR_CODE],
155 ),
156 RulePasswordTestItem(
158 create_backward_rule(),
159 PasswordData::with_password("p4tylecnal5gew".to_string()),
160 vec![ERROR_CODE_REVERSED],
161 ),
162 RulePasswordTestItem(
164 create_backward_rule(),
165 PasswordData::with_password("p4tlAnCeLy5gew".to_string()),
166 vec![],
167 ),
168 RulePasswordTestItem(
170 create_backward_rule(),
171 PasswordData::with_password("p4tyLeCnAl5gew".to_string()),
172 vec![],
173 ),
174 RulePasswordTestItem(
176 create_ignore_case_rule(),
177 PasswordData::with_password("p4t3t#7wd5gew".to_string()),
178 vec![],
179 ),
180 RulePasswordTestItem(
182 create_ignore_case_rule(),
183 PasswordData::with_password("p4tlancely5gew".to_string()),
184 vec![ERROR_CODE],
185 ),
186 RulePasswordTestItem(
188 create_ignore_case_rule(),
189 PasswordData::with_password("p4tylecnal5gew".to_string()),
190 vec![],
191 ),
192 RulePasswordTestItem(
194 create_ignore_case_rule(),
195 PasswordData::with_password("p4tlAnCeLy5gew".to_string()),
196 vec![ERROR_CODE],
197 ),
198 RulePasswordTestItem(
200 create_ignore_case_rule(),
201 PasswordData::with_password("p4tyLeCnAl5gew".to_string()),
202 vec![],
203 ),
204 RulePasswordTestItem(
206 create_all_rule(),
207 PasswordData::with_password("p4t3t#7wd5gew".to_string()),
208 vec![],
209 ),
210 RulePasswordTestItem(
212 create_all_rule(),
213 PasswordData::with_password("p4tlancely5gew".to_string()),
214 vec![ERROR_CODE],
215 ),
216 RulePasswordTestItem(
218 create_all_rule(),
219 PasswordData::with_password("p4tylecnal5gew".to_string()),
220 vec![ERROR_CODE_REVERSED],
221 ),
222 RulePasswordTestItem(
224 create_all_rule(),
225 PasswordData::with_password("p4tlAnCeLy5gew".to_string()),
226 vec![ERROR_CODE],
227 ),
228 RulePasswordTestItem(
230 create_all_rule(),
231 PasswordData::with_password("p4tyLeCnAl5gew".to_string()),
232 vec![ERROR_CODE_REVERSED],
233 ),
234 ];
235 check_passwords(test_cases);
236 }
237
238 #[test]
239 fn test_messages() {
240 let test_cases: Vec<RulePasswordTestItem> = vec![
241 RulePasswordTestItem(
242 create_rule(),
243 PasswordData::with_password("p4tlancely5gew".to_string()),
244 vec!["ILLEGAL_WORD,lance"],
245 ),
246 RulePasswordTestItem(
247 create_backward_rule(),
248 PasswordData::with_password("p4tylecnal5gew".to_string()),
249 vec!["ILLEGAL_WORD_REVERSED,lance"],
250 ),
251 ];
252 check_messages(test_cases);
253 }
254 fn create_rule() -> Box<DictionarySubstringRule<WordListDictionary<ArrayWordList>>> {
255 let case_sensitive_word_list = create_from_read(read_word_list(), true, Some(SliceSort));
256 let case_sensitive_dict = WordListDictionary::new(case_sensitive_word_list);
257 Box::new(DictionarySubstringRule::from_dictionary(
258 case_sensitive_dict,
259 ))
260 }
261
262 fn create_backward_rule() -> Box<DictionarySubstringRule<WordListDictionary<ArrayWordList>>> {
263 let case_sensitive_word_list = create_from_read(read_word_list(), true, Some(SliceSort));
264 let case_sensitive_dict = WordListDictionary::new(case_sensitive_word_list);
265 Box::new(DictionarySubstringRule::new(case_sensitive_dict, true))
266 }
267
268 fn create_ignore_case_rule() -> Box<DictionarySubstringRule<WordListDictionary<ArrayWordList>>>
269 {
270 let case_insensitive_word_list = create_from_read(read_word_list(), false, Some(SliceSort));
271 let case_insensitive_dict = WordListDictionary::new(case_insensitive_word_list);
272 Box::new(DictionarySubstringRule::from_dictionary(
273 case_insensitive_dict,
274 ))
275 }
276 fn create_all_rule() -> Box<DictionarySubstringRule<WordListDictionary<ArrayWordList>>> {
277 let case_insensitive_word_list = create_from_read(read_word_list(), false, Some(SliceSort));
278 let case_insensitive_dict = WordListDictionary::new(case_insensitive_word_list);
279 Box::new(DictionarySubstringRule::new(case_insensitive_dict, true))
280 }
281 fn read_word_list() -> &'static [u8] {
282 include_bytes!("../../resources/test/web2-gt3")
283 }
284}