Skip to main content

faker_rust/default/
internet.rs

1//! Internet generator - generates internet-related fake data
2#![allow(dead_code)]
3
4use crate::base::sample;
5// Removed unused ALPHANUMERIC
6use 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
14/// Generate a random email address
15pub 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
30/// Generate a random username with custom separators
31fn 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
51/// Generate a random username
52pub fn username(specifier: Option<&str>) -> String {
53    username_with_sep(specifier, &["."])
54}
55
56/// Generate a random password
57pub 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
107/// Generate a random domain name
108pub 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
126/// Generate a random domain suffix
127pub 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
139/// Fixes ä, ö, ü, ß characters in string
140pub fn fix_umlauts(string: &str) -> string::String {
141    string
142        .replace('ä', "ae")
143        .replace('ö', "oe")
144        .replace('ü', "ue")
145        .replace('ß', "ss")
146}
147
148/// Generate a random domain word
149fn 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
158/// Generate a random IPv4 address
159pub 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
170/// Generate a random private IPv4 address
171pub 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
195/// Generate a random IPv6 address
196pub 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
204/// Generate IPv4 with CIDR notation
205pub fn ip_v4_cidr() -> string::String {
206    let config = FakerConfig::current();
207    format!("{}/{}", ip_v4(), config.rand_range(1, 31))
208}
209
210/// Generate IPv6 with CIDR notation
211pub fn ip_v6_cidr() -> string::String {
212    let config = FakerConfig::current();
213    format!("{}/{}", ip_v6(), config.rand_range(1, 127))
214}
215
216/// Generate a random MAC address
217pub 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
244/// Generate a random URL
245pub 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
254/// Generate a random slug (URL-friendly string)
255pub 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
273/// Generate a random device token
274pub 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
286/// Generate a random UUID
287pub 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
319/// Generate a random user agent string
320pub fn user_agent(vendor: Option<&str>) -> string::String {
321    let config = FakerConfig::current();
322
323    // List of supported vendors (same as Ruby Faker)
324    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    // Try to get agents from locale - handle both key formats
349    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
367/// Generate a bot user agent
368pub fn bot_user_agent(vendor: Option<&str>) -> string::String {
369    let config = FakerConfig::current();
370
371    // List of supported vendors (same as Ruby Faker)
372    let vendors = [
373        "googlebot",
374        "bingbot",
375        "duckduckbot",
376        "baiduspider",
377        "yandexbot",
378    ];
379
380    let selected_vendor = match vendor {
381        Some(v) => {
382            // Validate vendor is supported
383            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    // Try to get agents from locale - use a simpler key format
396    let path = format!("bot_user_agent.{}", selected_vendor);
397
398    // Try with 'internet.' prefix first
399    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
407/// Generate a random base64 string
408pub 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
437/// Generate a random internet user hash
438pub 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];