From d1b2d29528b7794b41e66fc2136e395a02f8529b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:59:35 +0200 Subject: Merging upstream version 1.73.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/http/src/method.rs | 473 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 vendor/http/src/method.rs (limited to 'vendor/http/src/method.rs') diff --git a/vendor/http/src/method.rs b/vendor/http/src/method.rs new file mode 100644 index 000000000..b7b3b357c --- /dev/null +++ b/vendor/http/src/method.rs @@ -0,0 +1,473 @@ +//! The HTTP request method +//! +//! This module contains HTTP-method related structs and errors and such. The +//! main type of this module, `Method`, is also reexported at the root of the +//! crate as `http::Method` and is intended for import through that location +//! primarily. +//! +//! # Examples +//! +//! ``` +//! use http::Method; +//! +//! assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap()); +//! assert!(Method::GET.is_idempotent()); +//! assert_eq!(Method::POST.as_str(), "POST"); +//! ``` + +use self::Inner::*; +use self::extension::{InlineExtension, AllocatedExtension}; + +use std::convert::AsRef; +use std::error::Error; +use std::str::FromStr; +use std::convert::TryFrom; +use std::{fmt, str}; + +/// The Request Method (VERB) +/// +/// This type also contains constants for a number of common HTTP methods such +/// as GET, POST, etc. +/// +/// Currently includes 8 variants representing the 8 methods defined in +/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH, +/// and an Extension variant for all extensions. +/// +/// # Examples +/// +/// ``` +/// use http::Method; +/// +/// assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap()); +/// assert!(Method::GET.is_idempotent()); +/// assert_eq!(Method::POST.as_str(), "POST"); +/// ``` +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Method(Inner); + +/// A possible error value when converting `Method` from bytes. +pub struct InvalidMethod { + _priv: (), +} + +#[derive(Clone, PartialEq, Eq, Hash)] +enum Inner { + Options, + Get, + Post, + Put, + Delete, + Head, + Trace, + Connect, + Patch, + // If the extension is short enough, store it inline + ExtensionInline(InlineExtension), + // Otherwise, allocate it + ExtensionAllocated(AllocatedExtension), +} + + +impl Method { + /// GET + pub const GET: Method = Method(Get); + + /// POST + pub const POST: Method = Method(Post); + + /// PUT + pub const PUT: Method = Method(Put); + + /// DELETE + pub const DELETE: Method = Method(Delete); + + /// HEAD + pub const HEAD: Method = Method(Head); + + /// OPTIONS + pub const OPTIONS: Method = Method(Options); + + /// CONNECT + pub const CONNECT: Method = Method(Connect); + + /// PATCH + pub const PATCH: Method = Method(Patch); + + /// TRACE + pub const TRACE: Method = Method(Trace); + + /// Converts a slice of bytes to an HTTP method. + pub fn from_bytes(src: &[u8]) -> Result { + match src.len() { + 0 => Err(InvalidMethod::new()), + 3 => match src { + b"GET" => Ok(Method(Get)), + b"PUT" => Ok(Method(Put)), + _ => Method::extension_inline(src), + }, + 4 => match src { + b"POST" => Ok(Method(Post)), + b"HEAD" => Ok(Method(Head)), + _ => Method::extension_inline(src), + }, + 5 => match src { + b"PATCH" => Ok(Method(Patch)), + b"TRACE" => Ok(Method(Trace)), + _ => Method::extension_inline(src), + }, + 6 => match src { + b"DELETE" => Ok(Method(Delete)), + _ => Method::extension_inline(src), + }, + 7 => match src { + b"OPTIONS" => Ok(Method(Options)), + b"CONNECT" => Ok(Method(Connect)), + _ => Method::extension_inline(src), + }, + _ => { + if src.len() < InlineExtension::MAX { + Method::extension_inline(src) + } else { + let allocated = AllocatedExtension::new(src)?; + + Ok(Method(ExtensionAllocated(allocated))) + } + } + } + } + + fn extension_inline(src: &[u8]) -> Result { + let inline = InlineExtension::new(src)?; + + Ok(Method(ExtensionInline(inline))) + } + + /// Whether a method is considered "safe", meaning the request is + /// essentially read-only. + /// + /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1) + /// for more words. + pub fn is_safe(&self) -> bool { + match self.0 { + Get | Head | Options | Trace => true, + _ => false, + } + } + + /// Whether a method is considered "idempotent", meaning the request has + /// the same result if executed multiple times. + /// + /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for + /// more words. + pub fn is_idempotent(&self) -> bool { + match self.0 { + Put | Delete => true, + _ => self.is_safe(), + } + } + + /// Return a &str representation of the HTTP method + #[inline] + pub fn as_str(&self) -> &str { + match self.0 { + Options => "OPTIONS", + Get => "GET", + Post => "POST", + Put => "PUT", + Delete => "DELETE", + Head => "HEAD", + Trace => "TRACE", + Connect => "CONNECT", + Patch => "PATCH", + ExtensionInline(ref inline) => inline.as_str(), + ExtensionAllocated(ref allocated) => allocated.as_str(), + } + } +} + +impl AsRef for Method { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<'a> PartialEq<&'a Method> for Method { + #[inline] + fn eq(&self, other: &&'a Method) -> bool { + self == *other + } +} + +impl<'a> PartialEq for &'a Method { + #[inline] + fn eq(&self, other: &Method) -> bool { + *self == other + } +} + +impl PartialEq for Method { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_ref() == other + } +} + +impl PartialEq for str { + #[inline] + fn eq(&self, other: &Method) -> bool { + self == other.as_ref() + } +} + +impl<'a> PartialEq<&'a str> for Method { + #[inline] + fn eq(&self, other: &&'a str) -> bool { + self.as_ref() == *other + } +} + +impl<'a> PartialEq for &'a str { + #[inline] + fn eq(&self, other: &Method) -> bool { + *self == other.as_ref() + } +} + +impl fmt::Debug for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_ref()) + } +} + +impl fmt::Display for Method { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str(self.as_ref()) + } +} + +impl Default for Method { + #[inline] + fn default() -> Method { + Method::GET + } +} + +impl<'a> From<&'a Method> for Method { + #[inline] + fn from(t: &'a Method) -> Self { + t.clone() + } +} + +impl<'a> TryFrom<&'a [u8]> for Method { + type Error = InvalidMethod; + + #[inline] + fn try_from(t: &'a [u8]) -> Result { + Method::from_bytes(t) + } +} + +impl<'a> TryFrom<&'a str> for Method { + type Error = InvalidMethod; + + #[inline] + fn try_from(t: &'a str) -> Result { + TryFrom::try_from(t.as_bytes()) + } +} + +impl FromStr for Method { + type Err = InvalidMethod; + + #[inline] + fn from_str(t: &str) -> Result { + TryFrom::try_from(t) + } +} + +impl InvalidMethod { + fn new() -> InvalidMethod { + InvalidMethod { _priv: () } + } +} + +impl fmt::Debug for InvalidMethod { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("InvalidMethod") + // skip _priv noise + .finish() + } +} + +impl fmt::Display for InvalidMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid HTTP method") + } +} + +impl Error for InvalidMethod {} + +mod extension { + use super::InvalidMethod; + use std::str; + + #[derive(Clone, PartialEq, Eq, Hash)] + // Invariant: the first self.1 bytes of self.0 are valid UTF-8. + pub struct InlineExtension([u8; InlineExtension::MAX], u8); + + #[derive(Clone, PartialEq, Eq, Hash)] + // Invariant: self.0 contains valid UTF-8. + pub struct AllocatedExtension(Box<[u8]>); + + impl InlineExtension { + // Method::from_bytes() assumes this is at least 7 + pub const MAX: usize = 15; + + pub fn new(src: &[u8]) -> Result { + let mut data: [u8; InlineExtension::MAX] = Default::default(); + + write_checked(src, &mut data)?; + + // Invariant: write_checked ensures that the first src.len() bytes + // of data are valid UTF-8. + Ok(InlineExtension(data, src.len() as u8)) + } + + pub fn as_str(&self) -> &str { + let InlineExtension(ref data, len) = self; + // Safety: the invariant of InlineExtension ensures that the first + // len bytes of data contain valid UTF-8. + unsafe {str::from_utf8_unchecked(&data[..*len as usize])} + } + } + + impl AllocatedExtension { + pub fn new(src: &[u8]) -> Result { + let mut data: Vec = vec![0; src.len()]; + + write_checked(src, &mut data)?; + + // Invariant: data is exactly src.len() long and write_checked + // ensures that the first src.len() bytes of data are valid UTF-8. + Ok(AllocatedExtension(data.into_boxed_slice())) + } + + pub fn as_str(&self) -> &str { + // Safety: the invariant of AllocatedExtension ensures that self.0 + // contains valid UTF-8. + unsafe {str::from_utf8_unchecked(&self.0)} + } + } + + // From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can + // contain the following characters: + // + // ``` + // method = token + // token = 1*tchar + // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / + // "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA + // ``` + // + // https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method + // + // Note that this definition means that any &[u8] that consists solely of valid + // characters is also valid UTF-8 because the valid method characters are a + // subset of the valid 1 byte UTF-8 encoding. + const METHOD_CHARS: [u8; 256] = [ + // 0 1 2 3 4 5 6 7 8 9 + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 1x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 2x + b'\0', b'\0', b'\0', b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 3x + b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', // 4x + b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', // 5x + b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', // 6x + b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x + b'Z', b'\0', b'\0', b'\0', b'^', b'_', b'`', b'a', b'b', b'c', // 9x + b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x + b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x + b'x', b'y', b'z', b'\0', b'|', b'\0', b'~', b'\0', b'\0', b'\0', // 12x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0' // 25x + ]; + + // write_checked ensures (among other things) that the first src.len() bytes + // of dst are valid UTF-8 + fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> { + for (i, &b) in src.iter().enumerate() { + let b = METHOD_CHARS[b as usize]; + + if b == 0 { + return Err(InvalidMethod::new()); + } + + dst[i] = b; + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_method_eq() { + assert_eq!(Method::GET, Method::GET); + assert_eq!(Method::GET, "GET"); + assert_eq!(&Method::GET, "GET"); + + assert_eq!("GET", Method::GET); + assert_eq!("GET", &Method::GET); + + assert_eq!(&Method::GET, Method::GET); + assert_eq!(Method::GET, &Method::GET); + } + + #[test] + fn test_invalid_method() { + assert!(Method::from_str("").is_err()); + assert!(Method::from_bytes(b"").is_err()); + assert!(Method::from_bytes(&[0xC0]).is_err()); // invalid utf-8 + assert!(Method::from_bytes(&[0x10]).is_err()); // invalid method characters + } + + #[test] + fn test_is_idempotent() { + assert!(Method::OPTIONS.is_idempotent()); + assert!(Method::GET.is_idempotent()); + assert!(Method::PUT.is_idempotent()); + assert!(Method::DELETE.is_idempotent()); + assert!(Method::HEAD.is_idempotent()); + assert!(Method::TRACE.is_idempotent()); + + assert!(!Method::POST.is_idempotent()); + assert!(!Method::CONNECT.is_idempotent()); + assert!(!Method::PATCH.is_idempotent()); + } + + #[test] + fn test_extention_method() { + assert_eq!(Method::from_str("WOW").unwrap(), "WOW"); + assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!"); + + let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely."; + assert_eq!(Method::from_str(&long_method).unwrap(), long_method); + } +} -- cgit v1.2.3