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::Inner::*;
19use self::extension::{InlineExtension, AllocatedExtension};
20
21use std::convert::AsRef;
22use std::error::Error;
23use std::str::FromStr;
24use std::convert::TryFrom;
25use std::{fmt, str};
26
27/// The Request Method (VERB)
28///
29/// This type also contains constants for a number of common HTTP methods such
30/// as GET, POST, etc.
31///
32/// Currently includes 8 variants representing the 8 methods defined in
33/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH,
34/// and an Extension variant for all extensions.
35///
36/// # Examples
37///
38/// ```
39/// use http::Method;
40///
41/// assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
42/// assert!(Method::GET.is_idempotent());
43/// assert_eq!(Method::POST.as_str(), "POST");
44/// ```
45#[derive(Clone, PartialEq, Eq, Hash)]
46pub struct Method(Inner);
47
48/// A possible error value when converting `Method` from bytes.
49pub struct InvalidMethod {
50    _priv: (),
51}
52
53#[derive(Clone, PartialEq, Eq, Hash)]
54enum Inner {
55    Options,
56    Get,
57    Post,
58    Put,
59    Delete,
60    Head,
61    Trace,
62    Connect,
63    Patch,
64    // If the extension is short enough, store it inline
65    ExtensionInline(InlineExtension),
66    // Otherwise, allocate it
67    ExtensionAllocated(AllocatedExtension),
68}
69
70
71impl Method {
72    /// GET
73    pub const GET: Method = Method(Get);
74
75    /// POST
76    pub const POST: Method = Method(Post);
77
78    /// PUT
79    pub const PUT: Method = Method(Put);
80
81    /// DELETE
82    pub const DELETE: Method = Method(Delete);
83
84    /// HEAD
85    pub const HEAD: Method = Method(Head);
86
87    /// OPTIONS
88    pub const OPTIONS: Method = Method(Options);
89
90    /// CONNECT
91    pub const CONNECT: Method = Method(Connect);
92
93    /// PATCH
94    pub const PATCH: Method = Method(Patch);
95
96    /// TRACE
97    pub const TRACE: Method = Method(Trace);
98
99    /// Converts a slice of bytes to an HTTP method.
100    pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
101        match src.len() {
102            0 => Err(InvalidMethod::new()),
103            3 => match src {
104                b"GET" => Ok(Method(Get)),
105                b"PUT" => Ok(Method(Put)),
106                _ => Method::extension_inline(src),
107            },
108            4 => match src {
109                b"POST" => Ok(Method(Post)),
110                b"HEAD" => Ok(Method(Head)),
111                _ => Method::extension_inline(src),
112            },
113            5 => match src {
114                b"PATCH" => Ok(Method(Patch)),
115                b"TRACE" => Ok(Method(Trace)),
116                _ => Method::extension_inline(src),
117            },
118            6 => match src {
119                b"DELETE" => Ok(Method(Delete)),
120                _ => Method::extension_inline(src),
121            },
122            7 => match src {
123                b"OPTIONS" => Ok(Method(Options)),
124                b"CONNECT" => Ok(Method(Connect)),
125                _ => Method::extension_inline(src),
126            },
127            _ => {
128                if src.len() < InlineExtension::MAX {
129                    Method::extension_inline(src)
130                } else {
131                    let allocated = AllocatedExtension::new(src)?;
132
133                    Ok(Method(ExtensionAllocated(allocated)))
134                }
135            }
136        }
137    }
138
139    fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
140        let inline = InlineExtension::new(src)?;
141
142        Ok(Method(ExtensionInline(inline)))
143    }
144
145    /// Whether a method is considered "safe", meaning the request is
146    /// essentially read-only.
147    ///
148    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
149    /// for more words.
150    pub fn is_safe(&self) -> bool {
151        match self.0 {
152            Get | Head | Options | Trace => true,
153            _ => false,
154        }
155    }
156
157    /// Whether a method is considered "idempotent", meaning the request has
158    /// the same result if executed multiple times.
159    ///
160    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
161    /// more words.
162    pub fn is_idempotent(&self) -> bool {
163        match self.0 {
164            Put | Delete => true,
165            _ => self.is_safe(),
166        }
167    }
168
169    /// Return a &str representation of the HTTP method
170    #[inline]
171    pub fn as_str(&self) -> &str {
172        match self.0 {
173            Options => "OPTIONS",
174            Get => "GET",
175            Post => "POST",
176            Put => "PUT",
177            Delete => "DELETE",
178            Head => "HEAD",
179            Trace => "TRACE",
180            Connect => "CONNECT",
181            Patch => "PATCH",
182            ExtensionInline(ref inline) => inline.as_str(),
183            ExtensionAllocated(ref allocated) => allocated.as_str(),
184        }
185    }
186}
187
188impl AsRef<str> for Method {
189    #[inline]
190    fn as_ref(&self) -> &str {
191        self.as_str()
192    }
193}
194
195impl<'a> PartialEq<&'a Method> for Method {
196    #[inline]
197    fn eq(&self, other: &&'a Method) -> bool {
198        self == *other
199    }
200}
201
202impl<'a> PartialEq<Method> for &'a Method {
203    #[inline]
204    fn eq(&self, other: &Method) -> bool {
205        *self == other
206    }
207}
208
209impl PartialEq<str> for Method {
210    #[inline]
211    fn eq(&self, other: &str) -> bool {
212        self.as_ref() == other
213    }
214}
215
216impl PartialEq<Method> for str {
217    #[inline]
218    fn eq(&self, other: &Method) -> bool {
219        self == other.as_ref()
220    }
221}
222
223impl<'a> PartialEq<&'a str> for Method {
224    #[inline]
225    fn eq(&self, other: &&'a str) -> bool {
226        self.as_ref() == *other
227    }
228}
229
230impl<'a> PartialEq<Method> for &'a str {
231    #[inline]
232    fn eq(&self, other: &Method) -> bool {
233        *self == other.as_ref()
234    }
235}
236
237impl fmt::Debug for Method {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        f.write_str(self.as_ref())
240    }
241}
242
243impl fmt::Display for Method {
244    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
245        fmt.write_str(self.as_ref())
246    }
247}
248
249impl Default for Method {
250    #[inline]
251    fn default() -> Method {
252        Method::GET
253    }
254}
255
256impl<'a> From<&'a Method> for Method {
257    #[inline]
258    fn from(t: &'a Method) -> Self {
259        t.clone()
260    }
261}
262
263impl<'a> TryFrom<&'a [u8]> for Method {
264    type Error = InvalidMethod;
265
266    #[inline]
267    fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
268        Method::from_bytes(t)
269    }
270}
271
272impl<'a> TryFrom<&'a str> for Method {
273    type Error = InvalidMethod;
274
275    #[inline]
276    fn try_from(t: &'a str) -> Result<Self, Self::Error> {
277        TryFrom::try_from(t.as_bytes())
278    }
279}
280
281impl FromStr for Method {
282    type Err = InvalidMethod;
283
284    #[inline]
285    fn from_str(t: &str) -> Result<Self, Self::Err> {
286        TryFrom::try_from(t)
287    }
288}
289
290impl InvalidMethod {
291    fn new() -> InvalidMethod {
292        InvalidMethod { _priv: () }
293    }
294}
295
296impl fmt::Debug for InvalidMethod {
297    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
298        f.debug_struct("InvalidMethod")
299            // skip _priv noise
300            .finish()
301    }
302}
303
304impl fmt::Display for InvalidMethod {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.write_str("invalid HTTP method")
307    }
308}
309
310impl Error for InvalidMethod {}
311
312mod extension {
313    use super::InvalidMethod;
314    use std::str;
315
316    #[derive(Clone, PartialEq, Eq, Hash)]
317    // Invariant: the first self.1 bytes of self.0 are valid UTF-8.
318    pub struct InlineExtension([u8; InlineExtension::MAX], u8);
319
320    #[derive(Clone, PartialEq, Eq, Hash)]
321    // Invariant: self.0 contains valid UTF-8.
322    pub struct AllocatedExtension(Box<[u8]>);
323
324    impl InlineExtension {
325        // Method::from_bytes() assumes this is at least 7
326        pub const MAX: usize = 15;
327
328        pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
329            let mut data: [u8; InlineExtension::MAX] = Default::default();
330
331            write_checked(src, &mut data)?;
332
333            // Invariant: write_checked ensures that the first src.len() bytes
334            // of data are valid UTF-8.
335            Ok(InlineExtension(data, src.len() as u8))
336        }
337
338        pub fn as_str(&self) -> &str {
339            let InlineExtension(ref data, len) = self;
340            // Safety: the invariant of InlineExtension ensures that the first
341            // len bytes of data contain valid UTF-8.
342            unsafe {str::from_utf8_unchecked(&data[..*len as usize])}
343        }
344    }
345
346    impl AllocatedExtension {
347        pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
348            let mut data: Vec<u8> = vec![0; src.len()];
349
350            write_checked(src, &mut data)?;
351
352            // Invariant: data is exactly src.len() long and write_checked
353            // ensures that the first src.len() bytes of data are valid UTF-8.
354            Ok(AllocatedExtension(data.into_boxed_slice()))
355        }
356
357        pub fn as_str(&self) -> &str {
358            // Safety: the invariant of AllocatedExtension ensures that self.0
359            // contains valid UTF-8.
360            unsafe {str::from_utf8_unchecked(&self.0)}
361        }
362    }
363
364    // From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can
365    // contain the following characters:
366    //
367    // ```
368    // method = token
369    // token = 1*tchar
370    // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
371    //     "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
372    // ```
373    //
374    // https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method
375    //
376    // Note that this definition means that any &[u8] that consists solely of valid
377    // characters is also valid UTF-8 because the valid method characters are a
378    // subset of the valid 1 byte UTF-8 encoding.
379    const METHOD_CHARS: [u8; 256] = [
380        //  0      1      2      3      4      5      6      7      8      9
381        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //   x
382        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  1x
383        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  2x
384        b'\0', b'\0', b'\0',  b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  3x
385        b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', //  4x
386         b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', //  5x
387        b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
388         b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
389         b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
390         b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', //  9x
391         b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
392         b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
393         b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', // 12x
394        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x
395        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x
396        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x
397        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x
398        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x
399        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x
400        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x
401        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x
402        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x
403        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x
404        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x
405        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x
406        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              // 25x
407    ];
408
409    // write_checked ensures (among other things) that the first src.len() bytes
410    // of dst are valid UTF-8
411    fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
412        for (i, &b) in src.iter().enumerate() {
413            let b = METHOD_CHARS[b as usize];
414
415            if b == 0 {
416                return Err(InvalidMethod::new());
417            }
418
419            dst[i] = b;
420        }
421
422        Ok(())
423    }
424}
425
426#[cfg(test)]
427mod test {
428    use super::*;
429
430    #[test]
431    fn test_method_eq() {
432        assert_eq!(Method::GET, Method::GET);
433        assert_eq!(Method::GET, "GET");
434        assert_eq!(&Method::GET, "GET");
435
436        assert_eq!("GET", Method::GET);
437        assert_eq!("GET", &Method::GET);
438
439        assert_eq!(&Method::GET, Method::GET);
440        assert_eq!(Method::GET, &Method::GET);
441    }
442
443    #[test]
444    fn test_invalid_method() {
445        assert!(Method::from_str("").is_err());
446        assert!(Method::from_bytes(b"").is_err());
447        assert!(Method::from_bytes(&[0xC0]).is_err()); // invalid utf-8
448        assert!(Method::from_bytes(&[0x10]).is_err()); // invalid method characters
449    }
450
451    #[test]
452    fn test_is_idempotent() {
453        assert!(Method::OPTIONS.is_idempotent());
454        assert!(Method::GET.is_idempotent());
455        assert!(Method::PUT.is_idempotent());
456        assert!(Method::DELETE.is_idempotent());
457        assert!(Method::HEAD.is_idempotent());
458        assert!(Method::TRACE.is_idempotent());
459
460        assert!(!Method::POST.is_idempotent());
461        assert!(!Method::CONNECT.is_idempotent());
462        assert!(!Method::PATCH.is_idempotent());
463    }
464
465    #[test]
466    fn test_extention_method() {
467        assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
468        assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
469
470        let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
471        assert_eq!(Method::from_str(&long_method).unwrap(), long_method);
472    }
473}