varnish_sys/vcl/
ctx.rs

1//! Expose the Varnish context [`vrt_ctx`] as a Rust object
2//!
3#[cfg(not(varnishsys_6))]
4use std::ffi::{c_int, c_uint, c_void};
5
6use crate::ffi;
7use crate::ffi::{vrt_ctx, VRT_fail, VRT_CTX_MAGIC};
8use crate::vcl::{HttpHeaders, LogTag, TestWS, VclError, Workspace};
9
10/// VCL context
11///
12/// A mutable reference to this structure is always passed to vmod functions and provides access to
13/// the available HTTP objects, as well as the workspace.
14///
15/// This struct is a pure Rust structure, mirroring some of the C fields, so you should always use
16/// the provided methods to interact with them. If they are not enough, the `raw` field is actually
17/// the C original pointer that can be used to directly, and unsafely, act on the structure.
18///
19/// Which `http_*` are present will depend on which VCL sub routine the function is called from.
20///
21/// ``` rust
22/// # mod varnish { pub use varnish_sys::vcl; }
23/// use varnish::vcl::Ctx;
24///
25/// fn foo(ctx: &Ctx) {
26///     if let Some(ref req) = ctx.http_req {
27///         for (name, value) in req {
28///             println!("header {name} has value {value:?}");
29///         }
30///     }
31/// }
32/// ```
33#[derive(Debug)]
34pub struct Ctx<'a> {
35    pub raw: &'a mut vrt_ctx,
36    pub http_req: Option<HttpHeaders<'a>>,
37    pub http_req_top: Option<HttpHeaders<'a>>,
38    pub http_resp: Option<HttpHeaders<'a>>,
39    pub http_bereq: Option<HttpHeaders<'a>>,
40    pub http_beresp: Option<HttpHeaders<'a>>,
41    pub ws: Workspace<'a>,
42}
43
44impl<'a> Ctx<'a> {
45    /// Wrap a raw pointer into an object we can use.
46    ///
47    /// The pointer must be non-null, and the magic must match
48    pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
49        Self::from_ref(ptr.cast_mut().as_mut().unwrap())
50    }
51
52    /// Instantiate from a mutable reference to a [`vrt_ctx`].
53    #[cfg_attr(not(varnishsys_6), expect(clippy::useless_conversion))] // Varnish v6 has a different struct, requiring .into()
54    pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
55        assert_eq!(raw.magic, VRT_CTX_MAGIC);
56        Self {
57            http_req: HttpHeaders::from_ptr(raw.http_req.into()),
58            http_req_top: HttpHeaders::from_ptr(raw.http_req_top.into()),
59            http_resp: HttpHeaders::from_ptr(raw.http_resp.into()),
60            http_bereq: HttpHeaders::from_ptr(raw.http_bereq.into()),
61            http_beresp: HttpHeaders::from_ptr(raw.http_beresp.into()),
62            ws: Workspace::from_ptr(raw.ws),
63            raw,
64        }
65    }
66
67    /// Log an error message and fail the current VSL task.
68    ///
69    /// Once the control goes back to Varnish, it will see that the transaction was marked as fail
70    /// and will return a synthetic error to the client.
71    pub fn fail(&mut self, msg: impl Into<VclError>) {
72        let msg = msg.into();
73        let msg = msg.as_str();
74        unsafe {
75            VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
76        }
77    }
78
79    /// Log a message, attached to the current context
80    pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
81        unsafe {
82            let vsl = self.raw.vsl;
83            if vsl.is_null() {
84                log(tag, msg);
85            } else {
86                let msg = ffi::txt::from_str(msg.as_ref());
87                ffi::VSLbt(vsl, tag, msg);
88            }
89        }
90    }
91    #[cfg(not(varnishsys_6))]
92    pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
93        unsafe extern "C" fn chunk_collector(
94            priv_: *mut c_void,
95            _flush: c_uint,
96            ptr: *const c_void,
97            len: isize,
98        ) -> c_int {
99            let v = priv_.cast::<Vec<&[u8]>>().as_mut().unwrap();
100            let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
101            v.push(buf);
102            0
103        }
104
105        let req = unsafe { self.raw.req.as_mut().ok_or("req object isn't available")? };
106        unsafe {
107            if req.req_body_status != ffi::BS_CACHED.as_ptr() {
108                return Err("request body hasn't been previously cached".into());
109            }
110        }
111        let mut v: Box<Vec<&'a [u8]>> = Box::default();
112        let p: *mut Vec<&'a [u8]> = &raw mut *v;
113        match unsafe {
114            ffi::VRB_Iterate(
115                req.wrk,
116                req.vsl.as_mut_ptr(),
117                req,
118                Some(chunk_collector),
119                p.cast::<c_void>(),
120            )
121        } {
122            0 => Ok(*v),
123            _ => Err("req.body iteration failed".into()),
124        }
125    }
126}
127
128/// A struct holding both a native [`vrt_ctx`] struct and the space it points to.
129///
130/// As the name implies, this struct mainly exist to facilitate testing and should probably not be
131/// used elsewhere.
132#[derive(Debug)]
133pub struct TestCtx {
134    vrt_ctx: vrt_ctx,
135    test_ws: TestWS,
136}
137
138impl TestCtx {
139    /// Instantiate a [`vrt_ctx`], as well as the workspace (of size `sz`) it links to.
140    pub fn new(sz: usize) -> Self {
141        let mut test_ctx = Self {
142            vrt_ctx: vrt_ctx {
143                magic: VRT_CTX_MAGIC,
144                ..vrt_ctx::default()
145            },
146            test_ws: TestWS::new(sz),
147        };
148        test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
149        test_ctx
150    }
151
152    pub fn ctx(&mut self) -> Ctx<'_> {
153        Ctx::from_ref(&mut self.vrt_ctx)
154    }
155}
156
157pub fn log(tag: LogTag, msg: impl AsRef<str>) {
158    let msg = msg.as_ref();
159    #[cfg(not(varnishsys_6))]
160    unsafe {
161        let vxids = ffi::vxids::default();
162        ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
163    }
164    #[cfg(varnishsys_6)]
165    unsafe {
166        ffi::VSL(tag, 0, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn ctx_test() {
176        let mut test_ctx = TestCtx::new(100);
177        test_ctx.ctx();
178    }
179}
180
181/// This is an unsafe struct that holds the per-VCL state.
182/// It must be public because it is used by the macro-generated code.
183#[doc(hidden)]
184#[derive(Debug)]
185pub struct PerVclState<T> {
186    #[cfg(not(varnishsys_6))]
187    #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
188    pub fetch_filters: Vec<Box<ffi::vfp>>,
189    #[cfg(not(varnishsys_6))]
190    #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
191    pub delivery_filters: Vec<Box<ffi::vdp>>,
192    pub user_data: Option<Box<T>>,
193}
194
195// Implement the default trait that works even when `T` does not impl `Default`.
196impl<T> Default for PerVclState<T> {
197    fn default() -> Self {
198        Self {
199            #[cfg(not(varnishsys_6))]
200            fetch_filters: Vec::default(),
201            #[cfg(not(varnishsys_6))]
202            delivery_filters: Vec::default(),
203            user_data: None,
204        }
205    }
206}
207
208impl<T> PerVclState<T> {
209    pub fn get_user_data(&self) -> Option<&T> {
210        self.user_data.as_ref().map(AsRef::as_ref)
211    }
212}