1use self::extension::{AllocatedExtension, InlineExtension};
19use self::Inner::*;
20
21use std::convert::TryFrom;
22use std::error::Error;
23use std::str::FromStr;
24use std::{fmt, str};
25
26#[derive(Clone, PartialEq, Eq, Hash)]
45pub struct Method(Inner);
46
47pub struct InvalidMethod {
49    _priv: (),
50}
51
52#[derive(Clone, PartialEq, Eq, Hash)]
53enum Inner {
54    Options,
55    Get,
56    Post,
57    Put,
58    Delete,
59    Head,
60    Trace,
61    Connect,
62    Patch,
63    ExtensionInline(InlineExtension),
65    ExtensionAllocated(AllocatedExtension),
67}
68
69impl Method {
70    pub const GET: Method = Method(Get);
72
73    pub const POST: Method = Method(Post);
75
76    pub const PUT: Method = Method(Put);
78
79    pub const DELETE: Method = Method(Delete);
81
82    pub const HEAD: Method = Method(Head);
84
85    pub const OPTIONS: Method = Method(Options);
87
88    pub const CONNECT: Method = Method(Connect);
90
91    pub const PATCH: Method = Method(Patch);
93
94    pub const TRACE: Method = Method(Trace);
96
97    pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
99        match src.len() {
100            0 => Err(InvalidMethod::new()),
101            3 => match src {
102                b"GET" => Ok(Method(Get)),
103                b"PUT" => Ok(Method(Put)),
104                _ => Method::extension_inline(src),
105            },
106            4 => match src {
107                b"POST" => Ok(Method(Post)),
108                b"HEAD" => Ok(Method(Head)),
109                _ => Method::extension_inline(src),
110            },
111            5 => match src {
112                b"PATCH" => Ok(Method(Patch)),
113                b"TRACE" => Ok(Method(Trace)),
114                _ => Method::extension_inline(src),
115            },
116            6 => match src {
117                b"DELETE" => Ok(Method(Delete)),
118                _ => Method::extension_inline(src),
119            },
120            7 => match src {
121                b"OPTIONS" => Ok(Method(Options)),
122                b"CONNECT" => Ok(Method(Connect)),
123                _ => Method::extension_inline(src),
124            },
125            _ => {
126                if src.len() <= InlineExtension::MAX {
127                    Method::extension_inline(src)
128                } else {
129                    let allocated = AllocatedExtension::new(src)?;
130
131                    Ok(Method(ExtensionAllocated(allocated)))
132                }
133            }
134        }
135    }
136
137    fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
138        let inline = InlineExtension::new(src)?;
139
140        Ok(Method(ExtensionInline(inline)))
141    }
142
143    pub fn is_safe(&self) -> bool {
149        matches!(self.0, Get | Head | Options | Trace)
150    }
151
152    pub fn is_idempotent(&self) -> bool {
158        match self.0 {
159            Put | Delete => true,
160            _ => self.is_safe(),
161        }
162    }
163
164    #[inline]
166    pub fn as_str(&self) -> &str {
167        match self.0 {
168            Options => "OPTIONS",
169            Get => "GET",
170            Post => "POST",
171            Put => "PUT",
172            Delete => "DELETE",
173            Head => "HEAD",
174            Trace => "TRACE",
175            Connect => "CONNECT",
176            Patch => "PATCH",
177            ExtensionInline(ref inline) => inline.as_str(),
178            ExtensionAllocated(ref allocated) => allocated.as_str(),
179        }
180    }
181}
182
183impl AsRef<str> for Method {
184    #[inline]
185    fn as_ref(&self) -> &str {
186        self.as_str()
187    }
188}
189
190impl<'a> PartialEq<&'a Method> for Method {
191    #[inline]
192    fn eq(&self, other: &&'a Method) -> bool {
193        self == *other
194    }
195}
196
197impl<'a> PartialEq<Method> for &'a Method {
198    #[inline]
199    fn eq(&self, other: &Method) -> bool {
200        *self == other
201    }
202}
203
204impl PartialEq<str> for Method {
205    #[inline]
206    fn eq(&self, other: &str) -> bool {
207        self.as_ref() == other
208    }
209}
210
211impl PartialEq<Method> for str {
212    #[inline]
213    fn eq(&self, other: &Method) -> bool {
214        self == other.as_ref()
215    }
216}
217
218impl<'a> PartialEq<&'a str> for Method {
219    #[inline]
220    fn eq(&self, other: &&'a str) -> bool {
221        self.as_ref() == *other
222    }
223}
224
225impl<'a> PartialEq<Method> for &'a str {
226    #[inline]
227    fn eq(&self, other: &Method) -> bool {
228        *self == other.as_ref()
229    }
230}
231
232impl fmt::Debug for Method {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.write_str(self.as_ref())
235    }
236}
237
238impl fmt::Display for Method {
239    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
240        fmt.write_str(self.as_ref())
241    }
242}
243
244impl Default for Method {
245    #[inline]
246    fn default() -> Method {
247        Method::GET
248    }
249}
250
251impl<'a> From<&'a Method> for Method {
252    #[inline]
253    fn from(t: &'a Method) -> Self {
254        t.clone()
255    }
256}
257
258impl<'a> TryFrom<&'a [u8]> for Method {
259    type Error = InvalidMethod;
260
261    #[inline]
262    fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
263        Method::from_bytes(t)
264    }
265}
266
267impl<'a> TryFrom<&'a str> for Method {
268    type Error = InvalidMethod;
269
270    #[inline]
271    fn try_from(t: &'a str) -> Result<Self, Self::Error> {
272        TryFrom::try_from(t.as_bytes())
273    }
274}
275
276impl FromStr for Method {
277    type Err = InvalidMethod;
278
279    #[inline]
280    fn from_str(t: &str) -> Result<Self, Self::Err> {
281        TryFrom::try_from(t)
282    }
283}
284
285impl InvalidMethod {
286    fn new() -> InvalidMethod {
287        InvalidMethod { _priv: () }
288    }
289}
290
291impl fmt::Debug for InvalidMethod {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        f.debug_struct("InvalidMethod")
294            .finish()
296    }
297}
298
299impl fmt::Display for InvalidMethod {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        f.write_str("invalid HTTP method")
302    }
303}
304
305impl Error for InvalidMethod {}
306
307mod extension {
308    use super::InvalidMethod;
309    use std::str;
310
311    #[derive(Clone, PartialEq, Eq, Hash)]
312    pub struct InlineExtension([u8; InlineExtension::MAX], u8);
314
315    #[derive(Clone, PartialEq, Eq, Hash)]
316    pub struct AllocatedExtension(Box<[u8]>);
318
319    impl InlineExtension {
320        pub const MAX: usize = 15;
322
323        pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
324            let mut data: [u8; InlineExtension::MAX] = Default::default();
325
326            write_checked(src, &mut data)?;
327
328            Ok(InlineExtension(data, src.len() as u8))
331        }
332
333        pub fn as_str(&self) -> &str {
334            let InlineExtension(ref data, len) = self;
335            unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
338        }
339    }
340
341    impl AllocatedExtension {
342        pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
343            let mut data: Vec<u8> = vec![0; src.len()];
344
345            write_checked(src, &mut data)?;
346
347            Ok(AllocatedExtension(data.into_boxed_slice()))
350        }
351
352        pub fn as_str(&self) -> &str {
353            unsafe { str::from_utf8_unchecked(&self.0) }
356        }
357    }
358
359    #[rustfmt::skip]
375    const METHOD_CHARS: [u8; 256] = [
376        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0',  b'!', b'\0',  b'#',  b'$',  b'%',  b'&', b'\'', b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              ];
404
405    fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
408        for (i, &b) in src.iter().enumerate() {
409            let b = METHOD_CHARS[b as usize];
410
411            if b == 0 {
412                return Err(InvalidMethod::new());
413            }
414
415            dst[i] = b;
416        }
417
418        Ok(())
419    }
420}
421
422#[cfg(test)]
423mod test {
424    use super::*;
425
426    #[test]
427    fn test_method_eq() {
428        assert_eq!(Method::GET, Method::GET);
429        assert_eq!(Method::GET, "GET");
430        assert_eq!(&Method::GET, "GET");
431
432        assert_eq!("GET", Method::GET);
433        assert_eq!("GET", &Method::GET);
434
435        assert_eq!(&Method::GET, Method::GET);
436        assert_eq!(Method::GET, &Method::GET);
437    }
438
439    #[test]
440    fn test_invalid_method() {
441        assert!(Method::from_str("").is_err());
442        assert!(Method::from_bytes(b"").is_err());
443        assert!(Method::from_bytes(&[0xC0]).is_err()); assert!(Method::from_bytes(&[0x10]).is_err()); }
446
447    #[test]
448    fn test_is_idempotent() {
449        assert!(Method::OPTIONS.is_idempotent());
450        assert!(Method::GET.is_idempotent());
451        assert!(Method::PUT.is_idempotent());
452        assert!(Method::DELETE.is_idempotent());
453        assert!(Method::HEAD.is_idempotent());
454        assert!(Method::TRACE.is_idempotent());
455
456        assert!(!Method::POST.is_idempotent());
457        assert!(!Method::CONNECT.is_idempotent());
458        assert!(!Method::PATCH.is_idempotent());
459    }
460
461    #[test]
462    fn test_extension_method() {
463        assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
464        assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
465
466        let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
467        assert_eq!(Method::from_str(long_method).unwrap(), long_method);
468
469        let longest_inline_method = [b'A'; InlineExtension::MAX];
470        assert_eq!(
471            Method::from_bytes(&longest_inline_method).unwrap(),
472            Method(ExtensionInline(
473                InlineExtension::new(&longest_inline_method).unwrap()
474            ))
475        );
476        let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
477        assert_eq!(
478            Method::from_bytes(&shortest_allocated_method).unwrap(),
479            Method(ExtensionAllocated(
480                AllocatedExtension::new(&shortest_allocated_method).unwrap()
481            ))
482        );
483    }
484
485    #[test]
486    fn test_extension_method_chars() {
487        const VALID_METHOD_CHARS: &str =
488            "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
489
490        for c in VALID_METHOD_CHARS.chars() {
491            let c = c.to_string();
492
493            assert_eq!(
494                Method::from_str(&c).unwrap(),
495                c.as_str(),
496                "testing {c} is a valid method character"
497            );
498        }
499    }
500}