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.raw.hd.offset(idx as isize).as_mut().unwrap();
53            *hd = ws.copy_bytes_with_null(value.into())?;
54            let hdf = self.raw.hdf.offset(idx as isize).as_mut().unwrap();
55            *hdf = 0;
56        }
57        Ok(())
58    }
59
60    /// Append a new header using `name` and `value`. This can fail if we run out of internal slots
61    /// to store the new header
62    pub fn set_header(&mut self, name: &str, value: &str) -> VclResult<()> {
63        assert!(self.raw.nhd <= self.raw.shd);
64        if self.raw.nhd == self.raw.shd {
65            return Err(c"no more header slot".into());
66        }
67
68        let idx = self.raw.nhd;
69        self.raw.nhd += 1;
70        // FIXME: optimize this to avoid allocating a temporary string
71        let res = self.change_header(idx, &format!("{name}: {value}"));
72        if res.is_ok() {
73            unsafe {
74                ffi::VSLbt(
75                    self.raw.vsl,
76                    transmute::<u32, VslTag>((self.raw.logtag as u32) + u32::from(HDR_FIRST)),
77                    *self.raw.hd.add(idx as usize),
78                );
79            }
80        } else {
81            self.raw.nhd -= 1;
82        }
83        res
84    }
85
86    pub fn unset_header(&mut self, name: &str) {
87        let hdrs = unsafe {
88            &from_raw_parts_mut(self.raw.hd, self.raw.nhd as usize)[(HDR_FIRST as usize)..]
89        };
90
91        let mut idx_empty = 0;
92        for (idx, hd) in hdrs.iter().enumerate() {
93            let (n, _) = hd.parse_header().unwrap();
94            if name.eq_ignore_ascii_case(n) {
95                unsafe {
96                    ffi::VSLbt(
97                        self.raw.vsl,
98                        transmute::<u32, VslTag>(
99                            (self.raw.logtag as u32) + u32::from(HDR_UNSET) + u32::from(HDR_METHOD),
100                        ),
101                        *self.raw.hd.add(HDR_FIRST as usize + idx),
102                    );
103                }
104                continue;
105            }
106            if idx != idx_empty {
107                unsafe {
108                    std::ptr::copy_nonoverlapping(
109                        self.raw.hd.add(HDR_FIRST as usize + idx),
110                        self.raw.hd.add(HDR_FIRST as usize + idx_empty),
111                        1,
112                    );
113                    std::ptr::copy_nonoverlapping(
114                        self.raw.hdf.add(HDR_FIRST as usize + idx),
115                        self.raw.hdf.add(HDR_FIRST as usize + idx_empty),
116                        1,
117                    );
118                }
119            }
120            idx_empty += 1;
121        }
122        self.raw.nhd = HDR_FIRST + idx_empty as u16;
123    }
124
125    /// Return header at a specific position
126    fn field(&self, idx: u16) -> Option<StrOrBytes<'_>> {
127        unsafe {
128            if idx >= self.raw.nhd {
129                None
130            } else {
131                self.raw
132                    .hd
133                    .offset(idx as isize)
134                    .as_ref()
135                    .unwrap()
136                    .to_slice()
137                    .map(StrOrBytes::from)
138            }
139        }
140    }
141
142    /// Method of an HTTP request, `None` for a response
143    pub fn method(&self) -> Option<StrOrBytes<'_>> {
144        self.field(HDR_METHOD)
145    }
146
147    /// URL of an HTTP request, `None` for a response
148    pub fn url(&self) -> Option<StrOrBytes<'_>> {
149        self.field(HDR_URL)
150    }
151
152    /// Protocol of an object
153    ///
154    /// It should exist for both requests and responses, but the `Option` is maintained for
155    /// consistency.
156    pub fn proto(&self) -> Option<StrOrBytes<'_>> {
157        self.field(HDR_PROTO)
158    }
159
160    /// Set prototype
161    pub fn set_proto(&mut self, value: &str) -> VclResult<()> {
162        self.raw.protover = match value {
163            "HTTP/0.9" => 9,
164            "HTTP/1.0" => 10,
165            "HTTP/1.1" => 11,
166            "HTTP/2.0" => 20,
167            _ => 0,
168        };
169        self.change_header(HDR_PROTO, value)
170    }
171
172    /// Response status, `None` for a request
173    pub fn status(&self) -> Option<StrOrBytes<'_>> {
174        self.field(HDR_STATUS)
175    }
176
177    /// Set the response status, it will also set the reason
178    pub fn set_status(&mut self, status: u16) {
179        unsafe {
180            ffi::http_SetStatus(
181                self.raw,
182                status,
183                #[cfg(not(varnishsys_6))]
184                std::ptr::null(),
185            );
186        }
187    }
188
189    /// Response reason, `None` for a request
190    pub fn reason(&self) -> Option<StrOrBytes<'_>> {
191        self.field(HDR_REASON)
192    }
193
194    /// Set reason
195    pub fn set_reason(&mut self, value: &str) -> VclResult<()> {
196        self.change_header(HDR_REASON, value)
197    }
198
199    /// Returns the value of a header based on its name
200    ///
201    /// The header names are compared in a case-insensitive manner
202    pub fn header(&self, name: &str) -> Option<StrOrBytes<'_>> {
203        self.iter()
204            .find(|hdr| name.eq_ignore_ascii_case(hdr.0))
205            .map(|hdr| hdr.1)
206    }
207
208    pub fn iter(&self) -> HttpHeadersIter<'_> {
209        HttpHeadersIter {
210            http: self,
211            cursor: HDR_FIRST as isize,
212        }
213    }
214}
215
216impl<'a> IntoIterator for &'a HttpHeaders<'a> {
217    type Item = (&'a str, StrOrBytes<'a>);
218    type IntoIter = HttpHeadersIter<'a>;
219
220    fn into_iter(self) -> Self::IntoIter {
221        self.iter()
222    }
223}
224
225#[derive(Debug)]
226pub struct HttpHeadersIter<'a> {
227    http: &'a HttpHeaders<'a>,
228    cursor: isize,
229}
230
231impl<'a> Iterator for HttpHeadersIter<'a> {
232    type Item = (&'a str, StrOrBytes<'a>);
233
234    fn next(&mut self) -> Option<Self::Item> {
235        loop {
236            let nhd = self.http.raw.nhd;
237            if self.cursor >= nhd as isize {
238                return None;
239            }
240            let hd = unsafe { self.http.raw.hd.offset(self.cursor).as_ref().unwrap() };
241            self.cursor += 1;
242            if let Some(hdr) = hd.parse_header() {
243                return Some(hdr);
244            }
245        }
246    }
247}