| //! 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<Method, InvalidMethod> { |
| 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<Method, InvalidMethod> { |
| 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<str> 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<Method> for &'a Method { |
| #[inline] |
| fn eq(&self, other: &Method) -> bool { |
| *self == other |
| } |
| } |
| |
| impl PartialEq<str> for Method { |
| #[inline] |
| fn eq(&self, other: &str) -> bool { |
| self.as_ref() == other |
| } |
| } |
| |
| impl PartialEq<Method> 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<Method> 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<Self, Self::Error> { |
| Method::from_bytes(t) |
| } |
| } |
| |
| impl<'a> TryFrom<&'a str> for Method { |
| type Error = InvalidMethod; |
| |
| #[inline] |
| fn try_from(t: &'a str) -> Result<Self, Self::Error> { |
| TryFrom::try_from(t.as_bytes()) |
| } |
| } |
| |
| impl FromStr for Method { |
| type Err = InvalidMethod; |
| |
| #[inline] |
| fn from_str(t: &str) -> Result<Self, Self::Err> { |
| 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<InlineExtension, InvalidMethod> { |
| 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<AllocatedExtension, InvalidMethod> { |
| let mut data: Vec<u8> = 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); |
| } |
| } |