neutralts/bif/
parse_bif_obj.rs1#![doc = include_str!("../../doc/bif-obj.md")]
2
3use crate::{
4 bif::{constants::*, Bif, BifError, PhpExecutor, PythonExecutor},
5 constants::*,
6 utils::{is_empty_key, resolve_pointer},
7 Value,
8};
9use std::fs;
10use std::path::Path;
11
12impl<'a> Bif<'a> {
13 pub(crate) fn parse_bif_obj(&mut self) -> Result<(), BifError> {
18 if self.mod_filter || self.mod_negate {
19 return Err(self.bif_error(BIF_ERROR_MODIFIER_NOT_ALLOWED));
20 }
21
22 let mut added_bif_code = false;
23 if !self.src.contains(BIF_CODE) {
24 self.src.push_str(BIF_CODE);
25 added_bif_code = true;
26 }
27
28 self.extract_params_code(false);
29
30 if added_bif_code {
31 self.src.truncate(self.src.len() - BIF_CODE.len());
32 }
33
34 if !self.flags.is_empty() {
35 return Err(self.bif_error(BIF_ERROR_FLAGS_NOT_ALLOWED));
36 }
37
38 let obj_raw;
39 if self.params.starts_with('{')
40 && self.params.ends_with('}')
41 && self.params.chars().nth(1) != Some(':')
42 && self.params.chars().nth(self.params.len() - 2) != Some(':')
43 {
44 obj_raw = self.params.clone();
46 } else {
47 if self.params.contains(BIF_OPEN) {
48 self.params = new_child_parse!(self, &self.params, false);
49 }
50
51 self.file_path = self.params.clone();
52
53 if self.file_path.contains(BIF_OPEN) {
55 if !self.contains_allow(&self.file_path) {
56 return Err(self.bif_error(BIF_ERROR_INSECURE_FILE_NAME));
57 }
58 self.file_path = new_child_parse!(self, &self.params, false);
59 }
60
61 if let Some(stripped) = self.file_path.strip_prefix('#') {
62 self.file_path = format!("{}{}", self.inherit.current_dir, stripped);
63 }
64
65 let path = Path::new(&self.file_path);
66 if !path.exists() {
67 return Err(self.bif_error(BIF_ERROR_FILE_NOT_FOUND));
68 }
69
70 obj_raw = fs::read_to_string(&self.file_path)
71 .map_err(|e| self.bif_error(&format!("Failed to read file: {}", e)))?;
72 }
73
74 let mut obj: Value = serde_json::from_str(obj_raw.trim())
75 .map_err(|e| self.bif_error(&format!("Failed to parse JSON: {}", e)))?;
76
77 let engine = obj["engine"]
78 .as_str()
79 .unwrap_or(DEFAULT_OBJ_ENGINE)
80 .to_lowercase();
81 if engine != "python" && engine != "php" {
82 return Err(self.bif_error(BIF_ERROR_ONLY_PYTHON_ENGINE));
83 }
84
85 if !self.flags.contains("|inline|") {
86 self.parse_obj_values(&mut obj, false);
87 }
88
89 let mut file_path_obj = obj["file"].as_str().unwrap_or("").to_string();
90
91 if let Some(stripped) = file_path_obj.strip_prefix('#') {
92 file_path_obj = format!("{}{}", self.inherit.current_dir, stripped);
93 }
94
95 if !Path::new(&file_path_obj).exists() {
96 return Err(self.bif_error(BIF_ERROR_OBJ_FILE_NOT_FOUND));
97 }
98 file_path_obj = fs::canonicalize(&file_path_obj)
99 .map_err(|e| self.bif_error(&format!("Failed to canonicalize obj script: {}", e)))?
100 .to_string_lossy()
101 .into_owned();
102
103 let schema_data = obj
104 .get("schema_data")
105 .and_then(|v| v.as_str())
106 .map(|schema_data_name| {
107 let (schema_root, key_name) = if schema_data_name.starts_with("local::") {
108 (
109 &self.shared.schema["__indir"][&self.inherit.indir]["data"],
110 schema_data_name.strip_prefix("local::").unwrap_or(""),
111 )
112 } else {
113 (&self.shared.schema["data"], schema_data_name)
114 };
115
116 resolve_pointer(schema_root, key_name)
117 .cloned()
118 .unwrap_or(Value::Null)
119 });
120
121 let schema = if obj.get("schema").and_then(|v| v.as_bool()).unwrap_or(false) {
122 Some(&self.shared.schema)
123 } else {
124 None
125 };
126
127 let default_python_venv = self.shared.schema["config"]["obj_python_venv"]
128 .as_str()
129 .unwrap_or("");
130 let default_php_venv = self.shared.schema["config"]["obj_php_venv"]
131 .as_str()
132 .unwrap_or("");
133 let default_php_fpm = self.shared.schema["config"]["obj_php_fpm"]
134 .as_str()
135 .unwrap_or("unix:/run/php/php-fpm.sock");
136
137 let result = if engine == "python" {
138 let mut venv_path = obj["venv"]
139 .as_str()
140 .unwrap_or(default_python_venv)
141 .to_string();
142
143 if !venv_path.is_empty() {
144 if let Some(stripped) = venv_path.strip_prefix('#') {
145 venv_path = format!("{}{}", self.inherit.current_dir, stripped);
146 }
147
148 if !Path::new(&venv_path).exists() {
149 return Err(self.bif_error("venv path does not exist"));
150 }
151 }
152
153 PythonExecutor::exec_py(
154 &file_path_obj,
155 &obj["params"],
156 obj["callback"].as_str().unwrap_or(DEFAULT_OBJ_CALLBACK),
157 schema,
158 schema_data.as_ref(),
159 if venv_path.is_empty() {
160 None
161 } else {
162 Some(venv_path.as_str())
163 },
164 )
165 .map_err(|e| self.bif_error(&e.msg))?
166 } else {
167 let mut php_venv_path = obj["venv"]
168 .as_str()
169 .unwrap_or(default_php_venv)
170 .to_string();
171 if !php_venv_path.is_empty() {
172 if let Some(stripped) = php_venv_path.strip_prefix('#') {
173 php_venv_path = format!("{}{}", self.inherit.current_dir, stripped);
174 }
175 if !Path::new(&php_venv_path).exists() {
176 return Err(self.bif_error("venv path does not exist"));
177 }
178 }
179
180 let mut fpm_endpoint = obj["fpm"].as_str().unwrap_or(default_php_fpm).to_string();
181 if let Some(stripped) = fpm_endpoint.strip_prefix('#') {
182 fpm_endpoint = format!("{}{}", self.inherit.current_dir, stripped);
183 }
184
185 PhpExecutor::exec_php(
186 &file_path_obj,
187 &obj["params"],
188 obj["callback"].as_str().unwrap_or(DEFAULT_OBJ_CALLBACK),
189 schema,
190 schema_data.as_ref(),
191 if php_venv_path.is_empty() {
192 None
193 } else {
194 Some(php_venv_path.as_str())
195 },
196 &fpm_endpoint,
197 )
198 .map_err(|e| self.bif_error(&e.msg))?
199 };
200
201 let mut code = String::new();
202 if !is_empty_key(&result, "data") {
203 let data = serde_json::to_string(&result).unwrap();
204 code = String::from("{:data;{:flg; inline :}>>") + &data + ":}";
205 }
206 if !is_empty_key(&obj, "template") {
207 let template = obj["template"].as_str().unwrap();
208 code = code + "{:include;" + template + ":}";
209 }
210 self.code = code + &self.code.clone();
211
212 if self.code.contains(BIF_OPEN) {
213 self.out = new_child_parse!(self, &self.code, self.mod_scope);
214 } else {
215 self.out = self.code.clone();
216 }
217
218 Ok(())
219 }
220
221 fn parse_obj_values(&mut self, value: &mut Value, is_recursive_call: bool) {
222 if let Value::Object(map) = value {
223 for (key, val) in map.iter_mut() {
224 if key == "file"
225 || key == "template"
226 || key == "venv"
227 || key == "schema_data"
228 || key == "fpm"
229 {
230 if let Value::String(s) = val {
231 if s.contains(BIF_OPEN) {
232 *val = Value::String(new_child_parse!(self, s, false));
233 }
234 }
235 } else if key == "params" || is_recursive_call {
236 if let Value::String(s) = val {
238 if s.contains(BIF_OPEN) {
239 *val = Value::String(new_child_parse!(self, s, false));
240 }
241 } else if let Value::Object(_) = val {
242 self.parse_obj_values(val, true);
243 }
244 }
245 }
246 }
247 }
248}
249
250#[cfg(test)]
251#[path = "parse_bif_obj_tests.rs"]
252mod tests;