use crate::errors::Result;
use minifier::js::minify;
use sha3::{Digest, Sha3_256};
use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct JsFile {
hash: String,
content: String,
}
impl JsFile {
pub fn new(filename: &str) -> Result<JsFile> {
let dir = PathBuf::new().join("./src/assets/js");
let path = dir.clone().join(format!("{filename}.js"));
if !is_within_directory(&dir, &path) {
let kind = std::io::ErrorKind::NotFound;
Err(std::io::Error::new(
kind,
"File is not within the specified directory",
))?;
}
let content = std::fs::read_to_string(path)?;
let mut hasher = Sha3_256::new();
hasher.update(content.as_bytes());
let hash = hasher.finalize();
let hex_hash = base16ct::lower::encode_string(&hash);
Ok(JsFile {
hash: hex_hash,
content,
})
}
pub fn contents(&self) -> &str {
&self.content
}
pub fn min_contents(self) -> String {
minify(&self.content).to_string()
}
pub fn verify_hash(&self, hash: &str) -> Result<&Self> {
if !self.hash.starts_with(hash) || hash.is_empty() {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"File Hash Invalid",
))?;
}
Ok(self)
}
}
fn is_within_directory(dir: &Path, filename: &Path) -> bool {
if let (Ok(dir_abs), Ok(filename_abs)) = (dir.canonicalize(), filename.canonicalize()) {
return filename_abs.starts_with(&dir_abs);
}
false
}
pub fn js_path(filename: &str) -> Result<String> {
let file = JsFile::new(filename)?;
let hash = &file.hash[0..10];
Ok(format!("/assets/js/{filename}-{hash}.js"))
}