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 
18 use self::Inner::*;
19 
20 use std::convert::AsRef;
21 use std::error::Error;
22 use std::str::FromStr;
23 use std::convert::TryFrom;
24 use 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)]
45 pub struct Method(Inner);
46 
47 /// A possible error value when converting `Method` from bytes.
48 pub struct InvalidMethod {
49     _priv: (),
50 }
51 
52 #[derive(Clone, PartialEq, Eq, Hash)]
53 enum 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([u8; MAX_INLINE], u8),
65     // Otherwise, allocate it
66     ExtensionAllocated(Box<[u8]>),
67 }
68 
69 const MAX_INLINE: usize = 15;
70 
71 // From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can
72 // contain the following characters:
73 //
74 // ```
75 // method = token
76 // token = 1*tchar
77 // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
78 //     "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
79 // ```
80 //
81 // https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method
82 //
83 const METHOD_CHARS: [u8; 256] = [
84     //  0      1      2      3      4      5      6      7      8      9
85     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //   x
86     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  1x
87     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  2x
88     b'\0', b'\0', b'\0',  b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  3x
89     b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', //  4x
90      b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', //  5x
91     b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
92      b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
93      b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
94      b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', //  9x
95      b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
96      b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
97      b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', // 12x
98     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x
99     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x
100     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x
101     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x
102     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x
103     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x
104     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x
105     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x
106     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x
107     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x
108     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x
109     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x
110     b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              // 25x
111 ];
112 
113 impl Method {
114     /// GET
115     pub const GET: Method = Method(Get);
116 
117     /// POST
118     pub const POST: Method = Method(Post);
119 
120     /// PUT
121     pub const PUT: Method = Method(Put);
122 
123     /// DELETE
124     pub const DELETE: Method = Method(Delete);
125 
126     /// HEAD
127     pub const HEAD: Method = Method(Head);
128 
129     /// OPTIONS
130     pub const OPTIONS: Method = Method(Options);
131 
132     /// CONNECT
133     pub const CONNECT: Method = Method(Connect);
134 
135     /// PATCH
136     pub const PATCH: Method = Method(Patch);
137 
138     /// TRACE
139     pub const TRACE: Method = Method(Trace);
140 
141     /// Converts a slice of bytes to an HTTP method.
from_bytes(src: &[u8]) -> Result<Method, InvalidMethod>142     pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
143         match src.len() {
144             0 => Err(InvalidMethod::new()),
145             3 => match src {
146                 b"GET" => Ok(Method(Get)),
147                 b"PUT" => Ok(Method(Put)),
148                 _ => Method::extension_inline(src),
149             },
150             4 => match src {
151                 b"POST" => Ok(Method(Post)),
152                 b"HEAD" => Ok(Method(Head)),
153                 _ => Method::extension_inline(src),
154             },
155             5 => match src {
156                 b"PATCH" => Ok(Method(Patch)),
157                 b"TRACE" => Ok(Method(Trace)),
158                 _ => Method::extension_inline(src),
159             },
160             6 => match src {
161                 b"DELETE" => Ok(Method(Delete)),
162                 _ => Method::extension_inline(src),
163             },
164             7 => match src {
165                 b"OPTIONS" => Ok(Method(Options)),
166                 b"CONNECT" => Ok(Method(Connect)),
167                 _ => Method::extension_inline(src),
168             },
169             _ => {
170                 if src.len() < MAX_INLINE {
171                     Method::extension_inline(src)
172                 } else {
173                     let mut data: Vec<u8> = vec![0; src.len()];
174 
175                     write_checked(src, &mut data)?;
176 
177                     Ok(Method(ExtensionAllocated(data.into_boxed_slice())))
178                 }
179             }
180         }
181     }
182 
extension_inline(src: &[u8]) -> Result<Method, InvalidMethod>183     fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
184         let mut data: [u8; MAX_INLINE] = Default::default();
185 
186         write_checked(src, &mut data)?;
187 
188         Ok(Method(ExtensionInline(data, src.len() as u8)))
189     }
190 
191     /// Whether a method is considered "safe", meaning the request is
192     /// essentially read-only.
193     ///
194     /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
195     /// for more words.
is_safe(&self) -> bool196     pub fn is_safe(&self) -> bool {
197         match self.0 {
198             Get | Head | Options | Trace => true,
199             _ => false,
200         }
201     }
202 
203     /// Whether a method is considered "idempotent", meaning the request has
204     /// the same result if executed multiple times.
205     ///
206     /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
207     /// more words.
is_idempotent(&self) -> bool208     pub fn is_idempotent(&self) -> bool {
209         match self.0 {
210             Put | Delete => true,
211             _ => self.is_safe(),
212         }
213     }
214 
215     /// Return a &str representation of the HTTP method
216     #[inline]
as_str(&self) -> &str217     pub fn as_str(&self) -> &str {
218         match self.0 {
219             Options => "OPTIONS",
220             Get => "GET",
221             Post => "POST",
222             Put => "PUT",
223             Delete => "DELETE",
224             Head => "HEAD",
225             Trace => "TRACE",
226             Connect => "CONNECT",
227             Patch => "PATCH",
228             ExtensionInline(ref data, len) => unsafe {
229                 str::from_utf8_unchecked(&data[..len as usize])
230             },
231             ExtensionAllocated(ref data) => unsafe { str::from_utf8_unchecked(data) },
232         }
233     }
234 }
235 
write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod>236 fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
237     for (i, &b) in src.iter().enumerate() {
238         let b = METHOD_CHARS[b as usize];
239 
240         if b == 0 {
241             return Err(InvalidMethod::new());
242         }
243 
244         dst[i] = b;
245     }
246 
247     Ok(())
248 }
249 
250 impl AsRef<str> for Method {
251     #[inline]
as_ref(&self) -> &str252     fn as_ref(&self) -> &str {
253         self.as_str()
254     }
255 }
256 
257 impl<'a> PartialEq<&'a Method> for Method {
258     #[inline]
eq(&self, other: &&'a Method) -> bool259     fn eq(&self, other: &&'a Method) -> bool {
260         self == *other
261     }
262 }
263 
264 impl<'a> PartialEq<Method> for &'a Method {
265     #[inline]
eq(&self, other: &Method) -> bool266     fn eq(&self, other: &Method) -> bool {
267         *self == other
268     }
269 }
270 
271 impl PartialEq<str> for Method {
272     #[inline]
eq(&self, other: &str) -> bool273     fn eq(&self, other: &str) -> bool {
274         self.as_ref() == other
275     }
276 }
277 
278 impl PartialEq<Method> for str {
279     #[inline]
eq(&self, other: &Method) -> bool280     fn eq(&self, other: &Method) -> bool {
281         self == other.as_ref()
282     }
283 }
284 
285 impl<'a> PartialEq<&'a str> for Method {
286     #[inline]
eq(&self, other: &&'a str) -> bool287     fn eq(&self, other: &&'a str) -> bool {
288         self.as_ref() == *other
289     }
290 }
291 
292 impl<'a> PartialEq<Method> for &'a str {
293     #[inline]
eq(&self, other: &Method) -> bool294     fn eq(&self, other: &Method) -> bool {
295         *self == other.as_ref()
296     }
297 }
298 
299 impl fmt::Debug for Method {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result300     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301         f.write_str(self.as_ref())
302     }
303 }
304 
305 impl fmt::Display for Method {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result306     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
307         fmt.write_str(self.as_ref())
308     }
309 }
310 
311 impl Default for Method {
312     #[inline]
default() -> Method313     fn default() -> Method {
314         Method::GET
315     }
316 }
317 
318 impl<'a> From<&'a Method> for Method {
319     #[inline]
from(t: &'a Method) -> Self320     fn from(t: &'a Method) -> Self {
321         t.clone()
322     }
323 }
324 
325 impl<'a> TryFrom<&'a [u8]> for Method {
326     type Error = InvalidMethod;
327 
328     #[inline]
try_from(t: &'a [u8]) -> Result<Self, Self::Error>329     fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
330         Method::from_bytes(t)
331     }
332 }
333 
334 impl<'a> TryFrom<&'a str> for Method {
335     type Error = InvalidMethod;
336 
337     #[inline]
try_from(t: &'a str) -> Result<Self, Self::Error>338     fn try_from(t: &'a str) -> Result<Self, Self::Error> {
339         TryFrom::try_from(t.as_bytes())
340     }
341 }
342 
343 impl FromStr for Method {
344     type Err = InvalidMethod;
345 
346     #[inline]
from_str(t: &str) -> Result<Self, Self::Err>347     fn from_str(t: &str) -> Result<Self, Self::Err> {
348         TryFrom::try_from(t)
349     }
350 }
351 
352 impl InvalidMethod {
new() -> InvalidMethod353     fn new() -> InvalidMethod {
354         InvalidMethod { _priv: () }
355     }
356 }
357 
358 impl fmt::Debug for InvalidMethod {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result359     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360         f.debug_struct("InvalidMethod")
361             // skip _priv noise
362             .finish()
363     }
364 }
365 
366 impl fmt::Display for InvalidMethod {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result367     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368         f.write_str("invalid HTTP method")
369     }
370 }
371 
372 impl Error for InvalidMethod {}
373 
374 #[test]
test_method_eq()375 fn test_method_eq() {
376     assert_eq!(Method::GET, Method::GET);
377     assert_eq!(Method::GET, "GET");
378     assert_eq!(&Method::GET, "GET");
379 
380     assert_eq!("GET", Method::GET);
381     assert_eq!("GET", &Method::GET);
382 
383     assert_eq!(&Method::GET, Method::GET);
384     assert_eq!(Method::GET, &Method::GET);
385 }
386 
387 #[test]
test_invalid_method()388 fn test_invalid_method() {
389     assert!(Method::from_str("").is_err());
390     assert!(Method::from_bytes(b"").is_err());
391 }
392 
393 #[test]
test_is_idempotent()394 fn test_is_idempotent() {
395     assert!(Method::OPTIONS.is_idempotent());
396     assert!(Method::GET.is_idempotent());
397     assert!(Method::PUT.is_idempotent());
398     assert!(Method::DELETE.is_idempotent());
399     assert!(Method::HEAD.is_idempotent());
400     assert!(Method::TRACE.is_idempotent());
401 
402     assert!(!Method::POST.is_idempotent());
403     assert!(!Method::CONNECT.is_idempotent());
404     assert!(!Method::PATCH.is_idempotent());
405 }
406