varnish_sys/vcl/
processor.rs

1//! Varnish has the ability to modify the body of object leaving its cache using delivery
2//! processors, named `VDP` in the C API, and implemented here using the [`DeliveryProcessor`] trait.
3//! Processors are linked together and will read, modify and push data down the delivery pipeline.
4//!
5//! *Note:* The rust wrapper here is pretty thin and the vmod writer will most probably need to have to
6//! deal with the raw Varnish internals.
7
8use std::ffi::{c_int, c_void, CStr};
9use std::ptr;
10
11use crate::ffi::{vdp_ctx, vfp_ctx, vfp_entry, vrt_ctx, VdpAction, VfpStatus};
12use crate::vcl::{Ctx, VclError};
13use crate::{ffi, validate_vfp_ctx, validate_vfp_entry};
14
15/// The return type for [`DeliveryProcessor::push`]
16#[derive(Debug, Copy, Clone)]
17pub enum PushResult {
18    /// Indicates a failure, the pipeline will be stopped with an error
19    Err,
20    /// Nothing special, processing should continue
21    Ok,
22    /// Stop early, without error
23    End,
24}
25
26/// The return type for [`FetchProcessor::pull`]
27#[derive(Debug, Copy, Clone)]
28pub enum PullResult {
29    /// Indicates a failure, the pipeline will be stopped with an error
30    Err,
31    /// Specify how many bytes were written to the buffer, and that the processor is ready for the
32    /// next call
33    Ok(usize),
34    /// The processor is done, and returns how many bytes were treated
35    End(usize),
36}
37
38/// The return type for [`DeliveryProcessor::new`] and [`FetchProcessor::new`]
39#[derive(Debug)]
40pub enum InitResult<T> {
41    Err(VclError),
42    Ok(T),
43    Pass,
44}
45
46/// Describes a Varnish Delivery Processor (VDP)
47pub trait DeliveryProcessor: Sized {
48    /// The name of the processor.
49    fn name() -> &'static CStr;
50    /// Create a new processor, possibly using knowledge from the pipeline, or from the current
51    /// request.
52    fn new(vrt_ctx: &mut Ctx, vdp_ctx: &mut DeliveryProcCtx) -> InitResult<Self>;
53    /// Handle the data buffer from the previous processor. This function generally uses
54    /// [`DeliveryProcCtx::push`] to push data to the next processor.
55    fn push(&mut self, ctx: &mut DeliveryProcCtx, act: VdpAction, buf: &[u8]) -> PushResult;
56}
57
58pub unsafe extern "C" fn gen_vdp_init<T: DeliveryProcessor>(
59    vrt_ctx: *const vrt_ctx,
60    ctx_raw: *mut vdp_ctx,
61    priv_: *mut *mut c_void,
62    #[cfg(varnishsys_7_5_objcore_init)] _oc: *mut ffi::objcore,
63) -> c_int {
64    assert_ne!(priv_, ptr::null_mut());
65    assert_eq!(*priv_, ptr::null_mut());
66    match T::new(
67        &mut Ctx::from_ptr(vrt_ctx),
68        &mut DeliveryProcCtx::from_ptr(ctx_raw),
69    ) {
70        InitResult::Ok(proc) => {
71            *priv_ = Box::into_raw(Box::new(proc)).cast::<c_void>();
72            0
73        }
74        InitResult::Err(_) => -1, // TODO: log error
75        InitResult::Pass => 1,
76    }
77}
78
79pub unsafe extern "C" fn gen_vdp_fini<T: DeliveryProcessor>(
80    _: *mut vdp_ctx,
81    priv_: *mut *mut c_void,
82) -> c_int {
83    if !priv_.is_null() {
84        assert_ne!(*priv_, ptr::null_mut());
85        drop(Box::from_raw((*priv_).cast::<T>()));
86        *priv_ = ptr::null_mut();
87    }
88
89    0
90}
91
92pub unsafe extern "C" fn gen_vdp_push<T: DeliveryProcessor>(
93    ctx_raw: *mut vdp_ctx,
94    act: VdpAction,
95    priv_: *mut *mut c_void,
96    ptr: *const c_void,
97    len: isize,
98) -> c_int {
99    assert_ne!(priv_, ptr::null_mut());
100    assert_ne!(*priv_, ptr::null_mut());
101    if !matches!(act, VdpAction::Null | VdpAction::Flush | VdpAction::End) {
102        return 1; /* TODO: log */
103    }
104
105    let buf = if ptr.is_null() {
106        &[0; 0]
107    } else {
108        std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize)
109    };
110
111    match (*(*priv_).cast::<T>()).push(&mut DeliveryProcCtx::from_ptr(ctx_raw), act, buf) {
112        PushResult::Err => -1, // TODO: log error
113        PushResult::Ok => 0,
114        PushResult::End => 1,
115    }
116}
117
118/// Create a `ffi::vdp` that can be fed to `ffi::VRT_AddVDP`
119pub fn new_vdp<T: DeliveryProcessor>() -> ffi::vdp {
120    ffi::vdp {
121        name: T::name().as_ptr(),
122        init: Some(gen_vdp_init::<T>),
123        bytes: Some(gen_vdp_push::<T>),
124        fini: Some(gen_vdp_fini::<T>),
125        priv1: ptr::null(),
126    }
127}
128
129/// A thin wrapper around a `*mut ffi::vdp_ctx`
130#[derive(Debug)]
131pub struct DeliveryProcCtx<'a> {
132    pub raw: &'a mut vdp_ctx,
133}
134
135impl DeliveryProcCtx<'_> {
136    /// Check the pointer validity and returns the rust equivalent.
137    ///
138    /// # Safety
139    ///
140    /// The caller is in charge of making sure the structure doesn't outlive the pointer.
141    pub(crate) unsafe fn from_ptr(raw: *mut vdp_ctx) -> Self {
142        let raw = raw.as_mut().unwrap();
143        assert_eq!(raw.magic, ffi::VDP_CTX_MAGIC);
144        Self { raw }
145    }
146
147    /// Send buffer down the pipeline
148    pub fn push(&mut self, act: VdpAction, buf: &[u8]) -> PushResult {
149        match unsafe {
150            ffi::VDP_bytes(
151                self.raw,
152                act,
153                buf.as_ptr().cast::<c_void>(),
154                buf.len() as isize,
155            )
156        } {
157            r if r < 0 => PushResult::Err,
158            0 => PushResult::Ok,
159            _ => PushResult::End,
160        }
161    }
162}
163
164/// Describes a Varnish Fetch Processor (VFP)
165pub trait FetchProcessor: Sized {
166    /// The name of the processor.
167    fn name() -> &'static CStr;
168    /// Create a new processor, possibly using knowledge from the pipeline
169    fn new(vrt_ctx: &mut Ctx, vfp_ctx: &mut FetchProcCtx) -> InitResult<Self>;
170    /// Write data into `buf`, generally using `VFP_Suck` to collect data from the previous
171    /// processor.
172    fn pull(&mut self, ctx: &mut FetchProcCtx, buf: &mut [u8]) -> PullResult;
173}
174
175unsafe extern "C" fn wrap_vfp_init<T: FetchProcessor>(
176    vrt_ctx: *const vrt_ctx,
177    ctxp: *mut vfp_ctx,
178    vfep: *mut vfp_entry,
179) -> VfpStatus {
180    let ctx = validate_vfp_ctx(ctxp);
181    let vfe = validate_vfp_entry(vfep);
182    match T::new(
183        &mut Ctx::from_ptr(vrt_ctx),
184        &mut FetchProcCtx::from_ptr(ctx),
185    ) {
186        InitResult::Ok(proc) => {
187            vfe.priv1 = Box::into_raw(Box::new(proc)).cast::<c_void>();
188            VfpStatus::Ok
189        }
190        InitResult::Err(_) => VfpStatus::Error, // TODO: log the error,
191        InitResult::Pass => VfpStatus::End,
192    }
193}
194
195pub unsafe extern "C" fn wrap_vfp_pull<T: FetchProcessor>(
196    ctxp: *mut vfp_ctx,
197    vfep: *mut vfp_entry,
198    ptr: *mut c_void,
199    len: *mut isize,
200) -> VfpStatus {
201    let ctx = validate_vfp_ctx(ctxp);
202    let vfe = validate_vfp_entry(vfep);
203    let buf = if ptr.is_null() {
204        [0; 0].as_mut()
205    } else {
206        std::slice::from_raw_parts_mut(ptr.cast::<u8>(), *len as usize)
207    };
208    let obj = vfe.priv1.cast::<T>().as_mut().unwrap();
209    match obj.pull(&mut FetchProcCtx::from_ptr(ctx), buf) {
210        PullResult::Err => VfpStatus::Error, // TODO: log error
211        PullResult::Ok(l) => {
212            *len = l as isize;
213            VfpStatus::Ok
214        }
215        PullResult::End(l) => {
216            *len = l as isize;
217            VfpStatus::End
218        }
219    }
220}
221
222pub unsafe extern "C" fn wrap_vfp_fini<T: FetchProcessor>(
223    ctxp: *mut vfp_ctx,
224    vfep: *mut vfp_entry,
225) {
226    validate_vfp_ctx(ctxp);
227    let vfe = validate_vfp_entry(vfep);
228    if !vfe.priv1.is_null() {
229        let p = ptr::replace(&mut vfe.priv1, ptr::null_mut());
230        drop(Box::from_raw(p.cast::<T>()));
231    }
232}
233
234/// Create a `ffi::vfp` that can be fed to `ffi::VRT_AddVFP`
235pub fn new_vfp<T: FetchProcessor>() -> ffi::vfp {
236    ffi::vfp {
237        name: T::name().as_ptr(),
238        init: Some(wrap_vfp_init::<T>),
239        pull: Some(wrap_vfp_pull::<T>),
240        fini: Some(wrap_vfp_fini::<T>),
241        priv1: ptr::null(),
242    }
243}
244
245/// A thin wrapper around a `*mut ffi::vfp_ctx`
246#[derive(Debug)]
247pub struct FetchProcCtx<'a> {
248    pub raw: &'a mut vfp_ctx,
249}
250
251impl FetchProcCtx<'_> {
252    /// Check the pointer validity and returns the rust equivalent.
253    ///
254    /// # Safety
255    ///
256    /// The caller is in charge of making sure the structure doesn't outlive the pointer.
257    pub(crate) unsafe fn from_ptr(raw: *mut vfp_ctx) -> Self {
258        Self {
259            raw: validate_vfp_ctx(raw),
260        }
261    }
262
263    /// Pull data from the pipeline
264    pub fn pull(&mut self, buf: &mut [u8]) -> PullResult {
265        let mut len = buf.len() as isize;
266        let max_len = len;
267
268        match unsafe { ffi::VFP_Suck(self.raw, buf.as_ptr() as *mut c_void, &mut len) } {
269            VfpStatus::Ok => {
270                assert!(len <= max_len);
271                assert!(len >= 0);
272                PullResult::Ok(len as usize)
273            }
274            VfpStatus::End => {
275                assert!(len <= max_len);
276                assert!(len >= 0);
277                PullResult::End(len as usize)
278            }
279            VfpStatus::Error => PullResult::Err,
280            VfpStatus::Null => panic!("VFP_Suck() was never supposed to return VFP_NULL!"),
281            // In the future, there might be more enum values, so we should ensure it continues
282            // to compile, but we do want a warning when developing locally to add the new one.
283            #[expect(unreachable_patterns)]
284            n => panic!("unknown VfpStatus {n:?}"),
285        }
286    }
287}
288
289#[derive(Debug)]
290pub struct FetchFilters<'c, 'f> {
291    ctx: &'c vrt_ctx,
292    // The pointer to the box content must be stable.
293    // Storing values directly in the vector might be moved when the vector grows.
294    #[expect(clippy::vec_box)]
295    filters: &'f mut Vec<Box<ffi::vfp>>,
296}
297
298impl<'c, 'f> FetchFilters<'c, 'f> {
299    #[expect(clippy::vec_box)]
300    pub(crate) fn new(ctx: &'c vrt_ctx, filters: &'f mut Vec<Box<ffi::vfp>>) -> Self {
301        Self { ctx, filters }
302    }
303
304    fn find_position<T: FetchProcessor>(&self) -> Option<usize> {
305        let name = T::name().as_ptr();
306        self.filters.iter().position(|f| f.name == name)
307    }
308
309    pub fn register<T: FetchProcessor>(&mut self) -> bool {
310        if self.find_position::<T>().is_none() {
311            let instance = Box::new(new_vfp::<T>());
312            unsafe {
313                ffi::VRT_AddVFP(self.ctx, instance.as_ref());
314            }
315            self.filters.push(instance);
316            true
317        } else {
318            false
319        }
320    }
321
322    pub fn unregister<T: FetchProcessor>(&mut self) -> bool {
323        if let Some(pos) = self.find_position::<T>() {
324            let filter = self.filters.swap_remove(pos);
325            unsafe {
326                ffi::VRT_RemoveVFP(self.ctx, filter.as_ref());
327            }
328            true
329        } else {
330            false
331        }
332    }
333
334    pub fn unregister_all(&mut self) {
335        for filter in self.filters.drain(..) {
336            unsafe { ffi::VRT_RemoveVFP(self.ctx, filter.as_ref()) }
337        }
338    }
339}
340
341#[derive(Debug)]
342pub struct DeliveryFilters<'c, 'f> {
343    ctx: &'c vrt_ctx,
344    // The pointer to the box content must be stable.
345    // Storing values directly in the vector might be moved when the vector grows.
346    #[expect(clippy::vec_box)]
347    filters: &'f mut Vec<Box<ffi::vdp>>,
348}
349
350impl<'c, 'f> DeliveryFilters<'c, 'f> {
351    #[expect(clippy::vec_box)]
352    pub(crate) fn new(ctx: &'c vrt_ctx, filters: &'f mut Vec<Box<ffi::vdp>>) -> Self {
353        Self { ctx, filters }
354    }
355
356    fn find_position<T: DeliveryProcessor>(&self) -> Option<usize> {
357        let name = T::name().as_ptr();
358        self.filters.iter().position(|f| f.name == name)
359    }
360
361    pub fn register<T: DeliveryProcessor>(&mut self) -> bool {
362        if self.find_position::<T>().is_none() {
363            let instance = Box::new(new_vdp::<T>());
364            unsafe {
365                ffi::VRT_AddVDP(self.ctx, instance.as_ref());
366            }
367            self.filters.push(instance);
368            true
369        } else {
370            false
371        }
372    }
373
374    pub fn unregister<T: DeliveryProcessor>(&mut self) -> bool {
375        if let Some(pos) = self.find_position::<T>() {
376            let filter = self.filters.swap_remove(pos);
377            unsafe {
378                ffi::VRT_RemoveVDP(self.ctx, filter.as_ref());
379            }
380            true
381        } else {
382            false
383        }
384    }
385
386    pub fn unregister_all(&mut self) {
387        for filter in self.filters.drain(..) {
388            unsafe { ffi::VRT_RemoveVDP(self.ctx, filter.as_ref()) }
389        }
390    }
391}