Skip to main content

chromiumoxide/
utils.rs

1use std::path::{Path, PathBuf};
2
3use chromiumoxide_cdp::cdp::browser_protocol::network::ResourceType;
4
5/// Write to file — uses io_uring when available, falls back to tokio::fs.
6pub(crate) async fn write<P: AsRef<Path> + Unpin, C: AsRef<[u8]>>(
7    path: P,
8    contents: C,
9) -> std::io::Result<()> {
10    crate::uring_fs::write_file(
11        path.as_ref().display().to_string(),
12        contents.as_ref().to_vec(),
13    )
14    .await
15}
16
17/// Canonicalize path
18///
19/// Chromium sandboxing does not support Window UNC paths which are used by Rust
20/// when the path is relative. See https://bugs.chromium.org/p/chromium/issues/detail?id=1415018.
21pub(crate) async fn canonicalize<P: AsRef<Path> + Unpin>(path: P) -> std::io::Result<PathBuf> {
22    let path = tokio::fs::canonicalize(path.as_ref()).await?;
23
24    Ok(dunce::simplified(&path).to_path_buf())
25}
26
27/// Absolute path
28///
29pub(crate) fn absolute(path: PathBuf) -> std::io::Result<PathBuf> {
30    let path = if path.is_absolute() {
31        path
32    } else {
33        std::env::current_dir()?.join(path)
34    };
35    Ok(dunce::simplified(&path).to_path_buf())
36}
37
38/// Canonicalize path except if target binary is snap, in this case only make the path absolute
39///
40pub(crate) async fn canonicalize_except_snap(path: PathBuf) -> std::io::Result<PathBuf> {
41    // Canonalize paths to reduce issues with sandboxing
42    let executable_cleaned: PathBuf = canonicalize(&path).await?;
43
44    // Handle case where executable is provided by snap, ignore canonicalize result and only make path absolute
45    Ok(
46        if executable_cleaned
47            .to_str()
48            .map_or(false, |s| s.ends_with("/snap"))
49        {
50            absolute(path)?
51        } else {
52            executable_cleaned
53        },
54    )
55}
56
57pub mod base64 {
58    use base64::engine::general_purpose::STANDARD;
59    use base64::{DecodeError, Engine};
60
61    /// Decode base64 using the standard alphabet and padding
62    pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
63        STANDARD.decode(input)
64    }
65}
66
67/// Creates a javascript function string as `(<function>)("<param 1>", "<param
68/// 2>")`
69pub fn evaluation_string(function: impl AsRef<str>, params: &[impl AsRef<str>]) -> String {
70    let params = params
71        .iter()
72        .map(|s| format!("\"{}\"", s.as_ref()))
73        .collect::<Vec<_>>()
74        .join(",");
75    format!("({})({params})", function.as_ref())
76}
77
78/// Tries to identify whether this a javascript function
79pub fn is_likely_js_function(function: impl AsRef<str>) -> bool {
80    let mut fun = function.as_ref().trim_start();
81    if fun.is_empty() {
82        return false;
83    }
84    let mut offset = 0;
85
86    if fun.starts_with("async ") {
87        offset = "async ".len() - 1
88    }
89
90    if fun[offset..].trim_start().starts_with("function ") {
91        return true;
92    } else if skip_args(&mut fun) {
93        // attempt to detect arrow functions by stripping the leading arguments and
94        // looking for the arrow
95        if fun.trim_start().starts_with("=>") {
96            return true;
97        }
98    }
99    false
100}
101
102/// This attempts to strip any leading pair of parentheses from the input
103///
104/// `()=>` -> `=>`
105/// `(abc, def)=>` -> `=>`
106fn skip_args(input: &mut &str) -> bool {
107    if !input.starts_with('(') {
108        return false;
109    }
110    let mut open = 1;
111    let mut closed = 0;
112    *input = &input[1..];
113    while !input.is_empty() && open != closed {
114        if let Some(idx) = input.find(&['(', ')'] as &[_]) {
115            if &input[idx..=idx] == ")" {
116                closed += 1;
117            } else {
118                open += 1;
119            }
120            *input = &input[idx + 1..];
121        } else {
122            break;
123        }
124    }
125
126    open == closed
127}
128
129/// Is the resource a data event.
130pub fn is_data_resource(resource_type: &ResourceType) -> bool {
131    matches!(
132        resource_type,
133        ResourceType::Xhr
134            | ResourceType::Fetch
135            | ResourceType::WebSocket
136            | ResourceType::EventSource
137            | ResourceType::Prefetch
138            | ResourceType::Preflight
139    )
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn is_js_function() {
148        assert!(is_likely_js_function("function abc() {}"));
149        assert!(is_likely_js_function("async function abc() {}"));
150        assert!(is_likely_js_function("() => {}"));
151        assert!(is_likely_js_function("(abc, def) => {}"));
152        assert!(is_likely_js_function("((abc), (def)) => {}"));
153        assert!(is_likely_js_function("() => Promise.resolve(100 / 25)"));
154    }
155}