1use crate::errors::Result;
2use minifier::js::minify;
3use sha3::{Digest, Sha3_256};
4use std::path::{Path, PathBuf};
5
6#[derive(Clone)]
7
8pub struct JsFile {
13 hash: String,
14 content: String,
15}
16
17impl JsFile {
18 pub fn new(filename: &str) -> Result<JsFile> {
19 let dir = PathBuf::new().join("./src/assets/js");
20 let path = dir.clone().join(format!("{filename}.js"));
21
22 if !is_within_directory(&dir, &path) {
24 let kind = std::io::ErrorKind::NotFound;
25 Err(std::io::Error::new(
26 kind,
27 "File is not within the specified directory",
28 ))?;
29 }
30
31 let content = std::fs::read_to_string(path)?;
32
33 let mut hasher = Sha3_256::new();
34 hasher.update(content.as_bytes());
35 let hash = hasher.finalize();
36 let hex_hash = base16ct::lower::encode_string(&hash);
37 Ok(JsFile {
38 hash: hex_hash,
39 content,
40 })
41 }
42
43 pub fn contents(&self) -> &str {
44 &self.content
45 }
46
47 pub fn min_contents(self) -> String {
48 minify(&self.content).to_string()
49 }
50
51 pub fn verify_hash(&self, hash: &str) -> Result<&Self> {
52 if !self.hash.starts_with(hash) || hash.is_empty() {
53 Err(std::io::Error::other("File Hash Invalid"))?;
54 }
55 Ok(self)
56 }
57}
58
59fn is_within_directory(dir: &Path, filename: &Path) -> bool {
60 if let (Ok(dir_abs), Ok(filename_abs)) = (dir.canonicalize(), filename.canonicalize()) {
62 return filename_abs.starts_with(&dir_abs);
64 }
65 false
66}
67
68pub fn js_path(filename: &str) -> Result<String> {
72 js_path_absolute(filename)
73}
74
75pub fn js_path_relative(filename: &str) -> Result<String> {
80 let file = JsFile::new(filename)?;
81 let hash = &file.hash[0..10];
82 Ok(format!("assets/js/{filename}-{hash}.js"))
83}
84
85pub fn js_path_absolute(filename: &str) -> Result<String> {
90 let root = super::app_root();
91 let file = JsFile::new(filename)?;
92 let hash = &file.hash[0..10];
93 Ok(format!("{root}assets/js/{filename}-{hash}.js"))
94}