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