Skip to main content

varnish_sys/vcl/
http.rs

1//! Headers and top line of an HTTP object
2//!
3//! Depending on the VCL subroutine, the `Ctx` will give access to various [`HttpHeaders`] object which
4//! expose the request line (`req`, `req_top` and `bereq`), response line (`resp`, `beresp`) and
5//! headers of the objects Varnish is manipulating.
6//!
7//! `HTTP` implements `IntoIterator` that will expose the headers only (not the `method`, `status`,
8//! etc.)
9//!
10//! **Note:** at this stage, headers are assumed to be utf8, and you will get a panic if it's not
11//! the case. Future work needs to sanitize the headers to make this safer to use. It is tracked in
12//! this [issue](https://github.com/varnish-rs/varnish-rs/issues/4).
13
14use std::mem::transmute;
15use std::slice::from_raw_parts_mut;
16
17use crate::ffi;
18use crate::ffi::VslTag;
19use crate::vcl::str_or_bytes::StrOrBytes;
20use crate::vcl::{VclResult, Workspace};
21
22// C constants pop up as u32, but header indexing uses u16, redefine
23// some stuff to avoid casting all the time
24const HDR_FIRST: u16 = ffi::HTTP_HDR_FIRST as u16;
25const HDR_METHOD: u16 = ffi::HTTP_HDR_METHOD as u16;
26const HDR_PROTO: u16 = ffi::HTTP_HDR_PROTO as u16;
27const HDR_REASON: u16 = ffi::HTTP_HDR_REASON as u16;
28const HDR_STATUS: u16 = ffi::HTTP_HDR_STATUS as u16;
29const HDR_UNSET: u16 = ffi::HTTP_HDR_UNSET as u16;
30const HDR_URL: u16 = ffi::HTTP_HDR_URL as u16;
31
32/// HTTP headers of an object, wrapping `HTTP` from Varnish
33#[derive(Debug)]
34pub struct HttpHeaders<'a> {
35    pub raw: &'a mut ffi::http,
36}
37
38impl HttpHeaders<'_> {
39    /// Wrap a raw pointer into an object we can use.
40    pub(crate) fn from_ptr(p: ffi::VCL_HTTP) -> Option<Self> {
41        Some(HttpHeaders {
42            raw: unsafe { p.0.as_mut()? },
43        })
44    }
45
46    fn change_header<'a>(&mut self, idx: u16, value: impl Into<StrOrBytes<'a>>) -> VclResult<()> {
47        assert!(idx < self.raw.nhd);
48
49        /* XXX: aliasing warning, it's the same pointer as the one in Ctx */
50        let mut ws = Workspace::from_ptr(self.raw.ws);
51        unsafe {
52            let hd = self
53                .raw
54                .hd
55                .offset(idx as isize)
56                .as_mut()
57                .expect("HTTP header descriptor pointer must not be null");
58            *hd = ws.copy_bytes_with_null(value.into())?;
59            let hdf = self
60                .raw
61                .hdf
62                .offset(idx as isize)
63                .as_mut()
64                .expect("HTTP header flags pointer must not be null");
65            *hdf = 0;
66        }
67        Ok(())
68    }
69
70    fn set_header_raw<'a>(&mut self, raw: impl Into<StrOrBytes<'a>>) -> VclResult<()> {
71        assert!(self.raw.nhd <= self.raw.shd);
72        if self.raw.nhd == self.raw.shd {
73            return Err(c"no more header slot".into());
74        }
75        let idx = self.raw.nhd;
76        self.raw.nhd += 1;
77        let res = self.change_header(idx, raw);
78        if res.is_ok() {
79            unsafe {
80                ffi::VSLbt(
81                    self.raw.vsl,
82                    transmute::<u32, VslTag>((self.raw.logtag as u32) + u32::from(HDR_FIRST)),
83                    *self.raw.hd.add(idx as usize),
84                );
85            }
86        } else {
87            self.raw.nhd -= 1;
88        }
89        res
90    }
91
92    /// Append a new header using `name` and `value`. This can fail if we run out of internal slots
93    /// to store the new header
94    pub fn set_header(&mut self, name: &str, value: &str) -> VclResult<()> {
95        // FIXME: optimize this to avoid allocating a temporary string
96        self.set_header_raw(&format!("{name}: {value}"))
97    }
98
99    /// Remove all headers matching `name` (case-insensitive). No-op if the header is absent.
100    pub fn unset_header(&mut self, name: &str) {
101        let hdrs = unsafe {
102            &from_raw_parts_mut(self.raw.hd, self.raw.nhd as usize)[(HDR_FIRST as usize)..]
103        };
104
105        let mut idx_empty = 0;
106        for (idx, hd) in hdrs.iter().enumerate() {
107            let (n, _) = hd.parse_header().expect("HTTP header must be parseable");
108            if name.eq_ignore_ascii_case(n) {
109                unsafe {
110                    ffi::VSLbt(
111                        self.raw.vsl,
112                        transmute::<u32, VslTag>(
113                            (self.raw.logtag as u32) + u32::from(HDR_UNSET) + u32::from(HDR_METHOD),
114                        ),
115                        *self.raw.hd.add(HDR_FIRST as usize + idx),
116                    );
117                }
118                continue;
119            }
120            if idx != idx_empty {
121                unsafe {
122                    std::ptr::copy_nonoverlapping(
123                        self.raw.hd.add(HDR_FIRST as usize + idx),
124                        self.raw.hd.add(HDR_FIRST as usize + idx_empty),
125                        1,
126                    );
127                    std::ptr::copy_nonoverlapping(
128                        self.raw.hdf.add(HDR_FIRST as usize + idx),
129                        self.raw.hdf.add(HDR_FIRST as usize + idx_empty),
130                        1,
131                    );
132                }
133            }
134            idx_empty += 1;
135        }
136        self.raw.nhd = HDR_FIRST + idx_empty as u16;
137    }
138
139    /// Return header at a specific position
140    fn field(&self, idx: u16) -> Option<StrOrBytes<'_>> {
141        unsafe {
142            if idx >= self.raw.nhd {
143                None
144            } else {
145                self.raw
146                    .hd
147                    .offset(idx as isize)
148                    .as_ref()
149                    .expect("HTTP header pointer must not be null")
150                    .to_slice()
151                    .map(StrOrBytes::from)
152            }
153        }
154    }
155
156    /// Method of an HTTP request, `None` for a response
157    pub fn method(&self) -> Option<StrOrBytes<'_>> {
158        self.field(HDR_METHOD)
159    }
160
161    /// URL of an HTTP request, `None` for a response
162    pub fn url(&self) -> Option<StrOrBytes<'_>> {
163        self.field(HDR_URL)
164    }
165
166    /// Set the URL of this HTTP request.
167    ///
168    /// This updates the URL (path and query) component of the HTTP request line associated
169    /// with this [`HttpHeaders`] object. It is only meaningful for request objects; for responses
170    /// the corresponding [`url`](Self::url) accessor will return `None`.
171    ///
172    /// The new value must fit in the underlying Varnish workspace; otherwise an error is
173    /// returned.
174    ///
175    /// # Examples
176    ///
177    /// ```ignore
178    /// // Change the URL of the current request before it is processed further.
179    /// http.set_url("/new/path?foo=bar")?;
180    /// assert_eq!(http.url().unwrap().as_str(), "/new/path?foo=bar");
181    /// ```
182    pub fn set_url(&mut self, value: &str) -> VclResult<()> {
183        self.change_header(HDR_URL, value)
184    }
185
186    /// Protocol of an object
187    ///
188    /// It should exist for both requests and responses, but the `Option` is maintained for
189    /// consistency.
190    pub fn proto(&self) -> Option<StrOrBytes<'_>> {
191        self.field(HDR_PROTO)
192    }
193
194    /// Set prototype
195    pub fn set_proto(&mut self, value: &str) -> VclResult<()> {
196        self.raw.protover = match value {
197            "HTTP/0.9" => 9,
198            "HTTP/1.0" => 10,
199            "HTTP/1.1" => 11,
200            "HTTP/2.0" => 20,
201            _ => 0,
202        };
203        self.change_header(HDR_PROTO, value)
204    }
205
206    /// Response status, `None` for a request
207    pub fn status(&self) -> Option<StrOrBytes<'_>> {
208        self.field(HDR_STATUS)
209    }
210
211    /// Set the response status, it will also set the reason
212    pub fn set_status(&mut self, status: u16) {
213        unsafe {
214            ffi::http_SetStatus(self.raw, status, std::ptr::null());
215        }
216    }
217
218    /// Response reason, `None` for a request
219    pub fn reason(&self) -> Option<StrOrBytes<'_>> {
220        self.field(HDR_REASON)
221    }
222
223    /// Set reason
224    pub fn set_reason(&mut self, value: &str) -> VclResult<()> {
225        self.change_header(HDR_REASON, value)
226    }
227
228    /// Weaken the `ETag` header if present and not already weak.
229    ///
230    /// Implements [RFC 2616 ยง3.11](https://www.rfc-editor.org/rfc/rfc2616#section-3.11) `ETag`
231    /// weakening: if the `ETag` header exists and does not already start with `W/`, it is
232    /// replaced with `W/<original-value>`.
233    pub fn weaken_etag(&mut self) -> VclResult<()> {
234        let Some(etag) = self.header("ETag") else {
235            return Ok(());
236        };
237        let value = etag.as_ref();
238        if value.starts_with(b"W/") {
239            return Ok(());
240        }
241        let mut new_hdr = Vec::with_capacity(b"ETag: W/".len() + value.len());
242        new_hdr.extend_from_slice(b"ETag: W/");
243        new_hdr.extend_from_slice(value);
244        self.unset_header("ETag");
245        self.set_header_raw(new_hdr.as_slice())
246    }
247
248    /// Returns the value of a header based on its name
249    ///
250    /// The header names are compared in a case-insensitive manner
251    pub fn header(&self, name: &str) -> Option<StrOrBytes<'_>> {
252        self.iter()
253            .find(|hdr| name.eq_ignore_ascii_case(hdr.0))
254            .map(|hdr| hdr.1)
255    }
256
257    /// Iterate over `(name, value)` pairs for all headers, excluding the request/status line.
258    pub fn iter(&self) -> HttpHeadersIter<'_> {
259        HttpHeadersIter {
260            http: self,
261            cursor: HDR_FIRST as isize,
262        }
263    }
264}
265
266impl<'a> IntoIterator for &'a HttpHeaders<'a> {
267    type Item = (&'a str, StrOrBytes<'a>);
268    type IntoIter = HttpHeadersIter<'a>;
269
270    fn into_iter(self) -> Self::IntoIter {
271        self.iter()
272    }
273}
274
275/// Iterator over HTTP header `(name, value)` pairs, returned by [`HttpHeaders::iter`].
276#[derive(Debug)]
277pub struct HttpHeadersIter<'a> {
278    http: &'a HttpHeaders<'a>,
279    cursor: isize,
280}
281
282impl<'a> Iterator for HttpHeadersIter<'a> {
283    type Item = (&'a str, StrOrBytes<'a>);
284
285    fn next(&mut self) -> Option<Self::Item> {
286        loop {
287            let nhd = self.http.raw.nhd;
288            if self.cursor >= nhd as isize {
289                return None;
290            }
291            let hd = unsafe {
292                self.http
293                    .raw
294                    .hd
295                    .offset(self.cursor)
296                    .as_ref()
297                    .expect("HTTP header pointer must not be null")
298            };
299            self.cursor += 1;
300            if let Some(hdr) = hd.parse_header() {
301                return Some(hdr);
302            }
303        }
304    }
305}