use http::{HeaderName, HeaderValue, Method, StatusCode, header};
use crate::util::compare_lowercase_ascii;
#[cfg(feature = "server")]
pub(crate) trait StatusCodeExt {
fn body_allowed(&self) -> bool;
}
#[cfg(feature = "server")]
impl StatusCodeExt for StatusCode {
fn body_allowed(&self) -> bool {
!self.is_informational()
&& *self != StatusCode::NO_CONTENT
&& *self != StatusCode::NOT_MODIFIED
}
}
pub(crate) trait MethodExt {
#[cfg(feature = "client")]
fn is_http10(&self) -> bool;
#[cfg(feature = "client")]
fn is_http11(&self) -> bool;
fn need_request_body(&self) -> bool;
fn allow_request_body(&self) -> bool;
#[cfg(feature = "client")]
fn verify_version(&self, version: http::Version) -> Result<(), crate::Error>;
}
impl MethodExt for Method {
#[cfg(feature = "client")]
fn is_http10(&self) -> bool {
self == Method::GET || self == Method::HEAD || self == Method::POST
}
#[cfg(feature = "client")]
fn is_http11(&self) -> bool {
self == Method::PUT
|| self == Method::DELETE
|| self == Method::CONNECT
|| self == Method::OPTIONS
|| self == Method::TRACE
|| self == Method::PATCH
}
fn need_request_body(&self) -> bool {
self == Method::POST || self == Method::PUT || self == Method::PATCH
}
fn allow_request_body(&self) -> bool {
self != Method::HEAD && self != Method::CONNECT
}
#[cfg(feature = "client")]
fn verify_version(&self, v: http::Version) -> Result<(), crate::Error> {
use crate::Error;
use http::Version;
if v != Version::HTTP_10 && v != Version::HTTP_11 {
return Err(Error::UnsupportedVersion);
}
let method_ok = self.is_http10() || v == Version::HTTP_11 && self.is_http11();
if !method_ok {
return Err(Error::MethodVersionMismatch(self.clone(), v));
}
Ok(())
}
}
pub(crate) trait HeaderIterExt {
fn has(self, key: HeaderName, token: &str) -> bool;
fn has_expect_100(self) -> bool;
}
impl<'a, I: Iterator<Item = (&'a HeaderName, &'a HeaderValue)>> HeaderIterExt for I {
fn has(self, key: HeaderName, token: &str) -> bool {
self.filter(|(name, _)| **name == key)
.filter_map(|(_, value)| value.to_str().ok())
.flat_map(|value| value.split(','))
.any(|element| compare_lowercase_ascii(element.trim(), token))
}
fn has_expect_100(self) -> bool {
self.has(header::EXPECT, "100-continue")
}
}
#[cfg(feature = "client")]
pub(crate) trait StatusExt {
fn is_redirect_retaining_status(&self) -> bool;
}
#[cfg(feature = "client")]
impl StatusExt for StatusCode {
fn is_redirect_retaining_status(&self) -> bool {
*self == StatusCode::TEMPORARY_REDIRECT || *self == StatusCode::PERMANENT_REDIRECT
}
}
#[cfg(feature = "client")]
pub trait SchemeExt {
fn default_port(&self) -> Option<u16>;
}
#[cfg(feature = "client")]
impl SchemeExt for http::uri::Scheme {
fn default_port(&self) -> Option<u16> {
use http::uri::Scheme;
if *self == Scheme::HTTPS {
Some(443)
} else if *self == Scheme::HTTP {
Some(80)
} else {
debug!("Unknown scheme: {}", self);
None
}
}
}
#[cfg(feature = "client")]
pub(crate) trait AuthorityExt {
fn userinfo(&self) -> Option<&str>;
fn username(&self) -> Option<&str>;
fn password(&self) -> Option<&str>;
}
#[cfg(feature = "client")]
impl AuthorityExt for http::uri::Authority {
fn userinfo(&self) -> Option<&str> {
let s = self.as_str();
s.rfind('@').map(|i| &s[..i])
}
fn username(&self) -> Option<&str> {
self.userinfo()
.map(|a| a.rfind(':').map(|i| &a[..i]).unwrap_or(a))
}
fn password(&self) -> Option<&str> {
self.userinfo()
.and_then(|a| a.rfind(':').map(|i| &a[i + 1..]))
}
}
#[cfg(test)]
mod test {
use super::*;
use http::HeaderMap;
fn headers(lines: &[(&'static str, &'static str)]) -> HeaderMap {
let mut map = HeaderMap::new();
for (name, value) in lines {
map.append(*name, HeaderValue::from_static(value));
}
map
}
#[test]
fn has_is_case_insensitive() {
let h = headers(&[("connection", "Close")]);
assert!(h.iter().has(header::CONNECTION, "close"));
let h = headers(&[("connection", "Keep-Alive")]);
assert!(h.iter().has(header::CONNECTION, "keep-alive"));
}
#[test]
fn has_finds_element_in_list() {
let h = headers(&[("connection", "keep-alive, close")]);
assert!(h.iter().has(header::CONNECTION, "close"));
assert!(h.iter().has(header::CONNECTION, "keep-alive"));
let h = headers(&[("connection", "close,Upgrade")]);
assert!(h.iter().has(header::CONNECTION, "close"));
assert!(h.iter().has(header::CONNECTION, "upgrade"));
}
#[test]
fn has_across_repeated_lines() {
let h = headers(&[("connection", "keep-alive"), ("connection", "close")]);
assert!(h.iter().has(header::CONNECTION, "close"));
}
#[test]
fn has_no_partial_or_wrong_header_match() {
let h = headers(&[("connection", "closed")]);
assert!(!h.iter().has(header::CONNECTION, "close"));
let h = headers(&[("connection", "keep-alive")]);
assert!(!h.iter().has(header::CONNECTION, "close"));
let h = headers(&[("upgrade", "close")]);
assert!(!h.iter().has(header::CONNECTION, "close"));
let h = headers(&[("connection", "")]);
assert!(!h.iter().has(header::CONNECTION, "close"));
}
#[test]
fn has_expect_100_is_case_insensitive() {
let h = headers(&[("expect", "100-Continue")]);
assert!(h.iter().has_expect_100());
let h = headers(&[("expect", "100-continue")]);
assert!(h.iter().has_expect_100());
}
}