use std::{
borrow::Cow,
convert::TryFrom,
fmt,
iter::{IntoIterator, Iterator},
str::FromStr,
};
use http::{
Extensions, HeaderMap, Method, StatusCode,
header::{self, HeaderName, HeaderValue},
uri::{self, Authority, Parts, PathAndQuery, Scheme, Uri},
};
use smallvec::SmallVec;
use crate::{ext::Protocol, qpack::HeaderField};
define_enum_with_values! {
@U8
pub enum PseudoId {
Method => 0x0001,
Scheme => 0x0002,
Authority => 0x0003,
Path => 0x0004,
Protocol => 0x0005,
Status => 0x0006,
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct PseudoOrder {
ids: [PseudoId; PseudoId::DEFAULT_STACK_SIZE],
}
#[derive(Debug)]
pub struct PseudoOrderBuilder {
ids: SmallVec<[PseudoId; PseudoId::DEFAULT_STACK_SIZE]>,
mask: u8,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PseudoHeaderSensitivity {
mask: u8,
}
impl PseudoHeaderSensitivity {
pub fn set_sensitive(&mut self, id: PseudoId, sensitive: bool) {
if sensitive {
self.mask |= id.mask_id();
} else {
self.mask &= !id.mask_id();
}
}
pub fn is_sensitive(&self, id: PseudoId) -> bool {
self.mask & id.mask_id() != 0
}
pub(crate) fn is_empty(&self) -> bool {
self.mask == 0
}
}
impl PseudoOrder {
#[inline]
pub fn builder() -> PseudoOrderBuilder {
PseudoOrderBuilder {
ids: SmallVec::new(),
mask: 0,
}
}
}
impl Default for PseudoOrder {
#[inline]
fn default() -> Self {
PseudoOrder {
ids: PseudoId::DEFAULT_IDS,
}
}
}
impl<'a> IntoIterator for &'a PseudoOrder {
type Item = &'a PseudoId;
type IntoIter = std::slice::Iter<'a, PseudoId>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.ids.iter()
}
}
impl PseudoOrderBuilder {
pub fn push(mut self, id: PseudoId) -> Self {
let mask_id = id.mask_id();
if mask_id != 0 && self.mask & mask_id == 0 {
self.mask |= mask_id;
self.ids.push(id);
}
self
}
pub fn extend(mut self, iter: impl IntoIterator<Item = PseudoId>) -> Self {
for id in iter {
self = self.push(id);
}
self
}
pub fn build(mut self) -> PseudoOrder {
if self.ids.len() != PseudoId::DEFAULT_IDS.len() {
self = self.extend(PseudoId::DEFAULT_IDS);
}
let mut ids = PseudoId::DEFAULT_IDS;
for (target, source) in ids.iter_mut().zip(self.ids) {
*target = source;
}
PseudoOrder { ids }
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub struct Header {
pseudo: Pseudo,
fields: HeaderMap,
}
#[allow(clippy::len_without_is_empty)]
impl Header {
pub fn request(
method: Method,
uri: Uri,
fields: HeaderMap,
ext: Extensions,
) -> Result<Self, HeaderError> {
match (uri.authority(), fields.get(header::HOST)) {
(None, None) => Err(HeaderError::MissingAuthority),
(Some(a), Some(h)) if a.as_str() != h => Err(HeaderError::ContradictedAuthority),
_ => Ok(Self {
pseudo: Pseudo::request(method, uri, ext),
fields,
}),
}
}
pub fn response(status: StatusCode, fields: HeaderMap, ext: Extensions) -> Self {
Self {
pseudo: Pseudo::response(status, ext),
fields,
}
}
pub fn trailer(fields: HeaderMap) -> Self {
Self {
pseudo: Pseudo::default(),
fields,
}
}
pub fn into_request_parts(
self,
) -> Result<
(
Method,
Uri,
Option<Protocol>,
HeaderMap,
PseudoHeaderSensitivity,
),
HeaderError,
> {
if self.pseudo.status.is_some() {
return Err(HeaderError::UnexpectedPseudo);
}
let mut uri = Uri::builder();
if let Some(path) = self.pseudo.path {
uri = uri.path_and_query(path.as_str().as_bytes());
}
if let Some(scheme) = self.pseudo.scheme {
uri = uri.scheme(scheme.as_str().as_bytes());
}
match (self.pseudo.authority, self.fields.get(header::HOST)) {
(None, None) => return Err(HeaderError::MissingAuthority),
(Some(a), None) => uri = uri.authority(a.as_str().as_bytes()),
(None, Some(h)) => uri = uri.authority(h.as_bytes()),
(Some(a), Some(h)) if a.as_str() != h => {
return Err(HeaderError::ContradictedAuthority);
}
(Some(_), Some(h)) => uri = uri.authority(h.as_bytes()),
}
Ok((
self.pseudo.method.ok_or(HeaderError::MissingMethod)?,
uri.build().map_err(HeaderError::InvalidRequest)?,
self.pseudo.protocol,
self.fields,
PseudoHeaderSensitivity {
mask: self.pseudo.sensitive,
},
))
}
pub fn into_response_parts(
self,
) -> Result<(StatusCode, HeaderMap, PseudoHeaderSensitivity), HeaderError> {
if self.pseudo.method.is_some()
|| self.pseudo.scheme.is_some()
|| self.pseudo.authority.is_some()
|| self.pseudo.path.is_some()
|| self.pseudo.protocol.is_some()
{
return Err(HeaderError::UnexpectedPseudo);
}
Ok((
self.pseudo.status.ok_or(HeaderError::MissingStatus)?,
self.fields,
PseudoHeaderSensitivity {
mask: self.pseudo.sensitive,
},
))
}
pub fn into_trailers(self) -> Result<HeaderMap, HeaderError> {
if self.pseudo.len != 0 {
return Err(HeaderError::UnexpectedPseudo);
}
Ok(self.fields)
}
pub fn len(&self) -> usize {
self.pseudo.len() + self.fields.len()
}
pub fn size(&self) -> usize {
self.pseudo.len() + self.fields.len()
}
pub fn set_pseudo_order(&mut self, order: PseudoOrder) {
self.pseudo.order = Some(order);
}
#[cfg(test)]
pub(crate) fn authory_mut(&mut self) -> &mut Option<Authority> {
&mut self.pseudo.authority
}
}
impl<'a> IntoIterator for &'a Header {
type Item = HeaderField<'a>;
type IntoIter = HeaderIter<'a>;
fn into_iter(self) -> Self::IntoIter {
HeaderIter {
pseudo: &self.pseudo,
pseudo_order_index: 0,
fields: self.fields.iter(),
}
}
}
pub struct HeaderIter<'a> {
pseudo: &'a Pseudo,
pseudo_order_index: usize,
fields: header::Iter<'a, HeaderValue>,
}
impl<'a> Iterator for HeaderIter<'a> {
type Item = HeaderField<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.pseudo_order_index < PseudoId::DEFAULT_STACK_SIZE {
let index = self.pseudo_order_index;
self.pseudo_order_index += 1;
let id = self
.pseudo
.order
.as_ref()
.map_or(PseudoId::DEFAULT_IDS[index], |order| order.ids[index]);
if let Some(field) = self.pseudo.field_ref(id) {
return Some(field);
}
}
self.fields.next().map(|(name, value)| {
HeaderField::borrowed(
name.as_str().as_bytes(),
value.as_bytes(),
value.is_sensitive(),
)
})
}
}
impl TryFrom<Vec<HeaderField<'static>>> for Header {
type Error = HeaderError;
fn try_from(headers: Vec<HeaderField<'static>>) -> Result<Self, Self::Error> {
let mut fields =
HeaderMap::try_with_capacity(headers.len()).map_err(|_| HeaderError::TooManyFields)?;
let mut pseudo = Pseudo::default();
let mut seen = 0_u8;
for field in headers.into_iter() {
let sensitive = field.is_sensitive();
let (name, value) = field.into_inner();
let field = Field::parse(name, value)?;
if let Some(id) = field.pseudo_id() {
if !fields.is_empty() {
return Err(HeaderError::PseudoAfterField);
}
if seen & id.mask_id() != 0 {
return Err(HeaderError::DuplicatePseudo);
}
seen |= id.mask_id();
}
match field {
Field::Method(m) => {
pseudo.method = Some(m);
pseudo.len += 1;
pseudo.set_sensitive(PseudoId::Method, sensitive);
}
Field::Scheme(s) => {
pseudo.scheme = Some(s);
pseudo.len += 1;
pseudo.set_sensitive(PseudoId::Scheme, sensitive);
}
Field::Authority(a) => {
pseudo.authority = Some(a);
pseudo.len += 1;
pseudo.set_sensitive(PseudoId::Authority, sensitive);
}
Field::Path(p) => {
pseudo.path = Some(p);
pseudo.len += 1;
pseudo.set_sensitive(PseudoId::Path, sensitive);
}
Field::Status(s) => {
pseudo.status = Some(s);
pseudo.len += 1;
pseudo.set_sensitive(PseudoId::Status, sensitive);
}
Field::Header((n, mut v)) => {
v.set_sensitive(sensitive);
fields
.try_append(n, v)
.map_err(|_| HeaderError::TooManyFields)?;
}
Field::Protocol(p) => {
pseudo.protocol = Some(p);
pseudo.len += 1;
pseudo.set_sensitive(PseudoId::Protocol, sensitive);
}
}
}
Ok(Header { pseudo, fields })
}
}
enum Field {
Method(Method),
Scheme(Scheme),
Authority(Authority),
Path(PathAndQuery),
Status(StatusCode),
Protocol(Protocol),
Header((HeaderName, HeaderValue)),
}
impl Field {
fn pseudo_id(&self) -> Option<PseudoId> {
match self {
Self::Method(_) => Some(PseudoId::Method),
Self::Scheme(_) => Some(PseudoId::Scheme),
Self::Authority(_) => Some(PseudoId::Authority),
Self::Path(_) => Some(PseudoId::Path),
Self::Status(_) => Some(PseudoId::Status),
Self::Protocol(_) => Some(PseudoId::Protocol),
Self::Header(_) => None,
}
}
fn parse(name: Cow<'static, [u8]>, value: Cow<'static, [u8]>) -> Result<Self, HeaderError> {
let name = name.as_ref();
if name.is_empty() {
return Err(HeaderError::InvalidHeaderName("name is empty".into()));
}
if name[0] != b':' {
let shared_value = match value {
Cow::Borrowed(value) => bytes::Bytes::from_static(value),
Cow::Owned(value) => bytes::Bytes::from(value),
};
let diagnostic = shared_value.clone();
return Ok(Field::Header((
HeaderName::from_lowercase(name).map_err(|_| HeaderError::invalid_name(name))?,
HeaderValue::from_maybe_shared(shared_value)
.map_err(|_| HeaderError::invalid_value(name, diagnostic))?,
)));
}
Ok(match name {
b":scheme" => Field::Scheme(try_value(name, value)?),
b":authority" => Field::Authority(try_value(name, value)?),
b":path" => Field::Path(try_value(name, value)?),
b":method" => Field::Method(
Method::from_bytes(value.as_ref())
.map_err(|_| HeaderError::invalid_value(name, value))?,
),
b":status" => Field::Status(
StatusCode::from_bytes(value.as_ref())
.map_err(|_| HeaderError::invalid_value(name, value))?,
),
b":protocol" => Field::Protocol(try_value(name, value)?),
_ => return Err(HeaderError::invalid_name(name)),
})
}
}
fn try_value<N, V, R>(name: N, value: V) -> Result<R, HeaderError>
where
N: AsRef<[u8]>,
V: AsRef<[u8]>,
R: FromStr,
{
let (name, value) = (name.as_ref(), value.as_ref());
let s = std::str::from_utf8(value).map_err(|_| HeaderError::invalid_value(name, value))?;
R::from_str(s).map_err(|_| HeaderError::invalid_value(name, value))
}
#[derive(Debug, Default)]
#[cfg_attr(test, derive(PartialEq, Clone))]
struct Pseudo {
method: Option<Method>,
scheme: Option<Scheme>,
authority: Option<Authority>,
path: Option<PathAndQuery>,
status: Option<StatusCode>,
protocol: Option<Protocol>,
order: Option<PseudoOrder>,
sensitive: u8,
len: usize,
}
#[allow(clippy::len_without_is_empty)]
impl Pseudo {
fn request(method: Method, uri: Uri, ext: Extensions) -> Self {
let Parts {
scheme,
authority,
path_and_query,
..
} = uri::Parts::from(uri);
let path = path_and_query.map_or_else(
|| PathAndQuery::from_static("/"),
|path| {
if path.path().is_empty() && method != Method::OPTIONS {
PathAndQuery::from_static("/")
} else {
path
}
},
);
let protocol = if method == Method::CONNECT {
ext.get::<Protocol>().copied()
} else {
None
};
let (scheme, path) = if method == Method::CONNECT && protocol.is_none() {
(None, None)
} else {
(scheme.or(Some(Scheme::HTTPS)), Some(path))
};
let len = 3 + authority.is_some() as usize + protocol.is_some() as usize;
let order = ext.get::<PseudoOrder>().cloned();
let sensitive = ext
.get::<PseudoHeaderSensitivity>()
.copied()
.unwrap_or_default()
.mask;
Self {
method: Some(method),
scheme,
authority,
path,
status: None,
protocol,
order,
sensitive,
len,
}
}
fn response(status: StatusCode, ext: Extensions) -> Self {
Pseudo {
method: None,
scheme: None,
authority: None,
path: None,
status: Some(status),
protocol: None,
order: None,
sensitive: ext
.get::<PseudoHeaderSensitivity>()
.copied()
.unwrap_or_default()
.mask,
len: 1,
}
}
fn len(&self) -> usize {
self.len
}
fn set_sensitive(&mut self, id: PseudoId, sensitive: bool) {
if sensitive {
self.sensitive |= id.mask_id();
} else {
self.sensitive &= !id.mask_id();
}
}
fn is_sensitive(&self, id: PseudoId) -> bool {
self.sensitive & id.mask_id() != 0
}
#[inline]
fn field_ref(&self, id: PseudoId) -> Option<HeaderField<'_>> {
let (name, value): (&[u8], &[u8]) = match id {
PseudoId::Method => (b":method", self.method.as_ref()?.as_str().as_bytes()),
PseudoId::Scheme => (b":scheme", self.scheme.as_ref()?.as_str().as_bytes()),
PseudoId::Authority => (b":authority", self.authority.as_ref()?.as_str().as_bytes()),
PseudoId::Path => (b":path", self.path.as_ref()?.as_str().as_bytes()),
PseudoId::Status => (b":status", self.status.as_ref()?.as_str().as_bytes()),
PseudoId::Protocol => (b":protocol", self.protocol.as_ref()?.as_str().as_bytes()),
};
Some(HeaderField::borrowed(name, value, self.is_sensitive(id)))
}
}
#[derive(Debug)]
pub enum HeaderError {
InvalidHeaderName(String),
InvalidHeaderValue(String),
InvalidRequest(http::Error),
MissingMethod,
MissingStatus,
MissingAuthority,
ContradictedAuthority,
TooManyFields,
UnexpectedPseudo,
DuplicatePseudo,
PseudoAfterField,
}
impl HeaderError {
pub(crate) fn code(&self) -> crate::error::Code {
match self {
Self::TooManyFields => crate::error::Code::H3_EXCESSIVE_LOAD,
_ => crate::error::Code::H3_MESSAGE_ERROR,
}
}
fn invalid_name<N>(name: N) -> Self
where
N: AsRef<[u8]>,
{
HeaderError::InvalidHeaderName(format!("{:?}", name.as_ref()))
}
fn invalid_value<N, V>(name: N, value: V) -> Self
where
N: AsRef<[u8]>,
V: AsRef<[u8]>,
{
HeaderError::InvalidHeaderValue(format!(
"{:?} {:?}",
String::from_utf8_lossy(name.as_ref()),
value.as_ref()
))
}
}
impl std::error::Error for HeaderError {}
impl fmt::Display for HeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeaderError::InvalidHeaderName(h) => write!(f, "invalid header name: {}", h),
HeaderError::InvalidHeaderValue(v) => write!(f, "invalid header value: {}", v),
HeaderError::InvalidRequest(r) => write!(f, "invalid request: {}", r),
HeaderError::MissingMethod => write!(f, "missing method in request headers"),
HeaderError::MissingStatus => write!(f, "missing status in response headers"),
HeaderError::MissingAuthority => write!(f, "missing authority"),
HeaderError::ContradictedAuthority => {
write!(f, "uri and authority field are in contradiction")
}
HeaderError::TooManyFields => write!(f, "field section exceeds local header capacity"),
HeaderError::UnexpectedPseudo => write!(f, "pseudo-header is invalid in this context"),
HeaderError::DuplicatePseudo => write!(f, "duplicate pseudo-header"),
HeaderError::PseudoAfterField => write!(f, "pseudo-header follows a regular field"),
}
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
#[test]
fn received_pseudo_fields_reject_duplicates_and_late_placement() {
for (name, value) in [
(":method", "GET"),
(":scheme", "https"),
(":authority", "localhost"),
(":path", "/"),
(":status", "200"),
(":protocol", "webtransport"),
] {
let field = HeaderField::new(name, value);
assert_matches!(
Header::try_from(vec![field.clone(), field.clone()]),
Err(HeaderError::DuplicatePseudo)
);
assert_matches!(
Header::try_from(vec![HeaderField::new("x-regular", "v"), field]),
Err(HeaderError::PseudoAfterField)
);
}
}
#[test]
fn received_pseudo_fields_require_their_message_context() {
for (name, value) in [
(":method", "GET"),
(":scheme", "https"),
(":authority", "localhost"),
(":path", "/"),
(":protocol", "webtransport"),
] {
let field = HeaderField::new(name, value);
assert_matches!(
Header::try_from(vec![HeaderField::new(":status", "200"), field.clone()])
.unwrap()
.into_response_parts(),
Err(HeaderError::UnexpectedPseudo)
);
assert_matches!(
Header::try_from(vec![field]).unwrap().into_trailers(),
Err(HeaderError::UnexpectedPseudo)
);
}
let status = HeaderField::new(":status", "200");
assert_matches!(
Header::try_from(vec![status.clone()])
.unwrap()
.into_request_parts(),
Err(HeaderError::UnexpectedPseudo)
);
assert_matches!(
Header::try_from(vec![status]).unwrap().into_trailers(),
Err(HeaderError::UnexpectedPseudo)
);
}
#[test]
fn valid_received_trailers_preserve_duplicate_sensitive_fields() {
let mut field = HeaderField::new("x-trailer", "value");
field.sensitive = true;
let trailers = Header::try_from(vec![field; 2])
.unwrap()
.into_trailers()
.unwrap();
assert_eq!(trailers.len(), 2);
assert!(
trailers
.get_all("x-trailer")
.iter()
.all(HeaderValue::is_sensitive)
);
assert!(
Header::try_from(Vec::new())
.unwrap()
.into_trailers()
.unwrap()
.is_empty()
);
}
#[test]
fn received_connect_context_preserves_standard_and_extended_forms() {
for extended in [false, true] {
let mut fields = vec![
HeaderField::new(":method", "CONNECT"),
HeaderField::new(":authority", "localhost:443"),
];
if extended {
fields.extend([
HeaderField::new(":scheme", "https"),
HeaderField::new(":path", "/session"),
HeaderField::new(":protocol", "webtransport"),
]);
}
let (method, uri, protocol, _, _) = Header::try_from(fields)
.unwrap()
.into_request_parts()
.unwrap();
assert_eq!(method, Method::CONNECT);
assert_eq!(uri.authority().unwrap().as_str(), "localhost:443");
assert_eq!(
protocol.as_ref().map(Protocol::as_str),
extended.then_some("webtransport")
);
}
}
#[test]
fn oversized_decoded_field_count_is_a_resource_error() {
let fields = vec![HeaderField::new("accept", "*/*"); 40_000];
let mut encoded = bytes::BytesMut::new();
crate::qpack::encode_stateless(&mut encoded, &fields).unwrap();
assert!(encoded.len() < crate::config::DEFAULT_QPACK_DECODE_BUFFER_SIZE);
let decoded = crate::qpack::Decoder::new(0, 0)
.unwrap()
.decode_header(&mut encoded)
.unwrap();
let error = Header::try_from(decoded.fields).unwrap_err();
assert_matches!(error, HeaderError::TooManyFields);
assert_eq!(error.code(), crate::error::Code::H3_EXCESSIVE_LOAD);
}
#[test]
fn regular_duplicate_fields_and_sensitivity_are_preserved() {
let mut field = HeaderField::new("set-cookie", "a=b");
field.sensitive = true;
let header = Header::try_from(vec![field; 128]).unwrap();
assert_eq!(header.fields.len(), 128);
assert!(
header
.fields
.get_all("set-cookie")
.iter()
.all(HeaderValue::is_sensitive)
);
}
#[test]
fn request_has_no_authority_nor_host() {
let headers = Header::try_from(vec![(b":method", Method::GET.as_str()).into()]).unwrap();
assert!(headers.pseudo.authority.is_none());
assert_matches!(
headers.into_request_parts(),
Err(HeaderError::MissingAuthority)
);
}
#[test]
fn request_has_empty_authority() {
assert_matches!(
Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b":authority", b"").into(),
]),
Err(HeaderError::InvalidHeaderValue(_))
);
}
#[test]
fn request_has_empty_host() {
let headers = Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b"host", b"").into(),
])
.unwrap();
assert_matches!(
headers.into_request_parts(),
Err(HeaderError::InvalidRequest(_))
);
}
#[test]
fn request_has_authority() {
let headers = Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b":authority", b"test.com").into(),
])
.unwrap();
assert_matches!(headers.into_request_parts(), Ok(_));
}
#[test]
fn request_has_host() {
let headers = Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b"host", b"test.com").into(),
])
.unwrap();
assert!(headers.pseudo.authority.is_none());
assert_matches!(headers.into_request_parts(), Ok(_));
}
#[test]
fn request_has_same_host_and_authority() {
let headers = Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b":authority", b"test.com").into(),
(b"host", b"test.com").into(),
])
.unwrap();
assert_matches!(headers.into_request_parts(), Ok(_));
}
#[test]
fn request_has_different_host_and_authority() {
let headers = Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b":authority", b"authority.com").into(),
(b"host", b"host.com").into(),
])
.unwrap();
assert_matches!(
headers.into_request_parts(),
Err(HeaderError::ContradictedAuthority)
);
}
#[test]
fn preserves_duplicate_headers() {
let headers = Header::try_from(vec![
(b":method", Method::GET.as_str()).into(),
(b":authority", b"test.com").into(),
(b"set-cookie", b"foo=foo").into(),
(b"set-cookie", b"bar=bar").into(),
(b"other-header", b"other-header-value").into(),
])
.unwrap();
assert_eq!(
(&headers)
.into_iter()
.filter(|h| h.name.as_ref() == b"set-cookie")
.collect::<Vec<_>>(),
vec![
HeaderField {
name: std::borrow::Cow::Borrowed(b"set-cookie"),
value: std::borrow::Cow::Borrowed(b"foo=foo"),
sensitive: false,
},
HeaderField {
name: std::borrow::Cow::Borrowed(b"set-cookie"),
value: std::borrow::Cow::Borrowed(b"bar=bar"),
sensitive: false,
}
]
);
assert_eq!(
(&headers)
.into_iter()
.filter(|h| h.name.as_ref() == b"other-header")
.collect::<Vec<_>>(),
vec![HeaderField {
name: std::borrow::Cow::Borrowed(b"other-header"),
value: std::borrow::Cow::Borrowed(b"other-header-value"),
sensitive: false,
},]
);
}
#[test]
fn decoded_regular_fields_preserve_borrowed_and_owned_values() {
let mut owned = Vec::with_capacity(64);
owned.extend_from_slice(b"owned-value");
let owned_ptr = owned.as_ptr();
let borrowed: &'static [u8] = b"0";
let borrowed_ptr = borrowed.as_ptr();
let headers = Header::try_from(vec![
HeaderField {
name: Cow::Borrowed(b":status"),
value: Cow::Borrowed(b"200"),
sensitive: false,
},
HeaderField {
name: Cow::Borrowed(b"content-length"),
value: Cow::Borrowed(borrowed),
sensitive: true,
},
HeaderField {
name: Cow::Borrowed(b"x-owned"),
value: Cow::Owned(owned),
sensitive: false,
},
])
.unwrap();
let (status, fields, _) = headers.into_response_parts().unwrap();
assert_eq!(status, StatusCode::OK);
assert_eq!(fields["content-length"], "0");
assert!(fields["content-length"].is_sensitive());
assert_eq!(fields["x-owned"], "owned-value");
assert_eq!(fields["content-length"].as_bytes().as_ptr(), borrowed_ptr);
assert_eq!(fields["x-owned"].as_bytes().as_ptr(), owned_ptr);
}
#[test]
fn decoded_regular_field_errors_preserve_diagnostics() {
let borrowed = Header::try_from(vec![HeaderField {
name: Cow::Borrowed(b"x-invalid"),
value: Cow::Borrowed(b"bad\nvalue"),
sensitive: false,
}])
.unwrap_err();
let owned = Header::try_from(vec![HeaderField {
name: Cow::Owned(b"x-invalid".to_vec()),
value: Cow::Owned(b"bad\nvalue".to_vec()),
sensitive: false,
}])
.unwrap_err();
assert_matches!(borrowed, HeaderError::InvalidHeaderValue(_));
assert_matches!(owned, HeaderError::InvalidHeaderValue(_));
assert_eq!(borrowed.to_string(), owned.to_string());
}
#[test]
fn request_pseudo_sensitivity_survives_http_parts() {
let headers = Header::try_from(vec![
HeaderField::from((b":method", b"GET")).with_sensitive(true),
HeaderField::from((b":scheme", b"https")),
HeaderField::from((b":authority", b"example.com")),
HeaderField::from((b":path", b"/")),
])
.unwrap();
let (method, uri, _protocol, fields, sensitivity) = headers.into_request_parts().unwrap();
assert!(sensitivity.is_sensitive(PseudoId::Method));
let mut extensions = Extensions::new();
extensions.insert(sensitivity);
let forwarded = Header::request(method, uri, fields, extensions).unwrap();
let method = (&forwarded)
.into_iter()
.find(|field| field.name.as_ref() == b":method")
.unwrap();
assert!(method.is_sensitive());
}
#[test]
fn response_pseudo_sensitivity_survives_http_parts() {
let headers = Header::try_from(vec![
HeaderField::from((b":status", b"200")).with_sensitive(true),
])
.unwrap();
let (status, fields, sensitivity) = headers.into_response_parts().unwrap();
assert!(sensitivity.is_sensitive(PseudoId::Status));
let mut extensions = Extensions::new();
extensions.insert(sensitivity);
let forwarded = Header::response(status, fields, extensions);
let status = (&forwarded)
.into_iter()
.find(|field| field.name.as_ref() == b":status")
.unwrap();
assert!(status.is_sensitive());
}
#[test]
fn test_pseudo_order_default() {
let order = PseudoOrder::builder().build();
assert_eq!(order.ids.len(), PseudoId::DEFAULT_STACK_SIZE);
assert_eq!(order.ids, PseudoId::DEFAULT_IDS);
assert!(std::mem::size_of::<PseudoOrder>() <= 8);
assert!(std::mem::size_of::<Option<PseudoOrder>>() <= 8);
}
#[test]
fn test_pseudo_order_duplicate() {
let order = PseudoOrder::builder()
.push(PseudoId::Scheme)
.push(PseudoId::Scheme)
.build();
assert_eq!(order.ids.len(), PseudoId::DEFAULT_IDS.len());
assert_eq!(order.ids[0], PseudoId::Scheme);
assert_ne!(order.ids[1], PseudoId::Scheme);
}
#[test]
fn test_pseudo_order_custom_chrome_masp() {
let order = PseudoOrder::builder()
.push(PseudoId::Method)
.push(PseudoId::Authority)
.push(PseudoId::Scheme)
.push(PseudoId::Path)
.build();
let mut headers = Header::request(
Method::GET,
Uri::from_static("https://example.com/test"),
HeaderMap::new(),
Extensions::default(),
)
.unwrap();
headers.set_pseudo_order(order);
let pseudo_fields: Vec<_> = (&headers)
.into_iter()
.filter(|h| h.name.as_ref().starts_with(b":"))
.map(|h| String::from_utf8_lossy(h.name.as_ref()).into_owned())
.collect();
assert_eq!(
pseudo_fields,
vec![":method", ":authority", ":scheme", ":path"]
);
}
#[test]
fn test_pseudo_order_custom_firefox_msap() {
let order = PseudoOrder::builder()
.push(PseudoId::Method)
.push(PseudoId::Scheme)
.push(PseudoId::Authority)
.push(PseudoId::Path)
.build();
let mut headers = Header::request(
Method::GET,
Uri::from_static("https://example.com/test"),
HeaderMap::new(),
Extensions::default(),
)
.unwrap();
headers.set_pseudo_order(order);
let pseudo_fields: Vec<_> = (&headers)
.into_iter()
.filter(|h| h.name.as_ref().starts_with(b":"))
.map(|h| String::from_utf8_lossy(h.name.as_ref()).into_owned())
.collect();
assert_eq!(
pseudo_fields,
vec![":method", ":scheme", ":authority", ":path"]
);
}
#[test]
fn test_pseudo_order_default_unchanged() {
let headers = Header::request(
Method::GET,
Uri::from_static("https://example.com/test"),
HeaderMap::new(),
Extensions::default(),
)
.unwrap();
let pseudo_fields: Vec<_> = (&headers)
.into_iter()
.filter(|h| h.name.as_ref().starts_with(b":"))
.map(|h| String::from_utf8_lossy(h.name.as_ref()).into_owned())
.collect();
assert_eq!(
pseudo_fields,
vec![":method", ":scheme", ":authority", ":path"]
);
}
}