1use std::collections::HashMap;
5use std::result::Result;
6
7use crate::HttpHeaderError;
8use crate::RequestError;
9
10#[derive(Debug, Eq, Hash, PartialEq)]
12pub enum Header {
13 ContentLength,
15 ContentType,
17 Expect,
19 TransferEncoding,
21 Server,
23 Accept,
25 AcceptEncoding,
27}
28
29impl Header {
30 pub fn raw(&self) -> &'static [u8] {
32 match self {
33 Self::ContentLength => b"Content-Length",
34 Self::ContentType => b"Content-Type",
35 Self::Expect => b"Expect",
36 Self::TransferEncoding => b"Transfer-Encoding",
37 Self::Server => b"Server",
38 Self::Accept => b"Accept",
39 Self::AcceptEncoding => b"Accept-Encoding",
40 }
41 }
42
43 fn try_from(string: &[u8]) -> Result<Self, RequestError> {
50 if let Ok(mut utf8_string) = String::from_utf8(string.to_vec()) {
51 utf8_string.make_ascii_lowercase();
52 match utf8_string.trim() {
53 "content-length" => Ok(Self::ContentLength),
54 "content-type" => Ok(Self::ContentType),
55 "expect" => Ok(Self::Expect),
56 "transfer-encoding" => Ok(Self::TransferEncoding),
57 "server" => Ok(Self::Server),
58 "accept" => Ok(Self::Accept),
59 "accept-encoding" => Ok(Self::AcceptEncoding),
60 invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName(
61 invalid_key.to_string(),
62 ))),
63 }
64 } else {
65 Err(RequestError::InvalidRequest)
66 }
67 }
68}
69
70#[derive(Debug, Eq, PartialEq)]
82pub struct Headers {
83 content_length: u32,
86 expect: bool,
91 chunked: bool,
95 accept: MediaType,
98 custom_entries: HashMap<String, String>,
100}
101
102impl Default for Headers {
103 fn default() -> Self {
105 Self {
106 content_length: Default::default(),
107 expect: Default::default(),
108 chunked: Default::default(),
109 accept: MediaType::PlainText,
112 custom_entries: HashMap::default(),
113 }
114 }
115}
116
117impl Headers {
118 pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> {
138 match std::str::from_utf8(header_line) {
140 Ok(headers_str) => {
141 let entry = headers_str.splitn(2, ':').collect::<Vec<&str>>();
142 if entry.len() != 2 {
143 return Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat(
144 entry[0].to_string(),
145 )));
146 }
147 if let Ok(head) = Header::try_from(entry[0].as_bytes()) {
148 match head {
149 Header::ContentLength => match entry[1].trim().parse::<u32>() {
150 Ok(content_length) => {
151 self.content_length = content_length;
152 Ok(())
153 }
154 Err(_) => {
155 Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
156 entry[0].to_string(),
157 entry[1].to_string(),
158 )))
159 }
160 },
161 Header::ContentType => {
162 match MediaType::try_from(entry[1].trim().as_bytes()) {
163 Ok(_) => Ok(()),
164 Err(_) => Err(RequestError::HeaderError(
165 HttpHeaderError::UnsupportedValue(
166 entry[0].to_string(),
167 entry[1].to_string(),
168 ),
169 )),
170 }
171 }
172 Header::Accept => match MediaType::try_from(entry[1].trim().as_bytes()) {
173 Ok(accept_type) => {
174 self.accept = accept_type;
175 Ok(())
176 }
177 Err(_) => Err(RequestError::HeaderError(
178 HttpHeaderError::UnsupportedValue(
179 entry[0].to_string(),
180 entry[1].to_string(),
181 ),
182 )),
183 },
184 Header::TransferEncoding => match entry[1].trim() {
185 "chunked" => {
186 self.chunked = true;
187 Ok(())
188 }
189 "identity" => Ok(()),
190 _ => Err(RequestError::HeaderError(
191 HttpHeaderError::UnsupportedValue(
192 entry[0].to_string(),
193 entry[1].to_string(),
194 ),
195 )),
196 },
197 Header::Expect => match entry[1].trim() {
198 "100-continue" => {
199 self.expect = true;
200 Ok(())
201 }
202 _ => Err(RequestError::HeaderError(
203 HttpHeaderError::UnsupportedValue(
204 entry[0].to_string(),
205 entry[1].to_string(),
206 ),
207 )),
208 },
209 Header::Server => Ok(()),
210 Header::AcceptEncoding => Encoding::try_from(entry[1].trim().as_bytes()),
211 }
212 } else {
213 self.insert_custom_header(
214 entry[0].trim().to_string(),
215 entry[1].trim().to_string(),
216 )?;
217 Ok(())
218 }
219 }
220 Err(utf8_err) => Err(RequestError::HeaderError(
221 HttpHeaderError::InvalidUtf8String(utf8_err),
222 )),
223 }
224 }
225
226 pub fn content_length(&self) -> u32 {
228 self.content_length
229 }
230
231 #[allow(unused)]
233 pub fn chunked(&self) -> bool {
234 self.chunked
235 }
236
237 #[allow(unused)]
239 pub fn expect(&self) -> bool {
240 self.expect
241 }
242
243 pub fn accept(&self) -> MediaType {
245 self.accept
246 }
247
248 pub fn try_from(bytes: &[u8]) -> Result<Headers, RequestError> {
269 if let Ok(text) = std::str::from_utf8(bytes) {
271 let mut headers = Self::default();
272
273 let header_lines = text.split("\r\n");
274 for header_line in header_lines {
275 if header_line.is_empty() {
276 break;
277 }
278 match headers.parse_header_line(header_line.as_bytes()) {
279 Ok(_)
280 | Err(RequestError::HeaderError(HttpHeaderError::UnsupportedValue(_, _))) => {
281 continue
282 }
283 Err(e) => return Err(e),
284 };
285 }
286 return Ok(headers);
287 }
288 Err(RequestError::InvalidRequest)
289 }
290
291 pub fn set_accept(&mut self, media_type: MediaType) {
293 self.accept = media_type;
294 }
295
296 pub fn insert_custom_header(&mut self, key: String, value: String) -> Result<(), RequestError> {
298 self.custom_entries.insert(key, value);
299 Ok(())
300 }
301
302 pub fn custom_entries(&self) -> &HashMap<String, String> {
304 &self.custom_entries
305 }
306}
307
308#[derive(Clone, Copy, Debug, Eq, PartialEq)]
310pub struct Encoding {}
311
312impl Encoding {
313 pub fn try_from(bytes: &[u8]) -> Result<(), RequestError> {
332 if bytes.is_empty() {
333 return Err(RequestError::InvalidRequest);
334 }
335 match std::str::from_utf8(bytes) {
336 Ok(headers_str) => {
337 let entry = headers_str.split(',').collect::<Vec<&str>>();
338
339 for encoding in entry {
340 match encoding.trim() {
341 "identity;q=0" => {
342 Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
343 "Accept-Encoding".to_string(),
344 encoding.to_string(),
345 )))
346 }
347 "*;q=0" if !headers_str.contains("identity") => {
348 Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
349 "Accept-Encoding".to_string(),
350 encoding.to_string(),
351 )))
352 }
353 _ => Ok(()),
354 }?;
355 }
356 Ok(())
357 }
358 Err(utf8_err) => Err(RequestError::HeaderError(
359 HttpHeaderError::InvalidUtf8String(utf8_err),
360 )),
361 }
362 }
363}
364
365#[derive(Clone, Copy, Debug, Eq, PartialEq)]
367pub enum MediaType {
368 PlainText,
370 ApplicationJson,
372}
373
374impl Default for MediaType {
375 fn default() -> Self {
377 Self::ApplicationJson
378 }
379}
380
381impl MediaType {
382 pub fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
398 if bytes.is_empty() {
399 return Err(RequestError::InvalidRequest);
400 }
401 let utf8_slice =
402 String::from_utf8(bytes.to_vec()).map_err(|_| RequestError::InvalidRequest)?;
403 match utf8_slice.as_str().trim() {
404 "text/plain" => Ok(Self::PlainText),
405 "application/json" => Ok(Self::ApplicationJson),
406 _ => Err(RequestError::InvalidRequest),
407 }
408 }
409
410 pub fn as_str(self) -> &'static str {
421 match self {
422 Self::PlainText => "text/plain",
423 Self::ApplicationJson => "application/json",
424 }
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use std::collections::HashMap;
432
433 impl Headers {
434 pub fn new(content_length: u32, expect: bool, chunked: bool) -> Self {
435 Self {
436 content_length,
437 expect,
438 chunked,
439 accept: MediaType::PlainText,
440 custom_entries: HashMap::default(),
441 }
442 }
443 }
444
445 #[test]
446 fn test_default() {
447 let headers = Headers::default();
448 assert_eq!(headers.content_length(), 0);
449 assert!(!headers.chunked());
450 assert!(!headers.expect());
451 assert_eq!(headers.accept(), MediaType::PlainText);
452 assert_eq!(headers.custom_entries(), &HashMap::default());
453 }
454
455 #[test]
456 fn test_try_from_media() {
457 assert_eq!(
458 MediaType::try_from(b"application/json").unwrap(),
459 MediaType::ApplicationJson
460 );
461
462 assert_eq!(
463 MediaType::try_from(b"text/plain").unwrap(),
464 MediaType::PlainText
465 );
466
467 assert_eq!(
468 MediaType::try_from(b"").unwrap_err(),
469 RequestError::InvalidRequest
470 );
471
472 assert_eq!(
473 MediaType::try_from(b"application/json-patch").unwrap_err(),
474 RequestError::InvalidRequest
475 );
476 }
477
478 #[test]
479 fn test_media_as_str() {
480 let media_type = MediaType::ApplicationJson;
481 assert_eq!(media_type.as_str(), "application/json");
482
483 let media_type = MediaType::PlainText;
484 assert_eq!(media_type.as_str(), "text/plain");
485 }
486
487 #[test]
488 fn test_try_from_encoding() {
489 assert_eq!(
490 Encoding::try_from(b"").unwrap_err(),
491 RequestError::InvalidRequest
492 );
493
494 assert_eq!(
495 Encoding::try_from(b"identity;q=0").unwrap_err(),
496 RequestError::HeaderError(HttpHeaderError::InvalidValue(
497 "Accept-Encoding".to_string(),
498 "identity;q=0".to_string()
499 ))
500 );
501
502 assert!(Encoding::try_from(b"identity;q").is_ok());
503
504 assert_eq!(
505 Encoding::try_from(b"*;q=0").unwrap_err(),
506 RequestError::HeaderError(HttpHeaderError::InvalidValue(
507 "Accept-Encoding".to_string(),
508 "*;q=0".to_string()
509 ))
510 );
511
512 let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
513 assert!(Encoding::try_from(&bytes[..]).is_err());
514
515 assert!(Encoding::try_from(b"identity;q=1").is_ok());
516 assert!(Encoding::try_from(b"identity;q=0.1").is_ok());
517 assert!(Encoding::try_from(b"deflate, identity, *;q=0").is_ok());
518 assert!(Encoding::try_from(b"br").is_ok());
519 assert!(Encoding::try_from(b"compress").is_ok());
520 assert!(Encoding::try_from(b"gzip").is_ok());
521 }
522
523 #[test]
524 fn test_try_from_headers() {
525 let headers = Headers::try_from(
527 b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nAccept: application/json\r\nContent-Length: 55\r\n\r\n"
528 )
529 .unwrap();
530 assert_eq!(headers.content_length, 55);
531 assert_eq!(headers.accept, MediaType::ApplicationJson);
532 assert_eq!(
533 headers.custom_entries().get("Last-Modified").unwrap(),
534 "Tue, 15 Nov 1994 12:45:26 GMT"
535 );
536 assert_eq!(headers.custom_entries().len(), 1);
537
538 let headers = Headers::try_from(
541 b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nAccept:text/plain\r\nContent-Length: 49\r\n\r\n"
542 )
543 .unwrap();
544 assert_eq!(headers.content_length, 49);
545 assert_eq!(headers.accept, MediaType::PlainText);
546
547 let headers = Headers::try_from(
549 b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: 29\r\n\r\n",
550 )
551 .unwrap();
552 assert_eq!(headers.content_length, 29);
553
554 let headers = Headers::try_from(
556 b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nfoo: bar\r\nbar: 15\r\n\r\n",
557 )
558 .unwrap();
559 let custom_entries = headers.custom_entries();
560 assert_eq!(custom_entries.get("foo").unwrap(), "bar");
561 assert_eq!(custom_entries.get("bar").unwrap(), "15");
562 assert_eq!(custom_entries.len(), 3);
563
564 assert_eq!(
566 Headers::try_from(
567 b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: -55\r\n\r\n"
568 )
569 .unwrap_err(),
570 RequestError::HeaderError(HttpHeaderError::InvalidValue(
571 "Content-Length".to_string(),
572 " -55".to_string()
573 ))
574 );
575
576 let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
577 assert!(Headers::try_from(&bytes[..]).is_err());
579 }
580
581 #[test]
582 fn test_parse_header_line() {
583 let mut header = Headers::default();
584
585 assert_eq!(
587 header.parse_header_line(b"Expect"),
588 Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat(
589 "Expect".to_string()
590 )))
591 );
592
593 assert_eq!(
595 header.parse_header_line(b"Content-Length: five"),
596 Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
597 "Content-Length".to_string(),
598 " five".to_string()
599 )))
600 );
601
602 assert_eq!(
604 header.parse_header_line(b"Transfer-Encoding: gzip"),
605 Err(RequestError::HeaderError(
606 HttpHeaderError::UnsupportedValue(
607 "Transfer-Encoding".to_string(),
608 " gzip".to_string()
609 )
610 ))
611 );
612
613 assert_eq!(
615 header
616 .parse_header_line(b"Expect: 102-processing")
617 .unwrap_err(),
618 RequestError::HeaderError(HttpHeaderError::UnsupportedValue(
619 "Expect".to_string(),
620 " 102-processing".to_string()
621 ))
622 );
623
624 assert_eq!(
626 header
627 .parse_header_line(b"Content-Type: application/json-patch")
628 .unwrap_err(),
629 RequestError::HeaderError(HttpHeaderError::UnsupportedValue(
630 "Content-Type".to_string(),
631 " application/json-patch".to_string()
632 ))
633 );
634
635 let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
637 assert_eq!(
638 header.parse_header_line(&input[..]).unwrap_err(),
639 RequestError::HeaderError(HttpHeaderError::InvalidUtf8String(
640 String::from_utf8(input.to_vec()).unwrap_err().utf8_error()
641 ))
642 );
643
644 assert!(header
646 .parse_header_line(b"Transfer-Encoding: chunked")
647 .is_ok());
648 assert!(header.chunked());
649
650 assert!(header.parse_header_line(b"Expect: 100-continue").is_ok());
652 assert!(header.expect());
653
654 assert!(header
656 .parse_header_line(b"Content-Type: application/json")
657 .is_ok());
658
659 assert!(header
661 .parse_header_line(b"Accept: application/json")
662 .is_ok());
663 assert_eq!(header.accept, MediaType::ApplicationJson);
664 assert!(header.parse_header_line(b"Accept: text/plain").is_ok());
665 assert_eq!(header.accept, MediaType::PlainText);
666
667 assert!(header
669 .parse_header_line(b"Accept: application/json-patch")
670 .is_err());
671
672 assert_eq!(
674 header.parse_header_line(b"Content-Length: -1"),
675 Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
676 "Content-Length".to_string(),
677 " -1".to_string()
678 )))
679 );
680
681 assert!(header
682 .parse_header_line(b"Accept-Encoding: deflate")
683 .is_ok());
684 assert_eq!(
685 header.parse_header_line(b"Accept-Encoding: compress, identity;q=0"),
686 Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
687 "Accept-Encoding".to_string(),
688 " identity;q=0".to_string()
689 )))
690 );
691
692 assert_eq!(header.custom_entries().len(), 0);
694 assert!(header.parse_header_line(b"Custom-Header: foo").is_ok());
695 assert_eq!(
696 header.custom_entries().get("Custom-Header").unwrap(),
697 &"foo".to_string()
698 );
699 assert_eq!(header.custom_entries().len(), 1);
700 }
701
702 #[test]
703 fn test_parse_header_whitespace() {
704 let mut header = Headers::default();
705 assert!(header.parse_header_line(b"Content-Length:24").is_ok());
708 assert!(header.parse_header_line(b"Content-Length: 24").is_ok());
709
710 assert!(header
712 .parse_header_line(b"Content-Type:application/json")
713 .is_ok());
714 assert!(header
715 .parse_header_line(b"Content-Type: application/json")
716 .is_ok());
717
718 assert!(header.parse_header_line(b"Accept:application/json").is_ok());
720 assert!(header
721 .parse_header_line(b"Accept: application/json")
722 .is_ok());
723
724 assert!(header
726 .parse_header_line(b"Transfer-Encoding:chunked")
727 .is_ok());
728 assert!(header.chunked());
729 assert!(header
730 .parse_header_line(b"Transfer-Encoding: chunked")
731 .is_ok());
732 assert!(header.chunked());
733
734 assert!(header.parse_header_line(b"Server:xxx.yyy.zzz").is_ok());
736 assert!(header.parse_header_line(b"Server: xxx.yyy.zzz").is_ok());
737
738 assert!(header.parse_header_line(b"Expect:100-continue").is_ok());
740 assert!(header.parse_header_line(b"Expect: 100-continue").is_ok());
741
742 assert!(header.parse_header_line(b"Foo:bar").is_ok());
745 assert_eq!(header.custom_entries().get("Foo").unwrap(), "bar");
746 assert!(header.parse_header_line(b" Bar : foo ").is_ok());
747 assert_eq!(header.custom_entries().get("Bar").unwrap(), "foo");
748 }
749
750 #[test]
751 fn test_header_try_from() {
752 assert_eq!(
754 Header::try_from(b"Encoding").unwrap_err(),
755 RequestError::HeaderError(HttpHeaderError::UnsupportedName("encoding".to_string()))
756 );
757
758 let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
760 assert_eq!(
761 Header::try_from(&input[..]).unwrap_err(),
762 RequestError::InvalidRequest
763 );
764
765 let header = Header::try_from(b"Expect").unwrap();
767 assert_eq!(header.raw(), b"Expect");
768
769 let header = Header::try_from(b"Transfer-Encoding").unwrap();
770 assert_eq!(header.raw(), b"Transfer-Encoding");
771
772 let header = Header::try_from(b"content-length").unwrap();
773 assert_eq!(header.raw(), b"Content-Length");
774
775 let header = Header::try_from(b"Accept").unwrap();
776 assert_eq!(header.raw(), b"Accept");
777 }
778
779 #[test]
780 fn test_set_accept() {
781 let mut headers = Headers::default();
782 assert_eq!(headers.accept(), MediaType::PlainText);
783
784 headers.set_accept(MediaType::ApplicationJson);
785 assert_eq!(headers.accept(), MediaType::ApplicationJson);
786 }
787}