1use std::path::{Path, PathBuf};
2
3use chromiumoxide_cdp::cdp::browser_protocol::network::ResourceType;
4
5pub(crate) async fn write<P: AsRef<Path> + Unpin, C: AsRef<[u8]>>(
7 path: P,
8 contents: C,
9) -> std::io::Result<()> {
10 tokio::fs::write(path.as_ref(), contents.as_ref()).await
11}
12
13pub(crate) async fn canonicalize<P: AsRef<Path> + Unpin>(path: P) -> std::io::Result<PathBuf> {
18 let path = tokio::fs::canonicalize(path.as_ref()).await?;
19
20 Ok(dunce::simplified(&path).to_path_buf())
21}
22
23pub(crate) fn absolute(path: PathBuf) -> std::io::Result<PathBuf> {
26 let path = if path.is_absolute() {
27 path
28 } else {
29 std::env::current_dir()?.join(path)
30 };
31 Ok(dunce::simplified(&path).to_path_buf())
32}
33
34pub(crate) async fn canonicalize_except_snap(path: PathBuf) -> std::io::Result<PathBuf> {
37 let executable_cleaned: PathBuf = canonicalize(&path).await?;
39
40 Ok(if executable_cleaned.to_str().unwrap().ends_with("/snap") {
42 absolute(path).unwrap()
43 } else {
44 executable_cleaned
45 })
46}
47
48pub mod base64 {
49 use base64::engine::general_purpose::STANDARD;
50 use base64::{DecodeError, Engine};
51
52 pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
54 STANDARD.decode(input)
55 }
56}
57
58pub fn evaluation_string(function: impl AsRef<str>, params: &[impl AsRef<str>]) -> String {
61 let params = params
62 .iter()
63 .map(|s| format!("\"{}\"", s.as_ref()))
64 .collect::<Vec<_>>()
65 .join(",");
66 format!("({})({params})", function.as_ref())
67}
68
69pub fn is_likely_js_function(function: impl AsRef<str>) -> bool {
71 let mut fun = function.as_ref().trim_start();
72 if fun.is_empty() {
73 return false;
74 }
75 let mut offset = 0;
76
77 if fun.starts_with("async ") {
78 offset = "async ".len() - 1
79 }
80
81 if fun[offset..].trim_start().starts_with("function ") {
82 return true;
83 } else if skip_args(&mut fun) {
84 if fun.trim_start().starts_with("=>") {
87 return true;
88 }
89 }
90 false
91}
92
93fn skip_args(input: &mut &str) -> bool {
98 if !input.starts_with('(') {
99 return false;
100 }
101 let mut open = 1;
102 let mut closed = 0;
103 *input = &input[1..];
104 while !input.is_empty() && open != closed {
105 if let Some(idx) = input.find(&['(', ')'] as &[_]) {
106 if &input[idx..=idx] == ")" {
107 closed += 1;
108 } else {
109 open += 1;
110 }
111 *input = &input[idx + 1..];
112 } else {
113 break;
114 }
115 }
116
117 open == closed
118}
119
120pub fn is_data_resource(resource_type: &ResourceType) -> bool {
122 matches!(
123 resource_type,
124 ResourceType::Xhr | ResourceType::Fetch | ResourceType::WebSocket
125 )
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn is_js_function() {
134 assert!(is_likely_js_function("function abc() {}"));
135 assert!(is_likely_js_function("async function abc() {}"));
136 assert!(is_likely_js_function("() => {}"));
137 assert!(is_likely_js_function("(abc, def) => {}"));
138 assert!(is_likely_js_function("((abc), (def)) => {}"));
139 assert!(is_likely_js_function("() => Promise.resolve(100 / 25)"));
140 }
141}