1use anyhow::{anyhow, Result};
2use rand::{distributions::Alphanumeric, Rng};
3use regex::Regex;
4
5pub fn random_string(len: usize) -> String {
6 rand::thread_rng()
7 .sample_iter(&Alphanumeric)
8 .take(len)
9 .map(char::from)
10 .collect()
11}
12
13pub fn random_hex_string(len: usize) -> String {
14 let mut bytes = vec![0u8; len.div_ceil(2)];
15 rand::thread_rng().fill(&mut bytes[..]);
16 let hex_string = hex::encode(bytes);
17 hex_string[..len].to_string()
18}
19
20pub fn extract_js_links(html: &str) -> Vec<String> {
21 let re = Regex::new(r#"src="([^"]+\.js)""#).unwrap();
22 re.captures_iter(html)
23 .map(|cap| cap[1].to_string())
24 .collect()
25}
26
27pub fn extract_mappings(
28 js_code: &str,
29) -> Result<(
30 std::collections::HashMap<i32, String>,
31 std::collections::HashMap<i32, String>,
32)> {
33 let re_obj = Regex::new(r#"\{(\d+:"[^"]+"(?:,\d+:"[^"]+")*)\}"#).unwrap();
34
35 let matches: Vec<_> = re_obj.find_iter(js_code).map(|m| m.as_str()).collect();
36
37 if matches.len() < 5 {
38 return Err(anyhow!(
39 "Could not find both mappings in the JS code (matches found: {})",
40 matches.len()
41 ));
42 }
43
44 let map1 = parse_js_dict(matches[3])?;
45 let map2 = parse_js_dict(matches[4])?;
46
47 Ok((map1, map2))
48}
49
50fn parse_js_dict(s: &str) -> Result<std::collections::HashMap<i32, String>> {
51 let content = s.trim_start_matches('{').trim_end_matches('}');
52 let mut map = std::collections::HashMap::new();
53
54 let mut current = content;
55 while !current.is_empty() {
56 if let Some(colon_idx) = current.find(':') {
57 let key_str = ¤t[..colon_idx];
58 let key: i32 = key_str
59 .parse()
60 .map_err(|_| anyhow!("Failed to parse key: {}", key_str))?;
61
62 let remainder = ¤t[colon_idx + 1..];
63 if !remainder.starts_with('"') {
64 return Err(anyhow!("Value does not start with quote"));
65 }
66
67 if let Some(end_quote_idx) = remainder[1..].find('"') {
68 let end_quote_real_idx = end_quote_idx + 1;
69 let value = &remainder[1..end_quote_real_idx];
70 map.insert(key, value.to_string());
71
72 if remainder.len() > end_quote_real_idx + 1 {
73 if remainder.as_bytes()[end_quote_real_idx + 1] == b',' {
74 current = &remainder[end_quote_real_idx + 2..];
75 } else {
76 break;
77 }
78 } else {
79 break;
80 }
81 } else {
82 return Err(anyhow!("Value does not end with quote"));
83 }
84 } else {
85 break;
86 }
87 }
88
89 Ok(map)
90}
91
92pub fn combine_chunks(
95 name_map: &std::collections::HashMap<i32, String>,
96 hash_map: &std::collections::HashMap<i32, String>,
97) -> Vec<String> {
98 let mut combined = Vec::new();
99 for (key, name) in name_map {
100 if let Some(hash) = hash_map.get(key) {
101 combined.push(format!("{}.{}.js", name, hash));
103 }
104 }
105 combined
106}