ripht_php_sapi/execution/
context.rs

1use std::ffi::CString;
2use std::fmt;
3use std::path::PathBuf;
4
5use crate::sapi::ServerVars;
6use crate::ExecutionError;
7
8/// Parameters for PHP script execution.
9///
10/// Use the builder methods to configure the script path, server variables,
11/// POST body, environment variables, and INI overrides.
12#[derive(Debug, Clone)]
13pub struct ExecutionContext {
14    pub input: Vec<u8>,
15    pub script_path: PathBuf,
16    pub server_vars: ServerVars,
17    pub env_vars: Vec<(String, String)>,
18    pub ini_overrides: Vec<(String, String)>,
19    pub log_to_stderr: bool,
20}
21
22impl ExecutionContext {
23    pub fn script(path: impl Into<PathBuf>) -> Self {
24        Self {
25            input: Vec::new(),
26            script_path: path.into(),
27            server_vars: ServerVars::new(),
28            env_vars: Vec::new(),
29            ini_overrides: Vec::new(),
30            log_to_stderr: false,
31        }
32    }
33
34    pub fn var(
35        mut self,
36        key: impl Into<String>,
37        value: impl Into<String>,
38    ) -> Self {
39        self.server_vars
40            .set(key, value);
41        self
42    }
43
44    pub fn vars<I, K, V>(mut self, iter: I) -> Self
45    where
46        I: IntoIterator<Item = (K, V)>,
47        K: Into<String>,
48        V: Into<String>,
49    {
50        self.server_vars.extend(iter);
51        self
52    }
53
54    pub fn input(mut self, bytes: impl Into<Vec<u8>>) -> Self {
55        self.input = bytes.into();
56        self
57    }
58
59    pub fn env(
60        mut self,
61        key: impl Into<String>,
62        value: impl Into<String>,
63    ) -> Self {
64        self.env_vars
65            .push((key.into(), value.into()));
66        self
67    }
68
69    pub fn envs<I, K, V>(mut self, iter: I) -> Self
70    where
71        I: IntoIterator<Item = (K, V)>,
72        K: Into<String>,
73        V: Into<String>,
74    {
75        self.env_vars.extend(
76            iter.into_iter()
77                .map(|(k, v)| (k.into(), v.into())),
78        );
79        self
80    }
81
82    pub fn ini(
83        mut self,
84        key: impl Into<String>,
85        value: impl Into<String>,
86    ) -> Self {
87        self.ini_overrides
88            .push((key.into(), value.into()));
89        self
90    }
91
92    pub fn path_as_cstring(&self) -> Result<CString, ExecutionError> {
93        let path_str = self
94            .script_path
95            .to_string_lossy();
96        CString::new(path_str.as_bytes()).map_err(|_| {
97            ExecutionError::InvalidPath("Path contains null byte".to_string())
98        })
99    }
100}
101
102impl fmt::Display for ExecutionContext {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        writeln!(f, "ExecutionContext {{")?;
105        writeln!(f, "  script: {}", self.script_path.display())?;
106
107        let var_count = self.server_vars.len();
108        if var_count == 0 {
109            writeln!(f, "  server_vars: []")?;
110        } else {
111            writeln!(f, "  server_vars: [")?;
112
113            let display_count = var_count.min(15);
114            for (key, value) in self
115                .server_vars
116                .iter()
117                .take(display_count)
118            {
119                let escaped_value = escape_control_chars(value);
120                let truncated = if escaped_value.len() > 60 {
121                    format!("{}...", &escaped_value[..57])
122                } else {
123                    escaped_value
124                };
125                writeln!(f, "    {} = \"{}\"", key, truncated)?;
126            }
127
128            if var_count > display_count {
129                writeln!(f, "    ... ({} more)", var_count - display_count)?;
130            }
131            writeln!(f, "  ]")?;
132        }
133
134        writeln!(f, "  input: {} bytes", self.input.len())?;
135        write!(f, "}}")
136    }
137}
138
139fn escape_control_chars(s: &str) -> String {
140    let mut result = String::with_capacity(s.len());
141    for c in s.chars() {
142        if c.is_control() && c != '\t' && c != '\n' {
143            result.push_str(&format!("\\x{:02x}", c as u32));
144        } else {
145            result.push(c);
146        }
147    }
148    result
149}