chromiumoxide/
utils.rs

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