1#![allow(dead_code)]
3
4use crate::base::sample;
5use crate::base::DIGITS;
7use crate::base::L_LETTERS;
8use crate::base::U_LETTERS;
9use crate::company;
10use crate::config::FakerConfig;
11use crate::locale::fetch_locale;
12use crate::name;
13
14pub fn email(name: Option<&str>, domain: Option<&str>, separators: Option<Vec<&str>>) -> String {
16 let sep = separators.unwrap_or_else(|| vec!["."]);
17 let local = match name {
18 Some(n) => sanitize_email_local(username_with_sep(Some(n), &sep)),
19 None => username_with_sep(None, &sep),
20 };
21
22 let domain = match domain {
23 Some(d) => d.to_string(),
24 None => domain_suffix(false),
25 };
26
27 format!("{}@{}", local, domain)
28}
29
30fn username_with_sep(specifier: Option<&str>, separators: &[&str]) -> String {
32 match specifier {
33 Some(name) => {
34 let name = sanitize_username(name);
35 let sep = sample(separators);
36 name.to_lowercase()
37 .split_whitespace()
38 .take(2)
39 .collect::<Vec<_>>()
40 .join(sep)
41 }
42 None => {
43 let first = name::first_name().to_lowercase();
44 let last = name::last_name().to_lowercase();
45 let sep = sample(separators);
46 format!("{}{}{}", first, sep, last)
47 }
48 }
49}
50
51pub fn username(specifier: Option<&str>) -> String {
53 username_with_sep(specifier, &["."])
54}
55
56pub fn password(min_length: usize, max_length: usize, mix_case: bool, special: bool) -> String {
58 let config = FakerConfig::current();
59
60 let mut required_min = 2;
61 if mix_case {
62 required_min += 1;
63 }
64 if special {
65 required_min += 1;
66 }
67
68 if min_length < required_min {
69 panic!(
70 "min_length must be at least {} for the given options",
71 required_min
72 );
73 }
74
75 let length = config.rand_range(min_length as u32, max_length as u32) as usize;
76 let mut password = Vec::with_capacity(length);
77
78 password.push(config.rand_char(&L_LETTERS));
79 password.push(config.rand_char(&DIGITS));
80
81 if mix_case {
82 password.push(config.rand_char(&U_LETTERS));
83 }
84
85 if special {
86 password.push(sample(&['!', '@', '#', '$', '%', '^', '&', '*']));
87 }
88
89 let mut charset: Vec<char> = L_LETTERS.to_vec();
90 charset.extend_from_slice(&DIGITS);
91 if mix_case {
92 charset.extend_from_slice(&U_LETTERS);
93 }
94 if special {
95 charset.extend_from_slice(&['!', '@', '#', '$', '%', '^', '&', '*']);
96 }
97
98 while password.len() < length {
99 password.push(config.sample(&charset));
100 }
101
102 config.shuffle(&mut password);
103
104 password.into_iter().collect()
105}
106
107pub fn domain_name(subdomain: bool, domain: Option<&str>) -> String {
109 if let Some(d) = domain {
110 let parts: Vec<&str> = d.split('.').collect();
111 if parts.len() < 2 {
112 return format!("{}.{}", domain_word(), domain_suffix(false));
113 }
114 return d.to_string();
115 }
116
117 let base = domain_word();
118
119 if subdomain {
120 format!("{}.{}.{}", domain_word(), base, domain_suffix(false))
121 } else {
122 format!("{}.{}", base, domain_suffix(false))
123 }
124}
125
126pub fn domain_suffix(safe: bool) -> String {
128 if safe {
129 fetch_locale("internet.safe_domain_suffix", "en")
130 .map(|v| sample(&v))
131 .unwrap_or_else(|| sample(FALLBACK_SAFE_DOMAIN_SUFFIXES).to_string())
132 } else {
133 fetch_locale("internet.domain_suffix", "en")
134 .map(|v| sample(&v))
135 .unwrap_or_else(|| sample(FALLBACK_DOMAIN_SUFFIXES).to_string())
136 }
137}
138
139pub fn fix_umlauts(string: &str) -> string::String {
141 string
142 .replace('ä', "ae")
143 .replace('ö', "oe")
144 .replace('ü', "ue")
145 .replace('ß', "ss")
146}
147
148fn domain_word() -> string::String {
150 let company_name = company::name();
151 company_name
152 .split_whitespace()
153 .next()
154 .unwrap_or("example")
155 .to_lowercase()
156}
157
158pub fn ip_v4() -> string::String {
160 let config = FakerConfig::current();
161 format!(
162 "{}.{}.{}.{}",
163 config.rand_range(1, 255),
164 config.rand_range(0, 255),
165 config.rand_range(0, 255),
166 config.rand_range(1, 255)
167 )
168}
169
170pub fn private_ip_v4() -> string::String {
172 let config = FakerConfig::current();
173 let choice = config.rand_range(0, 5);
174 match choice {
175 0 => format!(
176 "10.{}.{}.{}",
177 config.rand_range(0, 255),
178 config.rand_range(0, 255),
179 config.rand_range(1, 255)
180 ),
181 1 => format!(
182 "172.{}.{}.{}",
183 config.rand_range(16, 31),
184 config.rand_range(0, 255),
185 config.rand_range(1, 255)
186 ),
187 _ => format!(
188 "192.168.{}.{}",
189 config.rand_range(0, 255),
190 config.rand_range(1, 255)
191 ),
192 }
193}
194
195pub fn ip_v6() -> string::String {
197 let config = FakerConfig::current();
198 (0..8)
199 .map(|_| format!("{:x}", config.rand_range(0, 65535)))
200 .collect::<Vec<_>>()
201 .join(":")
202}
203
204pub fn ip_v4_cidr() -> string::String {
206 let config = FakerConfig::current();
207 format!("{}/{}", ip_v4(), config.rand_range(1, 31))
208}
209
210pub fn ip_v6_cidr() -> string::String {
212 let config = FakerConfig::current();
213 format!("{}/{}", ip_v6(), config.rand_range(1, 127))
214}
215
216pub fn mac_address(prefix: Option<&str>) -> string::String {
218 let config = FakerConfig::current();
219
220 let (prefix_bytes, count) = if let Some(p) = prefix {
221 let parts: Vec<&str> = p.split(':').collect();
222 let bytes: Vec<u8> = parts
223 .iter()
224 .filter_map(|s| u8::from_str_radix(s, 16).ok())
225 .collect();
226 let len = bytes.len();
227 (bytes, 6 - len)
228 } else {
229 (vec![], 6)
230 };
231
232 let mut result = prefix_bytes;
233 for _ in 0..count {
234 result.push(config.rand_range(0, 256) as u8);
235 }
236
237 result
238 .iter()
239 .map(|b| format!("{:02x}", b))
240 .collect::<Vec<_>>()
241 .join(":")
242}
243
244pub fn url(host: Option<&str>, path: Option<&str>, scheme: Option<&str>) -> string::String {
246 let generated_host = domain_name(false, None);
247 let host = host.unwrap_or(&generated_host);
248 let generated_path = format!("/{}", username(None));
249 let path = path.unwrap_or(&generated_path);
250 let scheme = scheme.unwrap_or("http");
251 format!("{}://{}{}", scheme, host, path)
252}
253
254pub fn slug(words: Option<&str>, glue: Option<&str>) -> string::String {
256 let config = FakerConfig::current();
257
258 if let Some(w) = words {
259 let g = glue.unwrap_or("-");
260 w.replace([',', '.'], "")
261 .split_whitespace()
262 .collect::<Vec<_>>()
263 .join(g)
264 .to_lowercase()
265 } else {
266 let g = glue.unwrap_or_else(|| if config.rand_bool() { "-" } else { "_" });
267 let word1 = name::first_name().to_lowercase();
268 let word2 = name::last_name().to_lowercase();
269 format!("{}{}{}", word1, g, word2)
270 }
271}
272
273pub fn device_token() -> string::String {
275 let config = FakerConfig::current();
276 let mut chars: Vec<char> = (0..64)
277 .map(|_| {
278 let idx = config.rand_range(0, 16) as u8;
279 format!("{:x}", idx).chars().next().unwrap()
280 })
281 .collect();
282 config.shuffle(&mut chars);
283 chars.into_iter().collect()
284}
285
286pub fn uuid() -> string::String {
288 let config = FakerConfig::current();
289 let rng = config.rng.borrow_mut();
290 let mut bytes = [0u8; 16];
291 use rand::RngCore;
292 let mut rng = rng;
293 rng.fill_bytes(&mut bytes);
294
295 bytes[6] = (bytes[6] & 0x0f) | 0x40;
296 bytes[8] = (bytes[8] & 0x3f) | 0x80;
297
298 format!(
299 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
300 bytes[0],
301 bytes[1],
302 bytes[2],
303 bytes[3],
304 bytes[4],
305 bytes[5],
306 bytes[6],
307 bytes[7],
308 bytes[8],
309 bytes[9],
310 bytes[10],
311 bytes[11],
312 bytes[12],
313 bytes[13],
314 bytes[14],
315 bytes[15]
316 )
317}
318
319pub fn user_agent(vendor: Option<&str>) -> string::String {
321 let config = FakerConfig::current();
322
323 let vendors = [
325 "chrome",
326 "safari",
327 "firefox",
328 "internet_explorer",
329 "opera",
330 "netscape",
331 "aol",
332 ];
333
334 let selected_vendor = match vendor {
335 Some(v) => {
336 if vendors.contains(&v) {
337 v
338 } else {
339 return generate_fallback_user_agent(&config);
340 }
341 }
342 None => {
343 let idx = config.rand_range(0, vendors.len() as u32) as usize;
344 vendors[idx]
345 }
346 };
347
348 let path = format!("user_agent.{}", selected_vendor);
350
351 let result =
352 fetch_locale(&format!("internet.{}", path), "en").or_else(|| fetch_locale(&path, "en"));
353
354 result
355 .map(|agents| sample(&agents))
356 .unwrap_or_else(|| generate_fallback_user_agent(&config))
357}
358
359fn generate_fallback_user_agent(config: &FakerConfig) -> string::String {
360 let version = config.rand_range(80, 120);
361 format!(
362 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{} Safari/537.36",
363 version
364 )
365}
366
367pub fn bot_user_agent(vendor: Option<&str>) -> string::String {
369 let config = FakerConfig::current();
370
371 let vendors = [
373 "googlebot",
374 "bingbot",
375 "duckduckbot",
376 "baiduspider",
377 "yandexbot",
378 ];
379
380 let selected_vendor = match vendor {
381 Some(v) => {
382 if vendors.contains(&v) {
384 v
385 } else {
386 return sample(FALLBACK_BOT_AGENTS).to_string();
387 }
388 }
389 None => {
390 let idx = config.rand_range(0, vendors.len() as u32) as usize;
391 vendors[idx]
392 }
393 };
394
395 let path = format!("bot_user_agent.{}", selected_vendor);
397
398 let result =
400 fetch_locale(&format!("internet.{}", path), "en").or_else(|| fetch_locale(&path, "en"));
401
402 result
403 .map(|agents| sample(&agents))
404 .unwrap_or_else(|| sample(FALLBACK_BOT_AGENTS).to_string())
405}
406
407pub fn base64(length: usize, padding: bool, urlsafe: bool) -> string::String {
409 let config = FakerConfig::current();
410 let charset: Vec<char> = if urlsafe {
411 let mut c: Vec<char> = ('0'..='9').collect();
412 c.extend('A'..='Z');
413 c.extend('a'..='z');
414 c.push('-');
415 c.push('_');
416 c
417 } else {
418 let mut c: Vec<char> = ('0'..='9').collect();
419 c.extend('A'..='Z');
420 c.extend('a'..='z');
421 c.push('+');
422 c.push('/');
423 c
424 };
425
426 let mut result: string::String = (0..length).map(|_| config.sample(&charset)).collect();
427
428 if padding {
429 while result.len() % 4 != 0 {
430 result.push('=');
431 }
432 }
433
434 result
435}
436
437pub fn user(fields: Vec<&str>) -> std::collections::HashMap<string::String, string::String> {
439 use std::collections::HashMap;
440 let mut map = HashMap::new();
441
442 if fields.is_empty() {
443 map.insert("username".to_string(), username(None));
444 map.insert("email".to_string(), email(None, None, None));
445 } else {
446 for field in fields {
447 match field {
448 "username" => {
449 map.insert("username".to_string(), username(None));
450 }
451 "email" => {
452 map.insert("email".to_string(), email(None, None, None));
453 }
454 "password" => {
455 map.insert("password".to_string(), password(12, 20, true, true));
456 }
457 _ => {}
458 }
459 }
460 }
461 map
462}
463
464fn sanitize_email_local(local: string::String) -> string::String {
465 local
466 .chars()
467 .filter(|c| c.is_alphanumeric() || *c == '.' || *c == '_' || *c == '-' || *c == '+')
468 .collect()
469}
470
471fn sanitize_username(name: &str) -> string::String {
472 name.replace("'", "").replace('.', " ")
473}
474
475mod string {
476 pub type String = std::string::String;
477}
478
479const SAFE_DOMAIN_SUFFIXES: &[&str] = &["test", "example", "localhost"];
480
481const FALLBACK_DOMAIN_SUFFIXES: &[&str] = &[
482 "com", "net", "org", "io", "co", "biz", "info", "me", "us", "uk", "ca", "de", "fr", "jp",
483];
484
485const FALLBACK_SAFE_DOMAIN_SUFFIXES: &[&str] = &["test", "example", "localhost"];
486
487const FALLBACK_BOT_AGENTS: &[&str] = &[
488 "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/99.0.4844.84 Safari/537.36",
489 "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/86.0.4240.68 Safari/537.36 Edg/86.0.622.31",
490 "DuckDuckBot/1.0; (+http://duckduckgo.com/duckduckbot.html)",
491 "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)",
492 "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)",
493];