1use 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
22const 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#[derive(Debug)]
34pub struct HttpHeaders<'a> {
35 pub raw: &'a mut ffi::http,
36}
37
38impl HttpHeaders<'_> {
39 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 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 pub fn set_header(&mut self, name: &str, value: &str) -> VclResult<()> {
95 self.set_header_raw(&format!("{name}: {value}"))
97 }
98
99 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 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 pub fn method(&self) -> Option<StrOrBytes<'_>> {
158 self.field(HDR_METHOD)
159 }
160
161 pub fn url(&self) -> Option<StrOrBytes<'_>> {
163 self.field(HDR_URL)
164 }
165
166 pub fn set_url(&mut self, value: &str) -> VclResult<()> {
183 self.change_header(HDR_URL, value)
184 }
185
186 pub fn proto(&self) -> Option<StrOrBytes<'_>> {
191 self.field(HDR_PROTO)
192 }
193
194 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 pub fn status(&self) -> Option<StrOrBytes<'_>> {
208 self.field(HDR_STATUS)
209 }
210
211 pub fn set_status(&mut self, status: u16) {
213 unsafe {
214 ffi::http_SetStatus(self.raw, status, std::ptr::null());
215 }
216 }
217
218 pub fn reason(&self) -> Option<StrOrBytes<'_>> {
220 self.field(HDR_REASON)
221 }
222
223 pub fn set_reason(&mut self, value: &str) -> VclResult<()> {
225 self.change_header(HDR_REASON, value)
226 }
227
228 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 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 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#[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}