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