ic_pluto/
method.rs

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