Skip to main content

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::io::{self, Write};
5use std::net::SocketAddr;
6
7use crate::ffi;
8use crate::ffi::{vrt_ctx, VRT_call, VRT_check_call, VRT_fail, VRT_handled, VRT_CTX_MAGIC};
9use crate::vcl::{
10    sc_to_ptr,
11    subroutine::{Id, Subroutine},
12    Acl, Buffer, HttpHeaders, LogTag, StreamClose, TestWS, VclError, VclResult, Workspace,
13};
14
15/// VCL context
16///
17/// A mutable reference to this structure is always passed to vmod functions and provides access to
18/// the available HTTP objects, as well as the workspace.
19///
20/// This struct is a pure Rust structure, mirroring some of the C fields, so you should always use
21/// the provided methods to interact with them. If they are not enough, the `raw` field is actually
22/// the C original pointer that can be used to directly, and unsafely, act on the structure.
23///
24/// Which `http_*` are present will depend on which VCL sub routine the function is called from.
25///
26/// ``` rust
27/// # mod varnish { pub use varnish_sys::vcl; }
28/// use varnish::vcl::Ctx;
29///
30/// fn foo(ctx: &Ctx) {
31///     if let Some(ref req) = ctx.http_req {
32///         for (name, value) in req {
33///             println!("header {name} has value {value:?}");
34///         }
35///     }
36/// }
37/// ```
38#[derive(Debug)]
39pub struct Ctx<'a> {
40    pub raw: &'a mut vrt_ctx,
41    pub http_req: Option<HttpHeaders<'a>>,
42    pub http_req_top: Option<HttpHeaders<'a>>,
43    pub http_resp: Option<HttpHeaders<'a>>,
44    pub http_bereq: Option<HttpHeaders<'a>>,
45    pub http_beresp: Option<HttpHeaders<'a>>,
46    pub ws: Workspace<'a>,
47
48    req: Option<Req<'a>>,
49}
50
51/// The state of a request or response body, mirroring Varnish's `body_status_t`
52/// (see `tbl/body_status.h`).
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum BodyState {
55    /// No body.
56    None,
57    /// An error occurred while producing/reading the body.
58    Error,
59    /// Chunked transfer encoding; the length isn't known upfront.
60    Chunked,
61    /// A known length (e.g. `Content-Length`).
62    Length,
63    /// The body ends when the connection closes; the length isn't known upfront.
64    Eof,
65    /// The body has already been consumed by someone else.
66    Taken,
67    /// The body has been fully read and is cached as an object.
68    Cached,
69}
70
71impl BodyState {
72    fn from_raw(ptr: ffi::body_status_t) -> Self {
73        unsafe {
74            if ptr == ffi::BS_NONE.as_ptr() {
75                Self::None
76            } else if ptr == ffi::BS_ERROR.as_ptr() {
77                Self::Error
78            } else if ptr == ffi::BS_CHUNKED.as_ptr() {
79                Self::Chunked
80            } else if ptr == ffi::BS_LENGTH.as_ptr() {
81                Self::Length
82            } else if ptr == ffi::BS_EOF.as_ptr() {
83                Self::Eof
84            } else if ptr == ffi::BS_TAKEN.as_ptr() {
85                Self::Taken
86            } else if ptr == ffi::BS_CACHED.as_ptr() {
87                Self::Cached
88            } else {
89                unreachable!("unknown body_status_t")
90            }
91        }
92    }
93}
94
95/// State threaded through Varnish's body-iterate C callback via its `priv_` pointer.
96///
97/// Used by [`Ctx::req_body`].
98struct BodyWriterState<'w, W: Write> {
99    writer: &'w mut W,
100    error: Option<io::Error>,
101}
102
103/// Bridges Varnish's body-iterate callback (`objiterate_f`) to `W::write_all`.
104///
105/// Monomorphized per `W`. `ObjIterate`/`VRB_Iterate` call this synchronously
106/// and sequentially, so at most one `&mut` derived from `priv_` is ever live,
107/// and it never outlives that call. Used by [`Ctx::req_body`].
108///
109/// The `ptr.is_null() || len <= 0` guard below means `W::write_all` (and thus
110/// a caller's own `Write` impl) is never invoked with an empty buffer through
111/// this path — callers of [`Ctx::req_body`] can rely on that when implementing
112/// `W`.
113unsafe extern "C" fn write_body_iterate<W: Write>(
114    priv_: *mut c_void,
115    _flush: c_uint,
116    ptr: *const c_void,
117    len: isize,
118) -> c_int {
119    if ptr.is_null() || len <= 0 {
120        return 0;
121    }
122    let writer_state = priv_
123        .cast::<BodyWriterState<W>>()
124        .as_mut()
125        .expect("body-iterate callback priv pointer must not be null");
126    let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
127    match writer_state.writer.write_all(buf) {
128        Ok(()) => 0,
129        Err(e) => {
130            writer_state.error = Some(e);
131            1
132        }
133    }
134}
135
136/// Resolves a completed `VRB_Iterate` call into [`Ctx::req_body`]'s return value: a
137/// captured writer error always takes priority over the raw C return code (`rv < 0`
138/// signals a stream-side failure instead).
139fn resolve_iterate_result<W: Write>(
140    rv: isize,
141    writer_state: &mut BodyWriterState<'_, W>,
142    stream_err: &'static str,
143) -> VclResult<bool> {
144    if let Some(e) = writer_state.error.take() {
145        return Err(e.to_string().into());
146    }
147    if rv < 0 {
148        return Err(stream_err.into());
149    }
150    Ok(true)
151}
152
153impl<'a> Ctx<'a> {
154    /// Wrap a raw pointer into an object we can use.
155    ///
156    /// The pointer must be non-null, and the magic must match
157    pub unsafe fn from_ptr(ptr: *const vrt_ctx) -> Self {
158        Self::from_ref(
159            ptr.cast_mut()
160                .as_mut()
161                .expect("vrt_ctx pointer must not be null"),
162        )
163    }
164
165    /// Instantiate from a mutable reference to a [`vrt_ctx`].
166    pub fn from_ref(raw: &'a mut vrt_ctx) -> Self {
167        assert_eq!(raw.magic, VRT_CTX_MAGIC);
168        Self {
169            http_req: HttpHeaders::from_ptr(raw.http_req),
170            http_req_top: HttpHeaders::from_ptr(raw.http_req_top),
171            http_resp: HttpHeaders::from_ptr(raw.http_resp),
172            http_bereq: HttpHeaders::from_ptr(raw.http_bereq),
173            http_beresp: HttpHeaders::from_ptr(raw.http_beresp),
174            ws: Workspace::from_ptr(raw.ws),
175            req: unsafe { Req::from_ptr(raw.req) },
176            raw,
177        }
178    }
179
180    /// Log an error message and fail the current VSL task.
181    ///
182    /// Once the control goes back to Varnish, it will see that the transaction was marked as fail
183    /// and will return a synthetic error to the client.
184    pub fn fail(&mut self, msg: impl Into<VclError>) {
185        let msg = msg.into();
186        let msg = msg.as_str();
187        unsafe {
188            VRT_fail(self.raw, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
189        }
190    }
191
192    /// Log a message, attached to the current context
193    pub fn log(&mut self, tag: LogTag, msg: impl AsRef<str>) {
194        unsafe {
195            let vsl = self.raw.vsl;
196            if vsl.is_null() {
197                log(tag, msg);
198            } else {
199                let msg = ffi::txt::from_str(msg.as_ref());
200                ffi::VSLbt(vsl, tag, msg);
201            }
202        }
203    }
204
205    /// Match an ACL against a provided address.
206    pub fn acl_match(&self, acl: &Acl, addr: SocketAddr) -> bool {
207        assert!(!acl.raw.0.is_null());
208
209        unsafe {
210            let mut sa_buf = vec![0u8; ffi::vsa_suckaddr_len];
211            crate::vcl::convert::write_ip_to_buf(addr, &mut sa_buf);
212            ffi::VRT_acl_match(self.raw, acl.raw, ffi::VCL_IP(sa_buf.as_ptr().cast())) == 1
213        }
214    }
215
216    /// Return the name of the listener socket that received the current request.
217    ///
218    /// This corresponds to the VCL variable `local.socket` and returns the `-a` socket
219    /// name (e.g., `"a0"`, `"http-80"`). Returns an `Err` in backend context where the
220    /// session isn't available, or if the name is non-UTF-8.
221    pub fn local_socket(&self) -> Result<&'a str, VclError> {
222        // we're breaking abstraction here, but the other ways are to just reimplement the
223        // whole logic in rust (which is admittedly short), or to let the user crash
224        if self.raw.req.is_null() && self.raw.bo.is_null() {
225            return Err("local.socket isn't available in this context".into());
226        }
227        let raw = unsafe { ffi::VRT_r_local_socket(self.raw) };
228        let cstr = <&CStr>::from(raw);
229        Ok(cstr.to_str()?)
230    }
231
232    /// Return the address of the local endpoint that received the current request.
233    ///
234    /// This corresponds to the VCL variable `local.endpoint` and returns the address
235    /// string (e.g., `"127.0.0.1:8080"`, `"/var/run/varnish.sock"`). Returns an `Err` in
236    /// backend context where the session isn't available, or if the value is non-UTF-8.
237    // same notes as for local_socket
238    pub fn local_endpoint(&self) -> Result<&'a str, VclError> {
239        if self.raw.req.is_null() && self.raw.bo.is_null() {
240            return Err("local.endpoint isn't available in this context".into());
241        }
242        let raw = unsafe { ffi::VRT_r_local_endpoint(self.raw) };
243        let cstr = <&CStr>::from(raw);
244        Ok(cstr.to_str()?)
245    }
246
247    /// Call a VCL subroutine.
248    ///
249    /// Returns `Ok(true)` if the request was handled after the call, `Ok(false)` otherwise.
250    /// Returns `Err` if the subroutine cannot be called in the current context (e.g. wrong VCL
251    /// state or incompatible subroutine type).
252    /// If `Ok(true)` was returned, no other subroutine can be called, and doing so will result
253    /// in a VCL error.
254    pub fn call_sub(&mut self, sub: Subroutine) -> Result<bool, VclError> {
255        self.check_call_sub(sub)?;
256        unsafe { VRT_call(self.raw, sub.vcl_ptr()) };
257        Ok(self.is_handled())
258    }
259
260    /// Check whether a VCL subroutine can be called in the current context.
261    ///
262    /// Returns `Ok(())` if the call is valid, or `Err` with the reason otherwise.
263    pub fn check_call_sub(&self, sub: Subroutine) -> Result<(), VclError> {
264        let result = unsafe { VRT_check_call(self.raw, sub.vcl_ptr()) };
265        if result.0.is_null() {
266            Ok(())
267        } else {
268            let msg = unsafe { CStr::from_ptr(result.0) }
269                .to_string_lossy()
270                .into_owned();
271            Err(VclError::new(msg))
272        }
273    }
274
275    /// Returns `true` if the current request has already been handled.
276    /// If `true`, no other subroutine can be called, and doing so will result
277    /// in a VCL error.
278    pub fn is_handled(&self) -> bool {
279        unsafe { VRT_handled(self.raw) != 0 }
280    }
281
282    /// Return the VCL subroutine currently executing in this context.
283    ///
284    /// # Panics
285    /// Panics if `ctx.raw.method` doesn't match any known subroutine.
286    /// Every real `vrt_ctx` must be stamped with exactly one method bit while a subroutine body runs.
287    pub fn subroutine(&self) -> Id {
288        Id::from_bitfield(self.raw.method)
289            .expect("vrt_ctx.method must match a known VCL subroutine")
290    }
291
292    /// Retrieve the cached request body as a list of byte slices.
293    ///
294    /// Returns slices pointing into the workspace; each slice is a contiguous chunk of the body.
295    /// Fails if the body has not been cached (i.e. `std.cache_req_body()` was not called in VCL
296    /// before this subroutine ran).
297    pub fn cached_req_body(&mut self) -> Result<Vec<&'a [u8]>, VclError> {
298        unsafe extern "C" fn chunk_collector(
299            priv_: *mut c_void,
300            _flush: c_uint,
301            ptr: *const c_void,
302            len: isize,
303        ) -> c_int {
304            let v = priv_
305                .cast::<Vec<&[u8]>>()
306                .as_mut()
307                .expect("cached_req_body callback priv pointer must not be null");
308            let buf = std::slice::from_raw_parts(ptr.cast::<u8>(), len as usize);
309            v.push(buf);
310            0
311        }
312
313        let req = &mut *self.req.as_mut().ok_or("req object isn't available")?.raw;
314        unsafe {
315            if req.req_body_status != ffi::BS_CACHED.as_ptr() {
316                return Err("request body hasn't been previously cached".into());
317            }
318        }
319        let mut v: Box<Vec<&'a [u8]>> = Box::default();
320        let p: *mut Vec<&'a [u8]> = &raw mut *v;
321        match unsafe {
322            ffi::VRB_Iterate(
323                req.wrk,
324                req.vsl.as_mut_ptr(),
325                req,
326                Some(chunk_collector),
327                p.cast::<c_void>(),
328            )
329        } {
330            0 => Ok(*v),
331            _ => Err("req.body iteration failed".into()),
332        }
333    }
334
335    /// Return the current state of the request body — `bereq`'s body from a
336    /// backend context, or the client `req`'s body directly if called earlier
337    /// (`vcl_recv` and later, before any backend is involved).
338    ///
339    /// From backend context (busyobj set - typically
340    /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response)): if
341    /// the body has already been cached as an object (e.g. after
342    /// `std.cache_req_body()`, or on a fetch retry), returns
343    /// [`BodyState::Cached`]; otherwise reflects the live client body's state,
344    /// or [`BodyState::None`] if there is no client request to read from.
345    ///
346    /// From client context (no busyobj yet, e.g. `vcl_recv`/`vcl_hash`): reflects
347    /// `req`'s own state directly - [`BodyState::Cached`] after
348    /// `std.cache_req_body()`, otherwise whatever the live, not-yet-consumed
349    /// client body's state is.
350    pub fn req_body_state(&self) -> VclResult<BodyState> {
351        if let Some(bo) = unsafe { self.raw.bo.as_ref() } {
352            // mirrors V1F_SendReq's `AZ(bo->req)` in its `bo->bereq_body != NULL`
353            // branch: a live `bo.req` and an already-cached `bo.bereq_body` are
354            // normally mutually exclusive by the time a backend runs, but prefer
355            // the cached body rather than assert the invariant, so an unexpected
356            // combination degrades gracefully instead of panicking the worker.
357            if !bo.bereq_body.is_null() {
358                return Ok(BodyState::Cached);
359            }
360            if !bo.req.is_null() {
361                return Ok(BodyState::from_raw(unsafe { (*bo.req).req_body_status }));
362            }
363            return Ok(BodyState::None);
364        }
365        if let Some(req) = self.req.as_ref() {
366            return Ok(BodyState::from_raw(req.raw.req_body_status));
367        }
368        Err("req.body/bereq.body isn't available in this context".into())
369    }
370
371    /// Copy the request body into `writer` — `bereq`'s body from a backend
372    /// context, or the client `req`'s body directly if called earlier
373    /// (`vcl_recv` and later, before any backend is involved).
374    ///
375    /// From backend context (busyobj set - typically
376    /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response)):
377    /// transparently handles both a body already cached as an object (e.g.
378    /// after `std.cache_req_body()`, or on a fetch retry) and a body streamed
379    /// live from the client, hiding the underlying `ObjIterate`/`VRB_Iterate`
380    /// choice and bookkeeping.
381    ///
382    /// From client context (no busyobj yet, e.g. `vcl_recv`/`vcl_hash`): reads
383    /// `req`'s body directly, same `ObjIterate`/`VRB_Iterate` machinery, minus
384    /// the busyobj-specific `no_retry`/`doclose` bookkeeping (there's no fetch
385    /// yet to retry or close).
386    ///
387    /// **Read-once tradeoff**: per Varnish's own rule, an uncached body can be
388    /// read exactly once - either by you here, or later by whatever backend
389    /// ends up handling this request (a custom
390    /// [`VclBackend::get_response`](crate::vcl::VclBackend::get_response),
391    /// or a plain upstream backend forwarding it). Read it once in `vcl_recv`
392    /// without caching first, and that *later* read fails
393    /// (`BodyState::Taken`/an error), not this one. Call `std.cache_req_body()`
394    /// before reading if the body needs to survive for a backend (or a retry)
395    /// to read too - check first if you're not sure:
396    ///
397    /// ```
398    /// # mod varnish { pub use varnish_sys::vcl; }
399    /// # use varnish::vcl::{Ctx, BodyState};
400    /// # fn f(ctx: &mut Ctx) -> Result<(), Box<dyn std::error::Error>> {
401    /// match ctx.req_body_state()? {
402    ///     BodyState::Cached => {
403    ///         // safe: a cached body can be read here and still be read again
404    ///         // later (by a backend, or after a retry).
405    ///         let mut buf = Vec::new();
406    ///         ctx.req_body(&mut buf)?;
407    ///     }
408    ///     BodyState::None => {
409    ///         // no body at all - nothing to read, nothing to worry about.
410    ///     }
411    ///     BodyState::Length | BodyState::Chunked | BodyState::Eof => {
412    ///         // live and not cached: reading now consumes it. Only do this if
413    ///         // you're sure no backend/retry downstream also needs it, or call
414    ///         // `std.cache_req_body()` first if they might.
415    ///     }
416    ///     BodyState::Taken | BodyState::Error => {
417    ///         // already gone (consumed elsewhere) or failed - nothing left to read.
418    ///     }
419    /// }
420    /// # Ok(()) }
421    /// ```
422    ///
423    /// To consume the body without keeping it, pass [`std::io::sink()`] as `writer`.
424    ///
425    /// `writer` is only ever fed non-empty chunks — a custom `Write` impl
426    /// doesn't need to handle a zero-length `buf` in its `write`.
427    ///
428    /// Returns `Ok(false)` without touching `writer` if there is no body at all.
429    /// Returns `Ok(true)` once the full body has been copied into `writer`.
430    /// Returns `Err(_)` if called outside both a backend and a client context,
431    /// if the body stream itself failed to read, or if `writer` errors (the
432    /// `io::Error` is captured and turned into a [`VclError`]). Mirroring
433    /// upstream's `V1F_SendReq`, a failure while iterating an already-cached
434    /// body is not treated as fatal on its own (only a `writer` error is).
435    ///
436    /// Side effect (backend context only): if the body isn't already cached,
437    /// reading it marks the fetch as non-retryable (`bo.no_retry`), mirroring
438    /// `V1F_SendReq` — call `std.cache_req_body()` in `vcl_recv` first if the
439    /// backend may need to retry after reading the body.
440    pub fn req_body<W: Write>(&mut self, writer: &mut W) -> VclResult<bool> {
441        let state = self.req_body_state()?;
442        if state == BodyState::None {
443            return Ok(false);
444        }
445
446        if self.raw.bo.is_null() {
447            // client context: no busyobj yet, so none of the backend-only
448            // no_retry/doclose/err_code bookkeeping below applies - there's no
449            // fetch yet to retry or close. Read the read-once tradeoff warning
450            // above before relying on this branch.
451            let req = &mut *self
452                .req
453                .as_mut()
454                .ok_or("req.body/bereq.body isn't available in this context")?
455                .raw;
456            let mut writer_state = BodyWriterState {
457                writer,
458                error: None,
459            };
460            let state_ptr: *mut BodyWriterState<W> = &raw mut writer_state;
461            let rv = unsafe {
462                ffi::VRB_Iterate(
463                    req.wrk,
464                    req.vsl.as_mut_ptr(),
465                    req,
466                    Some(write_body_iterate::<W>),
467                    state_ptr.cast::<c_void>(),
468                )
469            };
470            return resolve_iterate_result(rv, &mut writer_state, "req.body read error");
471        }
472
473        let bo = unsafe { self.raw.bo.as_mut() }
474            .ok_or("bereq.body isn't available in this context (not a backend fetch)")?;
475
476        let mut writer_state = BodyWriterState {
477            writer,
478            error: None,
479        };
480        let state_ptr: *mut BodyWriterState<W> = &raw mut writer_state;
481
482        if state == BodyState::Cached {
483            // a previously-cached bereq.body (e.g. via std.cache_req_body(), or a
484            // retried fetch) takes priority, mirroring V1F_SendReq. Its return
485            // value is deliberately ignored, same as upstream's `(void)ObjIterate(...)`
486            // in V1F_SendReq: it conflates "callback stopped iteration" with an
487            // internal storage error, and upstream never treats it as fatal here.
488            // Untested: a failing cached-storage iteration isn't easily forced
489            // from VCL, so this specific claim rests on reading V1F_SendReq, not
490            // on a `.vtc` reproduction.
491            unsafe {
492                ffi::ObjIterate(
493                    bo.wrk,
494                    bo.bereq_body,
495                    state_ptr.cast::<c_void>(),
496                    Some(write_body_iterate::<W>),
497                    0,
498                );
499            }
500            if let Some(e) = writer_state.error.take() {
501                return Err(e.to_string().into());
502            }
503            Ok(true)
504        } else {
505            let rv = unsafe {
506                ffi::VRB_Iterate(
507                    bo.wrk,
508                    bo.vsl.as_mut_ptr(),
509                    bo.req,
510                    Some(write_body_iterate::<W>),
511                    state_ptr.cast::<c_void>(),
512                )
513            };
514
515            // bookkeeping mirrored from V1F_SendReq: needed regardless of whether
516            // the writer itself failed, since it reflects the now-(partially-)
517            // consumed client body stream.
518            let req = unsafe { &mut *bo.req };
519            unsafe {
520                if req.req_body_status != ffi::BS_CACHED.as_ptr() {
521                    bo.no_retry = c"bereq.body not cached".as_ptr();
522                }
523                // Only treat this as an upstream RX-body failure if the *writer*
524                // didn't cause it: mirroring vrb_pull, any early `func()` failure
525                // (including ours, from a `writer` error) also leaves the C side's
526                // `req_body_status` as `BS_ERROR` unless it happened to coincide
527                // with the last chunk read off the wire — so without this guard, a
528                // writer error on a multi-chunk body would incorrectly get flagged
529                // as a client-stream failure (`doclose`/400) here, even though the
530                // writer's own `Err` below already reports it correctly.
531                if writer_state.error.is_none() && req.req_body_status == ffi::BS_ERROR.as_ptr() {
532                    req.doclose = sc_to_ptr(StreamClose::RxBody);
533                    bo.err_code = 400;
534                }
535            }
536
537            resolve_iterate_result(rv, &mut writer_state, "bereq.body (streamed) read error")
538        }
539    }
540
541    /// Return a shared reference to the client request object, if present.
542    ///
543    /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
544    pub fn req(&self) -> Option<&Req<'_>> {
545        self.req.as_ref()
546    }
547
548    /// Return a mutable reference to the client request object, if present.
549    ///
550    /// Returns `None` outside of client-facing VCL contexts (e.g. in backend subroutines).
551    pub fn req_mut(&mut self) -> Option<&mut Req<'a>> {
552        self.req.as_mut()
553    }
554
555    /// Return the VSB used to build the response body, so it can be written
556    /// to directly (e.g. via [`Buffer::write`]).
557    ///
558    /// Valid when called from `vcl_synth` or `vcl_backend_error` — per
559    /// `vrt.h`'s comment on `specific` (`synth+error: struct vsb *`), those
560    /// are the only two subroutines where Varnish points `ctx.specific` at a
561    /// `struct vsb *`: building a synthetic response body (`vcl_synth`) or a
562    /// backend error page (`vcl_backend_error`).
563    ///
564    /// Returns `Err` if called from any other subroutine.
565    ///
566    /// # Panics
567    /// Panics if `specific` is null or isn't a VSB even though the
568    /// subroutine check passed — that would mean the crate's understanding
569    /// of Varnish's contract is wrong, not a recoverable misuse by the caller.
570    pub fn response_buffer(&mut self) -> VclResult<Buffer<'_>> {
571        match self.subroutine() {
572            Id::Synth | Id::BackendError => {
573                Ok(Buffer::from_ptr(self.raw.specific.cast::<ffi::vsb>()))
574            }
575            _ => Err(
576                "ctx.response_buffer() is only available in vcl_synth or vcl_backend_error".into(),
577            ),
578        }
579    }
580}
581
582/// Rust proxy for the C `req` struct.
583/// Its methods provide getters and setters for various fields that control how the client request
584/// is processed by Varnish.
585#[derive(Debug)]
586pub struct Req<'a> {
587    raw: &'a mut ffi::req,
588}
589
590impl Req<'_> {
591    /// Wrap a raw pointer into an object we can use.
592    pub(crate) unsafe fn from_ptr(p: *mut ffi::req) -> Option<Self> {
593        Some(Req { raw: p.as_mut()? })
594    }
595
596    /// Return whether this request bypasses the cache lookup and is always treated as a miss.
597    ///
598    /// Equivalent to `req.hash_always_miss` in VCL.
599    pub fn hash_always_miss(&self) -> bool {
600        self.raw.hash_always_miss() == 1
601    }
602
603    /// Force this request to be treated as a cache miss, skipping any existing cached object.
604    ///
605    /// Equivalent to setting `req.hash_always_miss` in VCL.
606    pub fn set_hash_always_miss(&mut self, val: bool) {
607        self.raw.set_hash_always_miss(val.into());
608    }
609
610    /// Return whether this request ignores busy (locked) cache objects and fetches from the backend instead of waiting.
611    ///
612    /// Equivalent to `req.hash_ignore_busy` in VCL.
613    pub fn hash_ignore_busy(&self) -> bool {
614        self.raw.hash_ignore_busy() == 1
615    }
616
617    /// Make this request skip waiting on busy cache objects and go straight to the backend.
618    ///
619    /// Equivalent to setting `req.hash_ignore_busy` in VCL.
620    pub fn set_hash_ignore_busy(&mut self, val: bool) {
621        self.raw.set_hash_ignore_busy(val.into());
622    }
623
624    /// Return whether this request ignores `Vary` headers during cache lookup.
625    ///
626    /// Equivalent to `req.hash_ignore_vary` in VCL.
627    pub fn hash_ignore_vary(&self) -> bool {
628        self.raw.hash_ignore_vary() == 1
629    }
630
631    /// Make this request ignore `Vary` headers during cache lookup, collapsing all variants into one cache key.
632    ///
633    /// Equivalent to setting `req.hash_ignore_vary` in VCL.
634    pub fn set_hash_ignore_vary(&mut self, val: bool) {
635        self.raw.set_hash_ignore_vary(val.into());
636    }
637}
638
639/// A struct holding both a native [`vrt_ctx`] struct and the space it points to.
640///
641/// As the name implies, this struct mainly exist to facilitate testing and should probably not be
642/// used elsewhere.
643#[derive(Debug)]
644pub struct TestCtx {
645    vrt_ctx: vrt_ctx,
646    test_ws: TestWS,
647}
648
649impl TestCtx {
650    /// Instantiate a [`vrt_ctx`], as well as the workspace (of size `sz`) it links to.
651    pub fn new(sz: usize) -> Self {
652        let mut test_ctx = Self {
653            vrt_ctx: vrt_ctx {
654                magic: VRT_CTX_MAGIC,
655                ..vrt_ctx::default()
656            },
657            test_ws: TestWS::new(sz),
658        };
659        test_ctx.vrt_ctx.ws = test_ctx.test_ws.as_ptr();
660        test_ctx
661    }
662
663    /// Return a [`Ctx`] wrapping this test context, for use in unit tests.
664    pub fn ctx(&mut self) -> Ctx<'_> {
665        Ctx::from_ref(&mut self.vrt_ctx)
666    }
667}
668
669/// Log a message outside of a request context using a VSL tag.
670///
671/// Useful in event handlers or other places where no [`Ctx`] is available.
672pub fn log(tag: LogTag, msg: impl AsRef<str>) {
673    let msg = msg.as_ref();
674    unsafe {
675        let vxids = ffi::vxids::default();
676        ffi::VSL(tag, vxids, c"%.*s".as_ptr(), msg.len(), msg.as_ptr());
677    }
678}
679
680#[cfg(test)]
681mod tests {
682    use super::*;
683
684    #[test]
685    fn ctx_test() {
686        let mut test_ctx = TestCtx::new(100);
687        test_ctx.ctx();
688    }
689
690    #[test]
691    fn subroutine_returns_current_method() {
692        let mut test_ctx = TestCtx::new(100);
693        test_ctx.vrt_ctx.method = Id::Recv.to_bitfield();
694        let ctx = test_ctx.ctx();
695        assert_eq!(ctx.subroutine(), Id::Recv);
696    }
697
698    #[test]
699    fn response_buffer_outside_synth_errs() {
700        let mut test_ctx = TestCtx::new(100);
701        test_ctx.vrt_ctx.method = Id::Recv.to_bitfield();
702        let mut ctx = test_ctx.ctx();
703        assert_eq!(ctx.subroutine(), Id::Recv);
704        assert!(ctx.response_buffer().is_err());
705    }
706
707    #[test]
708    fn response_buffer_in_vcl_synth_ok() {
709        let mut test_ctx = TestCtx::new(100);
710        test_ctx.vrt_ctx.method = Id::Synth.to_bitfield();
711        let mut vsb = unsafe { ffi::VSB_new_auto() };
712        test_ctx.vrt_ctx.specific = vsb.cast::<c_void>();
713
714        let mut ctx = test_ctx.ctx();
715        let mut buf = ctx
716            .response_buffer()
717            .expect("response_buffer should succeed in vcl_synth");
718        assert_eq!(buf.raw.magic, ffi::VSB_MAGIC);
719        buf.write(&"hello").expect("VSB write should succeed");
720
721        unsafe { ffi::VSB_destroy(&raw mut vsb) };
722    }
723}
724
725/// This is an unsafe struct that holds the per-VCL state.
726/// It must be public because it is used by the macro-generated code.
727#[doc(hidden)]
728#[derive(Debug)]
729pub struct PerVclState<T> {
730    #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
731    pub fetch_filters: Vec<Box<ffi::vfp>>,
732    #[expect(clippy::vec_box)] // FIXME: we may want to rethink this
733    pub delivery_filters: Vec<Box<ffi::vdp>>,
734    pub user_data: Option<Box<T>>,
735}
736
737// Implement the default trait that works even when `T` does not impl `Default`.
738impl<T> Default for PerVclState<T> {
739    fn default() -> Self {
740        Self {
741            fetch_filters: Vec::default(),
742            delivery_filters: Vec::default(),
743            user_data: None,
744        }
745    }
746}
747
748impl<T> PerVclState<T> {
749    pub fn get_user_data(&self) -> Option<&T> {
750        self.user_data.as_ref().map(AsRef::as_ref)
751    }
752}