neutralts/bif/
parse_bif_cache.rs1#![doc = include_str!("../../doc/bif-cache.md")]
2
3use crate::{bif::constants::*, bif::Bif, bif::BifError, constants::*, utils::*};
4use md5::Digest;
5use sha2::Sha256;
6use std::fs;
7use std::fs::File;
8use std::io::Write;
9use std::path::Path;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12impl<'a> Bif<'a> {
13 pub(crate) fn parse_bif_cache(&mut self) -> Result<(), BifError> {
20 if self.mod_filter || self.mod_scope {
21 return Err(self.bif_error(BIF_ERROR_MODIFIER_NOT_ALLOWED));
22 }
23
24 self.extract_params_code(false);
25
26 if self.params.contains("{:flg;") {
27 return Err(self.bif_error(BIF_ERROR_FLAGS_NOT_ALLOWED));
28 }
29
30 if self.mod_negate {
31 if self.inherit.in_cache {
32 self.out = self.raw.to_string();
33 } else {
34 self.out = new_child_parse!(self, &self.code, self.mod_scope);
36 }
37 return Ok(());
38 }
39
40 let restore_in_cache = self.inherit.in_cache;
41 let context = &self.shared.schema["data"]["CONTEXT"];
42 let has_post = !is_empty_key(context, "POST");
43 let has_get = !is_empty_key(context, "GET");
44 let has_cookies = !is_empty_key(context, "COOKIES");
45
46 if self.shared.cache_disable
47 || (has_post && !self.shared.cache_on_post)
48 || (has_get && !self.shared.cache_on_get)
49 || (has_cookies && !self.shared.cache_on_cookies)
50 {
51 if self.code.contains(BIF_OPEN) {
52 self.code = new_child_parse!(self, &self.code, self.mod_scope);
53 }
54 self.out = self.code.clone();
55 return Ok(());
56 }
57
58 self.inherit.in_cache = true;
59 let args = self.extract_args();
60 self.inherit.in_cache = restore_in_cache;
61
62 let expires = args
64 .get(1)
65 .cloned()
66 .ok_or_else(|| self.bif_error("arguments 'expires' not found"))?;
67
68 let mut id = args.get(2).cloned().unwrap_or("".to_string());
70
71 let only_custom_id: bool = match args.get(3) {
73 Some(value) => !matches!(value.as_str(), "false" | "0" | ""),
74 None => false,
75 };
76
77 if !only_custom_id {
78 id.push_str(&self.shared.lang);
79 id.push_str(&expires);
80 if has_post {
81 id.push_str(
82 &serde_json::to_string(&self.shared.schema["data"]["CONTEXT"]["POST"]).unwrap(),
83 );
84 }
85 if has_get {
86 id.push_str(
87 &serde_json::to_string(&self.shared.schema["data"]["CONTEXT"]["GET"]).unwrap(),
88 );
89 }
90 if has_cookies {
91 id.push_str(
92 &serde_json::to_string(&self.shared.schema["data"]["CONTEXT"]["COOKIES"])
93 .unwrap(),
94 );
95 }
96 id.push_str(&self.get_data("CONTEXT->HOST"));
97 id.push_str(&self.get_data("CONTEXT->ROUTE"));
98 id.push_str(&self.code);
99 }
100
101 let mut hasher = Sha256::new();
102 hasher.update(id.clone());
103 let cache_id = format!("{:x}", hasher.finalize());
104 let cache_dir = self.get_cache_dir(&cache_id);
105 let file = format!("{}/{}-{}", cache_dir, &cache_id, expires);
106 let file_path = Path::new(&file);
107
108 if file_path.exists()
109 && !self.cache_file_expires(file_path, expires.parse::<u64>().unwrap_or(0))
110 {
111 if let Ok(content) = fs::read_to_string(file_path) {
112 self.out = content;
113 } else {
114 if self.code.contains(BIF_OPEN) {
116 self.inherit.in_cache = true;
117 self.out = new_child_parse!(self, &self.code, self.mod_scope);
118 self.inherit.in_cache = restore_in_cache;
119 }
120 return Err(
121 self.bif_error(&format!("Failed to read cache {}", file_path.display()))
122 );
123 }
124 } else {
125 if self.code.contains(BIF_OPEN) {
126 self.inherit.in_cache = true;
127 self.code = new_child_parse!(self, &self.code, self.mod_scope);
128 self.inherit.in_cache = restore_in_cache;
129 }
130
131 self.out = self.code.clone();
133
134 self.set_cache_dir(&cache_dir)?;
136
137 match File::create(&file_path) {
139 Ok(mut file) => {
140 if let Err(e) = file.write_all(&self.code.as_bytes()) {
141 return Err(self.bif_error(&format!(
142 "Failed to write to cache {}: {}",
143 file_path.display(),
144 e.to_string()
145 )));
146 }
147 }
148 Err(e) => {
149 return Err(self.bif_error(&format!(
150 "Failed to create file {}: {}",
151 file_path.display(),
152 e.to_string()
153 )))
154 }
155 }
156 }
157
158 Ok(())
159 }
160
161 pub(crate) fn cache_file_expires(&self, file_path: &Path, expires: u64) -> bool {
162 let now: u64 = SystemTime::now()
163 .duration_since(UNIX_EPOCH)
164 .unwrap()
165 .as_secs();
166
167 let metadata = match fs::metadata(file_path) {
168 Ok(meta) => meta,
169 Err(_) => return true,
170 };
171
172 let modified_time = match metadata.modified() {
173 Ok(time) => time,
174 Err(_) => return true,
175 };
176
177 let duration_since_epoch = match modified_time.duration_since(UNIX_EPOCH) {
178 Ok(duration) => duration,
179 Err(_) => return true,
180 };
181
182 let file_modified_time = duration_since_epoch.as_secs();
183 let expiration_time = file_modified_time + expires;
184
185 if now > expiration_time {
186 return true;
187 }
188
189 false
190 }
191
192 pub(crate) fn set_cache_dir(&self, cache_dir: &str) -> Result<(), BifError> {
193 let cache_dir_levels = Path::new(&cache_dir);
194
195 match fs::create_dir_all(cache_dir_levels) {
196 Ok(_) => Ok(()),
197 Err(e) => {
198 return Err(self.bif_error(&format!(
199 "Failed to create cache directory {}: {}",
200 cache_dir,
201 e.to_string()
202 )))
203 }
204 }
205 }
206
207 pub(crate) fn get_cache_dir(&self, file: &str) -> String {
208 let mut cache_dir = self.shared.cache_dir.clone();
209
210 if !self.shared.cache_prefix.is_empty() {
211 cache_dir.push_str("/");
212 cache_dir.push_str(&self.shared.cache_prefix);
213 }
214
215 cache_dir.push_str("/");
216 cache_dir.push_str(&file[0..3]);
217
218 cache_dir.to_string()
219 }
220}
221
222#[cfg(test)]
223#[path = "parse_bif_cache_tests.rs"]
224mod tests;