chromiumoxide/
utils.rs

1use std::path::{Path, PathBuf};
2
3use chromiumoxide_cdp::cdp::browser_protocol::network::ResourceType;
4
5/// Write to file with configured runtime
6pub(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
13/// Canonicalize path
14///
15/// Chromium sandboxing does not support Window UNC paths which are used by Rust
16/// when the path is relative. See https://bugs.chromium.org/p/chromium/issues/detail?id=1415018.
17pub(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
23/// Absolute path
24///
25pub(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
34/// Canonicalize path except if target binary is snap, in this case only make the path absolute
35///
36pub(crate) async fn canonicalize_except_snap(path: PathBuf) -> std::io::Result<PathBuf> {
37    // Canonalize paths to reduce issues with sandboxing
38    let executable_cleaned: PathBuf = canonicalize(&path).await?;
39
40    // Handle case where executable is provided by snap, ignore canonicalize result and only make path absolute
41    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    /// Decode base64 using the standard alphabet and padding
53    pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
54        STANDARD.decode(input)
55    }
56}
57
58/// Creates a javascript function string as `(<function>)("<param 1>", "<param
59/// 2>")`
60pub 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
69/// Tries to identify whether this a javascript function
70pub 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        // attempt to detect arrow functions by stripping the leading arguments and
85        // looking for the arrow
86        if fun.trim_start().starts_with("=>") {
87            return true;
88        }
89    }
90    false
91}
92
93/// This attempts to strip any leading pair of parentheses from the input
94///
95/// `()=>` -> `=>`
96/// `(abc, def)=>` -> `=>`
97fn 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
120/// Is the resource a data event.
121pub 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}