#![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::mem::transmute;
use std::slice::from_raw_parts_mut;
use crate::ffi;
use crate::ffi::VslTag;
use crate::vcl::{VclResult, Workspace};
const HDR_FIRST: u16 = ffi::HTTP_HDR_FIRST as u16;
const HDR_METHOD: u16 = ffi::HTTP_HDR_METHOD as u16;
const HDR_PROTO: u16 = ffi::HTTP_HDR_PROTO as u16;
const HDR_REASON: u16 = ffi::HTTP_HDR_REASON as u16;
const HDR_STATUS: u16 = ffi::HTTP_HDR_STATUS as u16;
const HDR_UNSET: u16 = ffi::HTTP_HDR_UNSET as u16;
const HDR_URL: u16 = ffi::HTTP_HDR_URL as u16;
#[derive(Debug)]
pub struct HttpHeaders<'a> {
pub raw: &'a mut ffi::http,
}
impl<'a> HttpHeaders<'a> {
pub(crate) fn from_ptr(p: ffi::VCL_HTTP) -> Option<Self> {
Some(HttpHeaders {
raw: unsafe { p.0.as_mut()? },
})
}
fn change_header(&mut self, idx: u16, value: &str) -> VclResult<()> {
assert!(idx < self.raw.nhd);
let mut ws = Workspace::from_ptr(self.raw.ws);
unsafe {
let hd = self.raw.hd.offset(idx as isize).as_mut().unwrap();
*hd = ws.copy_bytes_with_null(value)?;
let hdf = self.raw.hdf.offset(idx as isize).as_mut().unwrap();
*hdf = 0;
}
Ok(())
}
pub fn set_header(&mut self, name: &str, value: &str) -> VclResult<()> {
assert!(self.raw.nhd <= self.raw.shd);
if self.raw.nhd == self.raw.shd {
return Err(c"no more header slot".into());
}
let idx = self.raw.nhd;
self.raw.nhd += 1;
let res = self.change_header(idx, &format!("{name}: {value}"));
if res.is_ok() {
unsafe {
ffi::VSLbt(
self.raw.vsl,
transmute::<u32, VslTag>((self.raw.logtag as u32) + u32::from(HDR_FIRST)),
*self.raw.hd.add(idx as usize),
);
}
} else {
self.raw.nhd -= 1;
}
res
}
pub fn unset_header(&mut self, name: &str) {
let hdrs = unsafe {
&from_raw_parts_mut(self.raw.hd, self.raw.nhd as usize)[(HDR_FIRST as usize)..]
};
let mut idx_empty = 0;
for (idx, hd) in hdrs.iter().enumerate() {
let (n, _) = hd.parse_header().unwrap();
if name.eq_ignore_ascii_case(n) {
unsafe {
ffi::VSLbt(
self.raw.vsl,
transmute::<u32, VslTag>(
(self.raw.logtag as u32) + u32::from(HDR_UNSET) + u32::from(HDR_METHOD),
),
*self.raw.hd.add(HDR_FIRST as usize + idx),
);
}
continue;
}
if idx != idx_empty {
unsafe {
std::ptr::copy_nonoverlapping(
self.raw.hd.add(HDR_FIRST as usize + idx),
self.raw.hd.add(HDR_FIRST as usize + idx_empty),
1,
);
std::ptr::copy_nonoverlapping(
self.raw.hdf.add(HDR_FIRST as usize + idx),
self.raw.hdf.add(HDR_FIRST as usize + idx_empty),
1,
);
}
}
idx_empty += 1;
}
self.raw.nhd = HDR_FIRST + idx_empty as u16;
}
fn field(&self, idx: u16) -> Option<&str> {
unsafe {
if idx >= self.raw.nhd {
None
} else {
self.raw.hd.offset(idx as isize).as_ref().unwrap().to_str()
}
}
}
pub fn method(&self) -> Option<&str> {
self.field(HDR_METHOD)
}
pub fn url(&self) -> Option<&str> {
self.field(HDR_URL)
}
pub fn proto(&self) -> Option<&str> {
self.field(HDR_PROTO)
}
pub fn set_proto(&mut self, value: &str) -> VclResult<()> {
self.raw.protover = match value {
"HTTP/0.9" => 9,
"HTTP/1.0" => 10,
"HTTP/1.1" => 11,
"HTTP/2.0" => 20,
_ => 0,
};
self.change_header(HDR_PROTO, value)
}
pub fn status(&self) -> Option<&str> {
self.field(HDR_STATUS)
}
pub fn set_status(&mut self, status: u16) {
unsafe { ffi::http_SetStatus(self.raw, status, std::ptr::null()) }
}
pub fn reason(&self) -> Option<&str> {
self.field(HDR_REASON)
}
pub fn set_reason(&mut self, value: &str) -> VclResult<()> {
self.change_header(HDR_REASON, value)
}
pub fn header(&self, name: &str) -> Option<&str> {
self.iter()
.find(|hdr| name.eq_ignore_ascii_case(hdr.0))
.map(|hdr| hdr.1)
}
pub fn iter(&self) -> HttpHeadersIter<'_> {
HttpHeadersIter {
http: self,
cursor: HDR_FIRST as isize,
}
}
}
impl<'a> IntoIterator for &'a HttpHeaders<'a> {
type Item = (&'a str, &'a str);
type IntoIter = HttpHeadersIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug)]
pub struct HttpHeadersIter<'a> {
http: &'a HttpHeaders<'a>,
cursor: isize,
}
impl<'a> Iterator for HttpHeadersIter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
loop {
let nhd = self.http.raw.nhd;
if self.cursor >= nhd as isize {
return None;
}
let hd = unsafe { self.http.raw.hd.offset(self.cursor).as_ref().unwrap() };
self.cursor += 1;
if let Some(hdr) = hd.parse_header() {
return Some(hdr);
}
}
}
}