1use ascii::{AsciiStr, AsciiString, FromAsciiError};
2use std::cmp::Ordering;
3use std::fmt::{self, Display, Formatter};
4use std::str::FromStr;
5
6#[derive(Eq, PartialEq, Copy, Clone, Debug, Ord, PartialOrd)]
8pub struct StatusCode(pub u16);
9
10impl StatusCode {
11 pub fn default_reason_phrase(&self) -> &'static str {
14 match self.0 {
15 100 => "Continue",
16 101 => "Switching Protocols",
17 102 => "Processing",
18 103 => "Early Hints",
19
20 200 => "OK",
21 201 => "Created",
22 202 => "Accepted",
23 203 => "Non-Authoritative Information",
24 204 => "No Content",
25 205 => "Reset Content",
26 206 => "Partial Content",
27 207 => "Multi-Status",
28 208 => "Already Reported",
29 226 => "IM Used",
30
31 300 => "Multiple Choices",
32 301 => "Moved Permanently",
33 302 => "Found",
34 303 => "See Other",
35 304 => "Not Modified",
36 305 => "Use Proxy",
37 307 => "Temporary Redirect",
38 308 => "Permanent Redirect",
39
40 400 => "Bad Request",
41 401 => "Unauthorized",
42 402 => "Payment Required",
43 403 => "Forbidden",
44 404 => "Not Found",
45 405 => "Method Not Allowed",
46 406 => "Not Acceptable",
47 407 => "Proxy Authentication Required",
48 408 => "Request Timeout",
49 409 => "Conflict",
50 410 => "Gone",
51 411 => "Length Required",
52 412 => "Precondition Failed",
53 413 => "Payload Too Large",
54 414 => "URI Too Long",
55 415 => "Unsupported Media Type",
56 416 => "Range Not Satisfiable",
57 417 => "Expectation Failed",
58 421 => "Misdirected Request",
59 422 => "Unprocessable Entity",
60 423 => "Locked",
61 424 => "Failed Dependency",
62 426 => "Upgrade Required",
63 428 => "Precondition Required",
64 429 => "Too Many Requests",
65 431 => "Request Header Fields Too Large",
66 451 => "Unavailable For Legal Reasons",
67
68 500 => "Internal Server Error",
69 501 => "Not Implemented",
70 502 => "Bad Gateway",
71 503 => "Service Unavailable",
72 504 => "Gateway Timeout",
73 505 => "HTTP Version Not Supported",
74 506 => "Variant Also Negotiates",
75 507 => "Insufficient Storage",
76 508 => "Loop Detected",
77 510 => "Not Extended",
78 511 => "Network Authentication Required",
79 _ => "Unknown",
80 }
81 }
82}
83
84impl From<i8> for StatusCode {
85 fn from(in_code: i8) -> StatusCode {
86 StatusCode(in_code as u16)
87 }
88}
89
90impl From<u8> for StatusCode {
91 fn from(in_code: u8) -> StatusCode {
92 StatusCode(in_code as u16)
93 }
94}
95
96impl From<i16> for StatusCode {
97 fn from(in_code: i16) -> StatusCode {
98 StatusCode(in_code as u16)
99 }
100}
101
102impl From<u16> for StatusCode {
103 fn from(in_code: u16) -> StatusCode {
104 StatusCode(in_code)
105 }
106}
107
108impl From<i32> for StatusCode {
109 fn from(in_code: i32) -> StatusCode {
110 StatusCode(in_code as u16)
111 }
112}
113
114impl From<u32> for StatusCode {
115 fn from(in_code: u32) -> StatusCode {
116 StatusCode(in_code as u16)
117 }
118}
119
120impl AsRef<u16> for StatusCode {
121 fn as_ref(&self) -> &u16 {
122 &self.0
123 }
124}
125
126impl PartialEq<u16> for StatusCode {
127 fn eq(&self, other: &u16) -> bool {
128 &self.0 == other
129 }
130}
131
132impl PartialEq<StatusCode> for u16 {
133 fn eq(&self, other: &StatusCode) -> bool {
134 self == &other.0
135 }
136}
137
138impl PartialOrd<u16> for StatusCode {
139 fn partial_cmp(&self, other: &u16) -> Option<Ordering> {
140 self.0.partial_cmp(other)
141 }
142}
143
144impl PartialOrd<StatusCode> for u16 {
145 fn partial_cmp(&self, other: &StatusCode) -> Option<Ordering> {
146 self.partial_cmp(&other.0)
147 }
148}
149
150#[derive(Debug, Clone)]
152pub struct Header {
153 pub field: HeaderField,
154 pub value: AsciiString,
155}
156
157impl Header {
158 #[allow(clippy::result_unit_err)]
166 pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
167 where
168 B1: Into<Vec<u8>> + AsRef<[u8]>,
169 B2: Into<Vec<u8>> + AsRef<[u8]>,
170 {
171 let header = HeaderField::from_bytes(header).or(Err(()))?;
172 let value = AsciiString::from_ascii(value).or(Err(()))?;
173
174 Ok(Header {
175 field: header,
176 value,
177 })
178 }
179}
180
181impl FromStr for Header {
182 type Err = ();
183
184 fn from_str(input: &str) -> Result<Header, ()> {
185 let mut elems = input.splitn(2, ':');
186
187 let field = elems.next().and_then(|f| f.parse().ok()).ok_or(())?;
188 let value = elems
189 .next()
190 .and_then(|v| AsciiString::from_ascii(v.trim()).ok())
191 .ok_or(())?;
192
193 Ok(Header { field, value })
194 }
195}
196
197impl Display for Header {
198 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
199 write!(formatter, "{}: {}", self.field, self.value.as_str())
200 }
201}
202
203#[derive(Debug, Clone, Eq)]
207pub struct HeaderField(AsciiString);
208
209impl HeaderField {
210 pub fn from_bytes<B>(bytes: B) -> Result<HeaderField, FromAsciiError<B>>
211 where
212 B: Into<Vec<u8>> + AsRef<[u8]>,
213 {
214 AsciiString::from_ascii(bytes).map(HeaderField)
215 }
216
217 pub fn as_str(&self) -> &AsciiStr {
218 &self.0
219 }
220
221 pub fn equiv(&self, other: &'static str) -> bool {
222 other.eq_ignore_ascii_case(self.as_str().as_str())
223 }
224}
225
226impl FromStr for HeaderField {
227 type Err = ();
228
229 fn from_str(s: &str) -> Result<HeaderField, ()> {
230 if s.contains(char::is_whitespace) {
231 Err(())
232 } else {
233 AsciiString::from_ascii(s).map(HeaderField).map_err(|_| ())
234 }
235 }
236}
237
238impl Display for HeaderField {
239 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
240 write!(formatter, "{}", self.0.as_str())
241 }
242}
243
244impl PartialEq for HeaderField {
245 fn eq(&self, other: &HeaderField) -> bool {
246 let self_str: &str = self.as_str().as_ref();
247 let other_str = other.as_str().as_ref();
248 self_str.eq_ignore_ascii_case(other_str)
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Hash)]
257pub enum Method {
258 Get,
260
261 Head,
263
264 Post,
266
267 Put,
269
270 Delete,
272
273 Connect,
275
276 Options,
278
279 Trace,
281
282 Patch,
284
285 NonStandard(AsciiString),
287}
288
289impl Method {
290 pub fn as_str(&self) -> &str {
291 match *self {
292 Method::Get => "GET",
293 Method::Head => "HEAD",
294 Method::Post => "POST",
295 Method::Put => "PUT",
296 Method::Delete => "DELETE",
297 Method::Connect => "CONNECT",
298 Method::Options => "OPTIONS",
299 Method::Trace => "TRACE",
300 Method::Patch => "PATCH",
301 Method::NonStandard(ref s) => s.as_str(),
302 }
303 }
304}
305
306impl FromStr for Method {
307 type Err = ();
308
309 fn from_str(s: &str) -> Result<Method, ()> {
310 Ok(match s {
311 "GET" => Method::Get,
312 "HEAD" => Method::Head,
313 "POST" => Method::Post,
314 "PUT" => Method::Put,
315 "DELETE" => Method::Delete,
316 "CONNECT" => Method::Connect,
317 "OPTIONS" => Method::Options,
318 "TRACE" => Method::Trace,
319 "PATCH" => Method::Patch,
320 s => {
321 let ascii_string = AsciiString::from_ascii(s).map_err(|_| ())?;
322 Method::NonStandard(ascii_string)
323 }
324 })
325 }
326}
327
328impl Display for Method {
329 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
330 write!(formatter, "{}", self.as_str())
331 }
332}
333
334#[allow(clippy::upper_case_acronyms)]
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct HTTPVersion(pub u8, pub u8);
338
339impl Display for HTTPVersion {
340 fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
341 write!(formatter, "{}.{}", self.0, self.1)
342 }
343}
344
345impl Ord for HTTPVersion {
346 fn cmp(&self, other: &Self) -> Ordering {
347 let HTTPVersion(my_major, my_minor) = *self;
348 let HTTPVersion(other_major, other_minor) = *other;
349
350 if my_major != other_major {
351 return my_major.cmp(&other_major);
352 }
353
354 my_minor.cmp(&other_minor)
355 }
356}
357
358impl PartialOrd for HTTPVersion {
359 fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
360 Some(self.cmp(other))
361 }
362}
363
364impl PartialEq<(u8, u8)> for HTTPVersion {
365 fn eq(&self, &(major, minor): &(u8, u8)) -> bool {
366 self.eq(&HTTPVersion(major, minor))
367 }
368}
369
370impl PartialEq<HTTPVersion> for (u8, u8) {
371 fn eq(&self, other: &HTTPVersion) -> bool {
372 let &(major, minor) = self;
373 HTTPVersion(major, minor).eq(other)
374 }
375}
376
377impl PartialOrd<(u8, u8)> for HTTPVersion {
378 fn partial_cmp(&self, &(major, minor): &(u8, u8)) -> Option<Ordering> {
379 self.partial_cmp(&HTTPVersion(major, minor))
380 }
381}
382
383impl PartialOrd<HTTPVersion> for (u8, u8) {
384 fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
385 let &(major, minor) = self;
386 HTTPVersion(major, minor).partial_cmp(other)
387 }
388}
389
390impl From<(u8, u8)> for HTTPVersion {
391 fn from((major, minor): (u8, u8)) -> HTTPVersion {
392 HTTPVersion(major, minor)
393 }
394}
395
396#[cfg(test)]
397mod test {
398 use super::Header;
399 use httpdate::HttpDate;
400 use std::time::{Duration, SystemTime};
401
402 #[test]
403 fn test_parse_header() {
404 let header: Header = "Content-Type: text/html".parse().unwrap();
405
406 assert!(header.field.equiv(&"content-type"));
407 assert!(header.value.as_str() == "text/html");
408
409 assert!("hello world".parse::<Header>().is_err());
410 }
411
412 #[test]
413 fn formats_date_correctly() {
414 let http_date = HttpDate::from(SystemTime::UNIX_EPOCH + Duration::from_secs(420895020));
415
416 assert_eq!(http_date.to_string(), "Wed, 04 May 1983 11:17:00 GMT")
417 }
418
419 #[test]
420 fn test_parse_header_with_doublecolon() {
421 let header: Header = "Time: 20: 34".parse().unwrap();
422
423 assert!(header.field.equiv(&"time"));
424 assert!(header.value.as_str() == "20: 34");
425 }
426
427 #[test]
431 fn test_strict_headers() {
432 assert!("Transfer-Encoding : chunked".parse::<Header>().is_err());
433 assert!(" Transfer-Encoding: chunked".parse::<Header>().is_err());
434 assert!("Transfer Encoding: chunked".parse::<Header>().is_err());
435 assert!(" Transfer\tEncoding : chunked".parse::<Header>().is_err());
436 assert!("Transfer-Encoding: chunked".parse::<Header>().is_ok());
437 assert!("Transfer-Encoding: chunked ".parse::<Header>().is_ok());
438 assert!("Transfer-Encoding: chunked ".parse::<Header>().is_ok());
439 }
440}