Skip to content

Instantly share code, notes, and snippets.

@xcuzalex
Created May 4, 2022 14:40
Show Gist options
  • Select an option

  • Save xcuzalex/5a6c5fe84de9923ba76225a9da6f95b2 to your computer and use it in GitHub Desktop.

Select an option

Save xcuzalex/5a6c5fe84de9923ba76225a9da6f95b2 to your computer and use it in GitHub Desktop.

Revisions

  1. xcuzalex created this gist May 4, 2022.
    473 changes: 473 additions & 0 deletions http-method.rs
    Original file line number Diff line number Diff line change
    @@ -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<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);
    }
    }