Created
May 4, 2022 14:40
-
-
Save xcuzalex/5a6c5fe84de9923ba76225a9da6f95b2 to your computer and use it in GitHub Desktop.
rs-libs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! 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); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment