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