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 use self::extension::{InlineExtension, AllocatedExtension}; 20 21 use std::convert::AsRef; 22 use std::error::Error; 23 use std::str::FromStr; 24 use std::convert::TryFrom; 25 use 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)] 46 pub struct Method(Inner); 47 48 /// A possible error value when converting `Method` from bytes. 49 pub struct InvalidMethod { 50 _priv: (), 51 } 52 53 #[derive(Clone, PartialEq, Eq, Hash)] 54 enum 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 71 impl 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. from_bytes(src: &[u8]) -> Result<Method, InvalidMethod>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 extension_inline(src: &[u8]) -> Result<Method, InvalidMethod>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. is_safe(&self) -> bool150 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. is_idempotent(&self) -> bool162 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] as_str(&self) -> &str171 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 188 impl AsRef<str> for Method { 189 #[inline] as_ref(&self) -> &str190 fn as_ref(&self) -> &str { 191 self.as_str() 192 } 193 } 194 195 impl<'a> PartialEq<&'a Method> for Method { 196 #[inline] eq(&self, other: &&'a Method) -> bool197 fn eq(&self, other: &&'a Method) -> bool { 198 self == *other 199 } 200 } 201 202 impl<'a> PartialEq<Method> for &'a Method { 203 #[inline] eq(&self, other: &Method) -> bool204 fn eq(&self, other: &Method) -> bool { 205 *self == other 206 } 207 } 208 209 impl PartialEq<str> for Method { 210 #[inline] eq(&self, other: &str) -> bool211 fn eq(&self, other: &str) -> bool { 212 self.as_ref() == other 213 } 214 } 215 216 impl PartialEq<Method> for str { 217 #[inline] eq(&self, other: &Method) -> bool218 fn eq(&self, other: &Method) -> bool { 219 self == other.as_ref() 220 } 221 } 222 223 impl<'a> PartialEq<&'a str> for Method { 224 #[inline] eq(&self, other: &&'a str) -> bool225 fn eq(&self, other: &&'a str) -> bool { 226 self.as_ref() == *other 227 } 228 } 229 230 impl<'a> PartialEq<Method> for &'a str { 231 #[inline] eq(&self, other: &Method) -> bool232 fn eq(&self, other: &Method) -> bool { 233 *self == other.as_ref() 234 } 235 } 236 237 impl fmt::Debug for Method { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 239 f.write_str(self.as_ref()) 240 } 241 } 242 243 impl fmt::Display for Method { fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result244 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 245 fmt.write_str(self.as_ref()) 246 } 247 } 248 249 impl Default for Method { 250 #[inline] default() -> Method251 fn default() -> Method { 252 Method::GET 253 } 254 } 255 256 impl<'a> From<&'a Method> for Method { 257 #[inline] from(t: &'a Method) -> Self258 fn from(t: &'a Method) -> Self { 259 t.clone() 260 } 261 } 262 263 impl<'a> TryFrom<&'a [u8]> for Method { 264 type Error = InvalidMethod; 265 266 #[inline] try_from(t: &'a [u8]) -> Result<Self, Self::Error>267 fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> { 268 Method::from_bytes(t) 269 } 270 } 271 272 impl<'a> TryFrom<&'a str> for Method { 273 type Error = InvalidMethod; 274 275 #[inline] try_from(t: &'a str) -> Result<Self, Self::Error>276 fn try_from(t: &'a str) -> Result<Self, Self::Error> { 277 TryFrom::try_from(t.as_bytes()) 278 } 279 } 280 281 impl FromStr for Method { 282 type Err = InvalidMethod; 283 284 #[inline] from_str(t: &str) -> Result<Self, Self::Err>285 fn from_str(t: &str) -> Result<Self, Self::Err> { 286 TryFrom::try_from(t) 287 } 288 } 289 290 impl InvalidMethod { new() -> InvalidMethod291 fn new() -> InvalidMethod { 292 InvalidMethod { _priv: () } 293 } 294 } 295 296 impl fmt::Debug for InvalidMethod { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result297 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 298 f.debug_struct("InvalidMethod") 299 // skip _priv noise 300 .finish() 301 } 302 } 303 304 impl fmt::Display for InvalidMethod { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 306 f.write_str("invalid HTTP method") 307 } 308 } 309 310 impl Error for InvalidMethod {} 311 312 mod 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 new(src: &[u8]) -> Result<InlineExtension, InvalidMethod>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 as_str(&self) -> &str338 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 { new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod>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 as_str(&self) -> &str357 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 write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod>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)] 427 mod test { 428 use super::*; 429 430 #[test] test_method_eq()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] test_invalid_method()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] test_is_idempotent()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] test_extention_method()466 fn test_extention_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