1use std::env;
6use std::fs::{File, create_dir_all};
7use std::io::{Error, ErrorKind, Read, Seek, Write};
8use std::path::{Path, PathBuf};
9use std::process::Command;
10
11use servo_url::ServoUrl;
12use tempfile::NamedTempFile;
13use uuid::Uuid;
14
15pub(crate) trait ScriptSource {
16 fn unminified_dir(&self) -> Option<String>;
17 fn extract_bytes(&self) -> &[u8];
18 fn rewrite_source(&mut self, source: String);
19 fn url(&self) -> ServoUrl;
20 fn is_external(&self) -> bool;
21}
22
23pub(crate) fn create_temp_files() -> Option<(NamedTempFile, File)> {
24 let (input, output) = (NamedTempFile::new(), tempfile::tempfile());
30 if let (Ok(input), Ok(output)) = (input, output) {
31 Some((input, output))
32 } else {
33 log::warn!("Error creating input and output temp files");
34 None
35 }
36}
37
38#[derive(Debug)]
39pub(crate) enum BeautifyFileType {
40 Css,
41 Js,
42}
43
44pub(crate) fn execute_js_beautify(input: &Path, output: File, file_type: BeautifyFileType) -> bool {
45 let mut cmd = Command::new("js-beautify");
46 match file_type {
47 BeautifyFileType::Js => (),
48 BeautifyFileType::Css => {
49 cmd.arg("--type").arg("css");
50 },
51 }
52 match cmd.arg(input).stdout(output).status() {
53 Ok(status) => status.success(),
54 _ => {
55 log::warn!(
56 "Failed to execute js-beautify --type {:?}, Will store unmodified script",
57 file_type
58 );
59 false
60 },
61 }
62}
63
64pub fn create_output_file(
65 unminified_dir: String,
66 url: &ServoUrl,
67 external: Option<bool>,
68) -> Result<File, Error> {
69 let path = PathBuf::from(unminified_dir);
70
71 if url.scheme() == "data" {
72 return Err(Error::new(
73 ErrorKind::InvalidInput,
74 "data URLs cannot be written as unminified files",
75 ));
76 }
77
78 let url_path = &url[url::Position::BeforeHost..url::Position::AfterPath];
82
83 let (base, has_name) = match url.as_str().ends_with('/') {
84 true => (path.join(url_path).as_path().to_owned(), false),
85 false => (path.join(url_path).parent().unwrap().to_owned(), true),
86 };
87
88 create_dir_all(&base)?;
89
90 let path = if external.unwrap_or(true) && has_name {
91 path.join(url_path)
93 } else {
94 base.join(Uuid::new_v4().to_string())
96 };
97
98 debug!("Unminified files will be stored in {:?}", path);
99
100 File::create(path)
101}
102
103pub(crate) fn unminify_js(script: &mut dyn ScriptSource) {
104 let Some(unminified_dir) = script.unminified_dir() else {
105 return;
106 };
107
108 if let Some((mut input, mut output)) = create_temp_files() {
109 input.write_all(script.extract_bytes()).unwrap();
110
111 if execute_js_beautify(
112 input.path(),
113 output.try_clone().unwrap(),
114 BeautifyFileType::Js,
115 ) {
116 let mut script_content = String::new();
117 output.seek(std::io::SeekFrom::Start(0)).unwrap();
118 output.read_to_string(&mut script_content).unwrap();
119 script.rewrite_source(script_content);
120 }
121 }
122
123 match create_output_file(unminified_dir, &script.url(), Some(script.is_external())) {
124 Ok(mut file) => file.write_all(script.extract_bytes()).unwrap(),
125 Err(why) => warn!("Could not store script {:?}", why),
126 }
127}
128
129pub(crate) fn unminified_path(dir: &str) -> String {
130 let mut path = env::current_dir().unwrap();
131 path.push(dir);
132 path.into_os_string().into_string().unwrap()
133}