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_extension_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}
474