varnish_sys/vcl/ctx.rs
1//! Expose the Varnish context [`vrt_ctx`] as a Rust object
2//!
3use std::ffi::{c_int, c_uint, c_void, CStr};
4use std::net::SocketAddr;
5
6use crate::ffi;
7use crate::ffi::{vrt_ctx, VRT_call, VRT_check_call, VRT_fail, VRT_handled, VRT_CTX_MAGIC};
8use crate::vcl::{subroutine::Subroutine, Acl, 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 req: Option<Req<'a>>,
44}
45
46impl<'a> Ctx<'a> {
47 /// Wrap a raw pointer into an object we can use.
48 ///
49 /// The pointer must be non-null, and the magic must match
50 pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
51 Self::from_ref(
52 ptr.cast_mut()
53 .as_mut()
54 .expect("vrt_ctx pointer must not be null"),
55 )
56 }
57
58 /// Instantiate from a mutable reference to a [`vrt_ctx`].
59 pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
60 assert_eq!(raw.magic, VRT_CTX_MAGIC);
61 Self {
62 http_req: HttpHeaders::from_ptr(raw.http_req),
63 http_req_top: HttpHeaders::from_ptr(raw.http_req_top),
64 http_resp: HttpHeaders::from_ptr(raw.http_resp),
65 http_bereq: HttpHeaders::from_ptr(raw.http_bereq),
66 http_beresp: HttpHeaders::from_ptr(raw.http_beresp),
67 ws: Workspace::from_ptr(raw.ws),
68 req: unsafe { Req::from_ptr(raw.req) },
69 raw,
70 }
71 }
72
73 /// Log an error message and fail the current VSL task.
74 ///
75 /// Once the control goes back to Varnish, it will see that the transaction was marked as fail
76 /// and will return a synthetic error to the client.
77 pub fn fail(&mut self, msg: impl Into<VclError>) {
78 let msg = msg.into();
79 let msg = msg.as_str();
80 unsafe {
81 VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
82 }
83 }
84
85 /// Log a message, attached to the current context
86 pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
87 unsafe {
88 let vsl = self.raw.vsl;
89 if vsl.is_null() {
90 log(tag, msg);
91 } else {
92 let msg = ffi::txt::from_str(msg.as_ref());
93 ffi::VSLbt(vsl, tag, msg);
94 }
95 }
96 }
97
98 /// Match an ACL against a provided address.
99 pub fn acl_match(&self, acl: &Acl, addr: SocketAddr) -> bool {
100 assert!(!acl.raw.0.is_null());
101
102 unsafe {
103 let mut sa_buf = vec![0u8; ffi::vsa_suckaddr_len];
104 crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
105 ffi::VRT_acl_match(self.raw, acl.raw, ffi::VCL_IP(sa_buf.as_ptr().cast())) == 1
106 }
107 }
108
109 /// Return the name of the listener socket that received the current request.
110 ///
111 /// This corresponds to the VCL variable `local.socket` and returns the `-a` socket
112 /// name (e.g., `"a0"`, `"http-80"`). Returns an `Err` in backend context where the
113 /// session isn't available, or if the name is non-UTF-8.
114 pub fn local_socket(&self) -> Result<&'a str, VclError> {
115 // we're breaking abstraction here, but the other ways are to just reimplement the
116 // whole logic in rust (which is admittedly short), or to let the user crash
117 if self.raw.req.is_null() && self.raw.bo.is_null() {
118 return Err("local.socket isn't available in this context".into());
119 }
120 let raw = unsafe { ffi::VRT_r_local_socket(self.raw) };
121 let cstr = <&CStr>::from(raw);
122 Ok(cstr.to_str()?)
123 }
124
125 /// Return the address of the local endpoint that received the current request.
126 ///
127 /// This corresponds to the VCL variable `local.endpoint` and returns the address
128 /// string (e.g., `"127.0.0.1:8080"`, `"/var/run/varnish.sock"`). Returns an `Err` in
129 /// backend context where the session isn't available, or if the value is non-UTF-8.
130 // same notes as for local_socket
131 pub fn local_endpoint(&self) -> Result<&'a str, VclError> {
132 if self.raw.req.is_null() && self.raw.bo.is_null() {
133 return Err("local.endpoint isn't available in this context".into());
134 }
135 let raw = unsafe { ffi::VRT_r_local_endpoint(self.raw) };
136 let cstr = <&CStr>::from(raw);
137 Ok(cstr.to_str()?)
138 }
139
140 /// Call a VCL subroutine.
141 ///
142 /// Returns `Ok(true)` if the request was handled after the call, `Ok(false)` otherwise.
143 /// Returns `Err` if the subroutine cannot be called in the current context (e.g. wrong VCL
144 /// state or incompatible subroutine type).
145 /// If `Ok(true)` was returned, no other subroutine can be called, and doing so will result
146 /// in a VCL error.
147 pub fn call_sub(&mut self, sub: Subroutine) -> Result<bool, VclError> {
148 self.check_call_sub(sub)?;
149 unsafe { VRT_call(self.raw, sub.vcl_ptr()) };
150 Ok(self.is_handled())
151 }
152
153 /// Check whether a VCL subroutine can be called in the current context.
154 ///
155 /// Returns `Ok(())` if the call is valid, or `Err` with the reason otherwise.
156 pub fn check_call_sub(&self, sub: Subroutine) -> Result<(), VclError> {
157 let result = unsafe { VRT_check_call(self.raw, sub.vcl_ptr()) };
158 if result.0.is_null() {
159 Ok(())
160 } else {
161 let msg = unsafe { CStr::from_ptr(result.0) }
162 .to_string_lossy()
163 .into_owned();
164 Err(VclError::new(msg))
165 }
166 }
167
168 /// Returns `true` if the current request has already been handled.
169 /// If `true`, no other subroutine can be called, and doing so will result
170 /// in a VCL error.
171 pub fn is_handled(&self) -> bool {
172 unsafe { VRT_handled(self.raw) != 0 }
173 }
174
175 /// Retrieve the cached request body as a list of byte slices.
176 ///
177 /// Returns slices pointing into the workspace; each slice is a contiguous chunk of the body.
178 /// Fails if the body has not been cached (i.e. `std.cache_req_body()` was not called in VCL
179 /// before this subroutine ran).
180 pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
181 unsafe extern "C" fn chunk_collector(
182 priv_: *mut c_void,
183 _flush: c_uint,
184 ptr: *const c_void,
185 len: isize,
186 ) -> c_int {
187 let v = priv_
188 .cast::<Vec<&[u8]>>()
189 .as_mut()
190 .expect("cached_req_body callback priv pointer must not be null");
191 let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
192 v.push(buf);
193 0
194 }
195
196 let req = &mut *self.req.as_mut().ok_or("req object isn't available")?.raw;
197 unsafe {
198 if req.req_body_status != ffi::BS_CACHED.as_ptr() {
199 return Err("request body hasn't been previously cached".into());
200 }
201 }
202 let mut v: Box<Vec<&'a [u8]>> = Box::default();
203 let p: *mut Vec<&'a [u8]> = &raw mut *v;
204 match unsafe {
205 ffi::VRB_Iterate(
206 req.wrk,
207 req.vsl.as_mut_ptr(),
208 req,
209 Some(chunk_collector),
210 p.cast::<c_void>(),
211 )
212 } {
213 0 => Ok(*v),
214 _ => Err("req.body iteration failed".into()),
215 }
216 }
217
218 /// Return a shared reference to the client request object, if present.
219 ///
220 /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
221 pub fn req(&self) -> Option<&Req<'_>> {
222 self.req.as_ref()
223 }
224
225 /// Return a mutable reference to the client request object, if present.
226 ///
227 /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
228 pub fn req_mut(&mut self) -> Option<&mut Req<'a>> {
229 self.req.as_mut()
230 }
231}
232
233/// Rust proxy for the C `req` struct.
234/// Its methods provide getters and setters for various fields that control how the client request
235/// is processed by Varnish.
236#[derive(Debug)]
237pub struct Req<'a> {
238 raw: &'a mut ffi::req,
239}
240
241impl Req<'_> {
242 /// Wrap a raw pointer into an object we can use.
243 pub(crate) unsafe fn from_ptr(p: *mut ffi::req) -> Option<Self> {
244 Some(Req { raw: p.as_mut()? })
245 }
246
247 /// Return whether this request bypasses the cache lookup and is always treated as a miss.
248 ///
249 /// Equivalent to `req.hash_always_miss` in VCL.
250 pub fn hash_always_miss(&self) -> bool {
251 self.raw.hash_always_miss() == 1
252 }
253
254 /// Force this request to be treated as a cache miss, skipping any existing cached object.
255 ///
256 /// Equivalent to setting `req.hash_always_miss` in VCL.
257 pub fn set_hash_always_miss(&mut self, val: bool) {
258 self.raw.set_hash_always_miss(val.into());
259 }
260
261 /// Return whether this request ignores busy (locked) cache objects and fetches from the backend instead of waiting.
262 ///
263 /// Equivalent to `req.hash_ignore_busy` in VCL.
264 pub fn hash_ignore_busy(&self) -> bool {
265 self.raw.hash_ignore_busy() == 1
266 }
267
268 /// Make this request skip waiting on busy cache objects and go straight to the backend.
269 ///
270 /// Equivalent to setting `req.hash_ignore_busy` in VCL.
271 pub fn set_hash_ignore_busy(&mut self, val: bool) {
272 self.raw.set_hash_ignore_busy(val.into());
273 }
274
275 /// Return whether this request ignores `Vary` headers during cache lookup.
276 ///
277 /// Equivalent to `req.hash_ignore_vary` in VCL.
278 pub fn hash_ignore_vary(&self) -> bool {
279 self.raw.hash_ignore_vary() == 1
280 }
281
282 /// Make this request ignore `Vary` headers during cache lookup, collapsing all variants into one cache key.
283 ///
284 /// Equivalent to setting `req.hash_ignore_vary` in VCL.
285 pub fn set_hash_ignore_vary(&mut self, val: bool) {
286 self.raw.set_hash_ignore_vary(val.into());
287 }
288}
289
290/// A struct holding both a native [`vrt_ctx`] struct and the space it points to.
291///
292/// As the name implies, this struct mainly exist to facilitate testing and should probably not be
293/// used elsewhere.
294#[derive(Debug)]
295pub struct TestCtx {
296 vrt_ctx: vrt_ctx,
297 test_ws: TestWS,
298}
299
300impl TestCtx {
301 /// Instantiate a [`vrt_ctx`], as well as the workspace (of size `sz`) it links to.
302 pub fn new(sz: usize) -> Self {
303 let mut test_ctx = Self {
304 vrt_ctx: vrt_ctx {
305 magic: VRT_CTX_MAGIC,
306 ..vrt_ctx::default()
307 },
308 test_ws: TestWS::new(sz),
309 };
310 test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
311 test_ctx
312 }
313
314 /// Return a [`Ctx`] wrapping this test context, for use in unit tests.
315 pub fn ctx(&mut self) -> Ctx<'_> {
316 Ctx::from_ref(&mut self.vrt_ctx)
317 }
318}
319
320/// Log a message outside of a request context using a VSL tag.
321///
322/// Useful in event handlers or other places where no [`Ctx`] is available.
323pub fn log(tag: LogTag, msg: impl AsRef<str>) {
324 let msg = msg.as_ref();
325 unsafe {
326 let vxids = ffi::vxids::default();
327 ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn ctx_test() {
337 let mut test_ctx = TestCtx::new(100);
338 test_ctx.ctx();
339 }
340}
341
342/// This is an unsafe struct that holds the per-VCL state.
343/// It must be public because it is used by the macro-generated code.
344#[doc(hidden)]
345#[derive(Debug)]
346pub struct PerVclState<T> {
347 #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
348 pub fetch_filters: Vec<Box<ffi::vfp>>,
349 #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
350 pub delivery_filters: Vec<Box<ffi::vdp>>,
351 pub user_data: Option<Box<T>>,
352}
353
354// Implement the default trait that works even when `T` does not impl `Default`.
355impl<T> Default for PerVclState<T> {
356 fn default() -> Self {
357 Self {
358 fetch_filters: Vec::default(),
359 delivery_filters: Vec::default(),
360 user_data: None,
361 }
362 }
363}
364
365impl<T> PerVclState<T> {
366 pub fn get_user_data(&self) -> Option<&T> {
367 self.user_data.as_ref().map(AsRef::as_ref)
368 }
369}