ripht_php_sapi/sapi/
server_context.rs

1use std::cell::Cell;
2use std::ffi::CString;
3use std::sync::OnceLock;
4
5use crate::execution::{
6    ExecutionContext, ExecutionMessage, ExecutionResult, ResponseHeader,
7};
8use crate::sapi::ServerVarsCString;
9
10const MIN_BUFFER_SIZE: usize = 4096;
11const DEFAULT_BUFFER_SIZE: usize = 65536;
12
13/// Output buffer configuration. Set via `SAPI_INIT_BUF` and `SAPI_BUF_GROWTH` env vars.
14#[derive(Clone, Copy)]
15struct BufferPolicy {
16    initial_cap: usize,
17    strategy: Growth,
18}
19
20/// Buffer growth strategy when output exceeds capacity.
21#[derive(Clone, Copy)]
22enum Growth {
23    X4,
24    X2,
25    Fixed(usize),
26}
27
28static BUFFER_POLICY: OnceLock<BufferPolicy> = OnceLock::new();
29
30fn buffer_policy() -> &'static BufferPolicy {
31    BUFFER_POLICY.get_or_init(|| {
32        let initial_cap = std::env::var("SAPI_INIT_BUF")
33            .ok()
34            .and_then(|v| v.parse::<usize>().ok())
35            .filter(|&n| n >= MIN_BUFFER_SIZE)
36            .unwrap_or(DEFAULT_BUFFER_SIZE);
37
38        let strategy = match std::env::var("SAPI_BUF_GROWTH")
39            .ok()
40            .as_deref()
41        {
42            Some("x2") | Some("X2") => Growth::X2,
43            Some("fixed32k") => Growth::Fixed(32 * 1024),
44            _ => Growth::X4,
45        };
46
47        BufferPolicy {
48            initial_cap,
49            strategy,
50        }
51    })
52}
53
54type FlushCallback = Box<dyn FnMut()>;
55type OutputCallback = Box<dyn FnMut(&[u8])>;
56
57/// Per-request state for the SAPI.
58///
59/// # Interior Mutability
60///
61/// `status_code` and `post_position` use `Cell` because they're mutated via raw
62/// pointers from FFI callbacks. Cell provides interior mutability without runtime
63/// overhead, making the aliasing pattern well-defined per Rust's memory model.
64pub struct ServerContext {
65    status_code: Cell<u16>,
66    pub post_data: Vec<u8>,
67    post_position: Cell<usize>,
68    pub output_buffer: Vec<u8>,
69    pub messages: Vec<ExecutionMessage>,
70    pub vars: Option<ServerVarsCString>,
71    pub env_vars: Vec<(CString, CString)>,
72    pub ini_overrides: Vec<(CString, CString)>,
73    pub response_headers: Vec<ResponseHeader>,
74    pub output_callback: Option<OutputCallback>,
75    pub flush_callback: Option<FlushCallback>,
76    pub log_to_stderr: bool,
77}
78
79impl Default for ServerContext {
80    fn default() -> Self {
81        Self::new()
82    }
83}
84
85impl ServerContext {
86    pub fn new() -> Self {
87        let policy = buffer_policy();
88
89        Self {
90            post_data: Vec::new(),
91            post_position: Cell::new(0),
92            output_buffer: Vec::with_capacity(policy.initial_cap),
93            status_code: Cell::new(200),
94            messages: Vec::with_capacity(8),
95            vars: None,
96            env_vars: Vec::new(),
97            ini_overrides: Vec::new(),
98            response_headers: Vec::with_capacity(16),
99            output_callback: None,
100            flush_callback: None,
101            log_to_stderr: false,
102        }
103    }
104
105    pub fn status_code(&self) -> u16 {
106        self.status_code.get()
107    }
108
109    pub fn content_type_ptr(&self) -> *const std::ffi::c_char {
110        self.vars
111            .as_ref()
112            .map(|v| v.content_type_ptr())
113            .unwrap_or(std::ptr::null())
114    }
115
116    pub fn query_string_ptr(&self) -> *mut std::ffi::c_char {
117        self.vars
118            .as_ref()
119            .map(|v| v.query_string_ptr())
120            .unwrap_or(std::ptr::null_mut())
121    }
122
123    pub fn cookie_data_ptr(&self) -> *mut std::ffi::c_char {
124        self.vars
125            .as_ref()
126            .map(|v| v.cookie_ptr())
127            .unwrap_or(std::ptr::null_mut())
128    }
129
130    pub fn request_method_ptr(&self) -> *const std::ffi::c_char {
131        self.vars
132            .as_ref()
133            .map(|v| v.request_method_ptr())
134            .unwrap_or(c"GET".as_ptr())
135    }
136
137    pub fn server_vars(&self) -> &[(CString, CString)] {
138        self.vars
139            .as_ref()
140            .map(|v| v.vars.as_slice())
141            .unwrap_or(&[])
142    }
143
144    pub fn read_post(&self, buffer: &mut [u8]) -> usize {
145        if buffer.is_empty() {
146            return 0;
147        }
148
149        let pos = self.post_position.get();
150        let remaining = self
151            .post_data
152            .len()
153            .saturating_sub(pos);
154        let to_copy = remaining.min(buffer.len());
155
156        if to_copy > 0 {
157            let end = pos + to_copy;
158            buffer[..to_copy].copy_from_slice(&self.post_data[pos..end]);
159            self.post_position.set(end);
160        }
161
162        to_copy
163    }
164
165    pub fn write_output(&mut self, data: &[u8]) -> usize {
166        if let Some(ref mut callback) = self.output_callback {
167            callback(data);
168
169            return data.len();
170        }
171
172        let actual_buffer_length = self.output_buffer.capacity();
173        let required_buffer_length = self.output_buffer.len() + data.len();
174
175        if required_buffer_length > actual_buffer_length {
176            let policy = buffer_policy();
177
178            let new_cap = match policy.strategy {
179                Growth::X4 => actual_buffer_length
180                    .saturating_mul(4)
181                    .max(required_buffer_length + policy.initial_cap),
182                Growth::X2 => actual_buffer_length
183                    .saturating_mul(2)
184                    .max(required_buffer_length + policy.initial_cap),
185                Growth::Fixed(step) => {
186                    let mut cap = actual_buffer_length;
187                    while cap < required_buffer_length {
188                        cap = cap.saturating_add(step);
189                    }
190                    cap
191                }
192            };
193
194            self.output_buffer
195                .reserve(new_cap - self.output_buffer.len());
196        }
197
198        self.output_buffer
199            .extend_from_slice(data);
200        data.len()
201    }
202
203    pub fn add_header(&mut self, header: ResponseHeader) {
204        self.response_headers
205            .push(header);
206    }
207
208    pub fn set_status(&self, code: u16) {
209        self.status_code.set(code);
210    }
211
212    pub fn add_message(&mut self, message: ExecutionMessage) {
213        self.messages.push(message);
214    }
215
216    pub fn set_output_callback<F: FnMut(&[u8]) + 'static>(
217        &mut self,
218        callback: F,
219    ) {
220        self.output_callback = Some(Box::new(callback));
221    }
222
223    pub fn set_flush_callback<F: FnMut() + 'static>(&mut self, callback: F) {
224        self.flush_callback = Some(Box::new(callback));
225    }
226
227    pub fn flush(&mut self) {
228        if let Some(ref mut callback) = self.flush_callback {
229            callback();
230        }
231    }
232
233    pub fn get_env(&self, key: &[u8]) -> Option<*const std::ffi::c_char> {
234        self.env_vars
235            .iter()
236            .find(|(k, _)| k.as_bytes() == key)
237            .map(|(_, v)| v.as_ptr())
238    }
239
240    pub fn into_result(self, body: Vec<u8>) -> ExecutionResult {
241        ExecutionResult {
242            status: self.status_code.get(),
243            headers: self.response_headers,
244            body,
245            messages: self.messages,
246        }
247    }
248}
249
250impl From<ExecutionContext> for Box<ServerContext> {
251    fn from(ctx: ExecutionContext) -> Self {
252        let mut server_ctx = Box::new(ServerContext::new());
253
254        server_ctx.post_data = ctx.input;
255        server_ctx.log_to_stderr = ctx.log_to_stderr;
256
257        server_ctx.vars = Some(
258            ctx.server_vars
259                .into_cstring_pairs(),
260        );
261
262        server_ctx.env_vars = ctx
263            .env_vars
264            .into_iter()
265            .filter_map(|(k, v)| {
266                Some((CString::new(k).ok()?, CString::new(v).ok()?))
267            })
268            .collect();
269
270        server_ctx.ini_overrides = ctx
271            .ini_overrides
272            .into_iter()
273            .filter_map(|(k, v)| {
274                Some((CString::new(k).ok()?, CString::new(v).ok()?))
275            })
276            .collect();
277
278        server_ctx
279    }
280}