neutralts/bif/
parse_bif_obj.rs

1#![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    /*
14        {:obj; ...  :}
15        {:obj; {:flags; inline :} --- >>  <div>...</div>  :}
16    */
17    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            // json inline
45            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            // For security requires {:allow;
54            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                    // Only "params" needs recursion.
237                    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;