1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
extern crate rand;
mod utils;
use rand::Rng;
#[cfg(feature="built_in_dicts")]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum Dictionary {
en_US,
}
#[cfg(feature="built_in_dicts")]
impl Dictionary {
fn to_raw_dict(self) -> &'static str {
match self {
Dictionary::en_US => include_str!(concat!(env!("OUT_DIR"), "/dictionary/en_US")),
}
}
}
pub fn generate_password_custom_dict(word_count: usize, dict: &[&str]) -> String {
let dict = utils::trim_right_by(dict, |e| e.is_empty());
let capacity = word_count * dict.len() + word_count;
let n_of_words: Vec<usize> = dict.iter().enumerate().map(|x| x.1.len() / (x.0 + 1)).collect();
let total_n_of_words = n_of_words.iter().sum();
if dict.iter().zip(n_of_words.iter()).enumerate().any(|(wlen, (words, count))| (wlen+1)*count != words.len()) {
panic!("Error in password generation: Dictionary invalid.");
}
let mut s = String::with_capacity(capacity);
for word_n in (0..word_count).map(|_| rand::thread_rng().gen_range(0, total_n_of_words)) {
let mut found: Option<&str> = None;
let mut real_pos = 0;
for (wlen, (n_words, words)) in n_of_words.iter().zip(dict.iter()).enumerate() {
let wlen = wlen + 1;
real_pos += n_words;
if word_n < real_pos {
let base_pos = real_pos - n_words;
let inner_pos = word_n - base_pos;
let inner_index = inner_pos * wlen;
found = Some(&words[inner_index..inner_index+wlen]);
break;
}
}
s.push_str(found.expect("bug in xkcd-password"));
s.push(' ');
}
s
}
pub fn generate_password_from_words<'a, I: IntoIterator<Item=&'a str>>(word_count: usize, dict: I) -> String {
let iter = dict.into_iter();
let mut dict: Vec<String> = vec![];
for word in iter {
if word.contains(' ') || word.contains('\n') {
panic!("Word contains space or newline characters");
}
if dict.len() < word.len() {
dict.resize(word.len(), String::default());
}
if dict[word.len() - 1].as_bytes().chunks(word.len()).any(|chunk| chunk == word.as_bytes()) {
panic!("Duplicate word in dict");
}
dict[word.len() - 1].push_str(word);
}
let dict: Vec<&str> = dict.iter().map(String::as_str).collect();
generate_password_custom_dict(word_count, &dict)
}
#[cfg(feature="built_in_dicts")]
pub fn generate_password(word_count: usize, dict: Dictionary) -> String {
let v: Vec<&str> = dict.to_raw_dict().lines().collect();
generate_password_custom_dict(word_count, &v)
}