//! 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); } }