use std::mem::transmute;
use std::slice::from_raw_parts_mut;
use crate::ffi;
use crate::ffi::VslTag;
use crate::vcl::str_or_bytes::StrOrBytes;
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 HttpHeaders<'_> {
pub(crate) fn from_ptr(p: ffi::VCL_HTTP) -> Option<Self> {
Some(HttpHeaders {
raw: unsafe { p.0.as_mut()? },
})
}
fn change_header<'a>(&mut self, idx: u16, value: impl Into<StrOrBytes<'a>>) -> 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()
.expect("HTTP header descriptor pointer must not be null");
*hd = ws.copy_bytes_with_null(value.into())?;
let hdf = self
.raw
.hdf
.offset(idx as isize)
.as_mut()
.expect("HTTP header flags pointer must not be null");
*hdf = 0;
}
Ok(())
}
fn set_header_raw<'a>(&mut self, raw: impl Into<StrOrBytes<'a>>) -> 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, raw);
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 set_header(&mut self, name: &str, value: &str) -> VclResult<()> {
self.set_header_raw(&format!("{name}: {value}"))
}
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().expect("HTTP header must be parseable");
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<StrOrBytes<'_>> {
unsafe {
if idx >= self.raw.nhd {
None
} else {
self.raw
.hd
.offset(idx as isize)
.as_ref()
.expect("HTTP header pointer must not be null")
.to_slice()
.map(StrOrBytes::from)
}
}
}
pub fn method(&self) -> Option<StrOrBytes<'_>> {
self.field(HDR_METHOD)
}
pub fn url(&self) -> Option<StrOrBytes<'_>> {
self.field(HDR_URL)
}
pub fn set_url(&mut self, value: &str) -> VclResult<()> {
self.change_header(HDR_URL, value)
}
pub fn proto(&self) -> Option<StrOrBytes<'_>> {
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<StrOrBytes<'_>> {
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<StrOrBytes<'_>> {
self.field(HDR_REASON)
}
pub fn set_reason(&mut self, value: &str) -> VclResult<()> {
self.change_header(HDR_REASON, value)
}
pub fn weaken_etag(&mut self) -> VclResult<()> {
let Some(etag) = self.header("ETag") else {
return Ok(());
};
let value = etag.as_ref();
if value.starts_with(b"W/") {
return Ok(());
}
let mut new_hdr = Vec::with_capacity(b"ETag: W/".len() + value.len());
new_hdr.extend_from_slice(b"ETag: W/");
new_hdr.extend_from_slice(value);
self.unset_header("ETag");
self.set_header_raw(new_hdr.as_slice())
}
pub fn header(&self, name: &str) -> Option<StrOrBytes<'_>> {
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, StrOrBytes<'a>);
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, StrOrBytes<'a>);
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()
.expect("HTTP header pointer must not be null")
};
self.cursor += 1;
if let Some(hdr) = hd.parse_header() {
return Some(hdr);
}
}
}
}