http/
method.rs

1//! The HTTP request method
2//!
3//! This module contains HTTP-method related structs and errors and such. The
4//! main type of this module, `Method`, is also reexported at the root of the
5//! crate as `http::Method` and is intended for import through that location
6//! primarily.
7//!
8//! # Examples
9//!
10//! ```
11//! use http::Method;
12//!
13//! assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
14//! assert!(Method::GET.is_idempotent());
15//! assert_eq!(Method::POST.as_str(), "POST");
16//! ```
17
18use 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/// The Request Method (VERB)
27///
28/// This type also contains constants for a number of common HTTP methods such
29/// as GET, POST, etc.
30///
31/// Currently includes 8 variants representing the 8 methods defined in
32/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH,
33/// and an Extension variant for all extensions.
34///
35/// # Examples
36///
37/// ```
38/// use http::Method;
39///
40/// assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
41/// assert!(Method::GET.is_idempotent());
42/// assert_eq!(Method::POST.as_str(), "POST");
43/// ```
44#[derive(Clone, PartialEq, Eq, Hash)]
45pub struct Method(Inner);
46
47/// A possible error value when converting `Method` from bytes.
48pub 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    // If the extension is short enough, store it inline
64    ExtensionInline(InlineExtension),
65    // Otherwise, allocate it
66    ExtensionAllocated(AllocatedExtension),
67}
68
69impl Method {
70    /// GET
71    pub const GET: Method = Method(Get);
72
73    /// POST
74    pub const POST: Method = Method(Post);
75
76    /// PUT
77    pub const PUT: Method = Method(Put);
78
79    /// DELETE
80    pub const DELETE: Method = Method(Delete);
81
82    /// HEAD
83    pub const HEAD: Method = Method(Head);
84
85    /// OPTIONS
86    pub const OPTIONS: Method = Method(Options);
87
88    /// CONNECT
89    pub const CONNECT: Method = Method(Connect);
90
91    /// PATCH
92    pub const PATCH: Method = Method(Patch);
93
94    /// TRACE
95    pub const TRACE: Method = Method(Trace);
96
97    /// Converts a slice of bytes to an HTTP method.
98    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    /// Whether a method is considered "safe", meaning the request is
144    /// essentially read-only.
145    ///
146    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
147    /// for more words.
148    pub fn is_safe(&self) -> bool {
149        matches!(self.0, Get | Head | Options | Trace)
150    }
151
152    /// Whether a method is considered "idempotent", meaning the request has
153    /// the same result if executed multiple times.
154    ///
155    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
156    /// more words.
157    pub fn is_idempotent(&self) -> bool {
158        match self.0 {
159            Put | Delete => true,
160            _ => self.is_safe(),
161        }
162    }
163
164    /// Return a &str representation of the HTTP method
165    #[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            // skip _priv noise
295            .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    // Invariant: the first self.1 bytes of self.0 are valid UTF-8.
313    pub struct InlineExtension([u8; InlineExtension::MAX], u8);
314
315    #[derive(Clone, PartialEq, Eq, Hash)]
316    // Invariant: self.0 contains valid UTF-8.
317    pub struct AllocatedExtension(Box<[u8]>);
318
319    impl InlineExtension {
320        // Method::from_bytes() assumes this is at least 7
321        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            // Invariant: write_checked ensures that the first src.len() bytes
329            // of data are valid UTF-8.
330            Ok(InlineExtension(data, src.len() as u8))
331        }
332
333        pub fn as_str(&self) -> &str {
334            let InlineExtension(ref data, len) = self;
335            // Safety: the invariant of InlineExtension ensures that the first
336            // len bytes of data contain valid UTF-8.
337            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            // Invariant: data is exactly src.len() long and write_checked
348            // ensures that the first src.len() bytes of data are valid UTF-8.
349            Ok(AllocatedExtension(data.into_boxed_slice()))
350        }
351
352        pub fn as_str(&self) -> &str {
353            // Safety: the invariant of AllocatedExtension ensures that self.0
354            // contains valid UTF-8.
355            unsafe { str::from_utf8_unchecked(&self.0) }
356        }
357    }
358
359    // From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can
360    // contain the following characters:
361    //
362    // ```
363    // method = token
364    // token = 1*tchar
365    // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
366    //     "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
367    // ```
368    //
369    // https://datatracker.ietf.org/doc/html/rfc9110#section-9.1
370    //
371    // Note that this definition means that any &[u8] that consists solely of valid
372    // characters is also valid UTF-8 because the valid method characters are a
373    // subset of the valid 1 byte UTF-8 encoding.
374    #[rustfmt::skip]
375    const METHOD_CHARS: [u8; 256] = [
376        //  0      1      2      3      4      5      6      7      8      9
377        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //   x
378        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  1x
379        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  2x
380        b'\0', b'\0', b'\0',  b'!', b'\0',  b'#',  b'$',  b'%',  b'&', b'\'', //  3x
381        b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', //  4x
382         b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', //  5x
383        b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
384         b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
385         b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
386         b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', //  9x
387         b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
388         b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
389         b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', // 12x
390        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x
391        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x
392        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x
393        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x
394        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x
395        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x
396        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x
397        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x
398        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x
399        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x
400        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x
401        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x
402        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              // 25x
403    ];
404
405    // write_checked ensures (among other things) that the first src.len() bytes
406    // of dst are valid UTF-8
407    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()); // invalid utf-8
444        assert!(Method::from_bytes(&[0x10]).is_err()); // invalid method characters
445    }
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}