1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::env;
use std::fs::{create_dir_all, File};
use std::io::{Error, Read, Seek, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::rc::Rc;

use servo_url::ServoUrl;
use tempfile::NamedTempFile;
use uuid::Uuid;

use crate::dom::bindings::str::DOMString;

pub(crate) trait ScriptSource {
    fn unminified_dir(&self) -> Option<String>;
    fn extract_bytes(&self) -> &[u8];
    fn rewrite_source(&mut self, source: Rc<DOMString>);
    fn url(&self) -> ServoUrl;
    fn is_external(&self) -> bool;
}

pub fn create_temp_files() -> Option<(NamedTempFile, File)> {
    // Write the minified code to a temporary file and pass its path as an argument
    // to js-beautify to read from. Meanwhile, redirect the process' stdout into
    // another temporary file and read that into a string. This avoids some hangs
    // observed on macOS when using direct input/output pipes with very large
    // unminified content.
    let (input, output) = (NamedTempFile::new(), tempfile::tempfile());
    if let (Ok(input), Ok(output)) = (input, output) {
        Some((input, output))
    } else {
        log::warn!("Error creating input and output temp files");
        None
    }
}

#[derive(Debug)]
pub enum BeautifyFileType {
    Css,
    Js,
}

pub fn execute_js_beautify(input: &Path, output: File, file_type: BeautifyFileType) -> bool {
    let mut cmd = Command::new("js-beautify");
    match file_type {
        BeautifyFileType::Js => (),
        BeautifyFileType::Css => {
            cmd.arg("--type").arg("css");
        },
    }
    match cmd.arg(input).stdout(output).status() {
        Ok(status) => status.success(),
        _ => {
            log::warn!(
                "Failed to execute js-beautify --type {:?}, Will store unmodified script",
                file_type
            );
            false
        },
    }
}

pub fn create_output_file(
    unminified_dir: String,
    url: &ServoUrl,
    external: Option<bool>,
) -> Result<File, Error> {
    let path = PathBuf::from(unminified_dir);

    let (base, has_name) = match url.as_str().ends_with('/') {
        true => (
            path.join(&url[url::Position::BeforeHost..])
                .as_path()
                .to_owned(),
            false,
        ),
        false => (
            path.join(&url[url::Position::BeforeHost..])
                .parent()
                .unwrap()
                .to_owned(),
            true,
        ),
    };

    create_dir_all(&base)?;

    let path = if external.unwrap_or(true) && has_name {
        // External.
        path.join(&url[url::Position::BeforeHost..])
    } else {
        // Inline file or url ends with '/'
        base.join(Uuid::new_v4().to_string())
    };

    debug!("Unminified files will be stored in {:?}", path);

    File::create(path)
}

pub(crate) fn unminify_js(script: &mut dyn ScriptSource) {
    let Some(unminified_dir) = script.unminified_dir() else {
        return;
    };

    if let Some((mut input, mut output)) = create_temp_files() {
        input.write_all(script.extract_bytes()).unwrap();

        if execute_js_beautify(
            input.path(),
            output.try_clone().unwrap(),
            BeautifyFileType::Js,
        ) {
            let mut script_content = String::new();
            output.seek(std::io::SeekFrom::Start(0)).unwrap();
            output.read_to_string(&mut script_content).unwrap();
            script.rewrite_source(Rc::new(DOMString::from(script_content)));
        }
    }

    match create_output_file(unminified_dir, &script.url(), Some(script.is_external())) {
        Ok(mut file) => file.write_all(script.extract_bytes()).unwrap(),
        Err(why) => warn!("Could not store script {:?}", why),
    }
}

pub(crate) fn unminified_path(dir: &str) -> String {
    let mut path = env::current_dir().unwrap();
    path.push(dir);
    path.into_os_string().into_string().unwrap()
}