dbs_uhttp/common/
headers.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashMap;
5use std::result::Result;
6
7use crate::HttpHeaderError;
8use crate::RequestError;
9
10/// Wrapper over an HTTP Header type.
11#[derive(Debug, Eq, Hash, PartialEq)]
12pub enum Header {
13    /// Header `Content-Length`.
14    ContentLength,
15    /// Header `Content-Type`.
16    ContentType,
17    /// Header `Expect`.
18    Expect,
19    /// Header `Transfer-Encoding`.
20    TransferEncoding,
21    /// Header `Server`.
22    Server,
23    /// Header `Accept`
24    Accept,
25    /// Header `Accept-Encoding`
26    AcceptEncoding,
27}
28
29impl Header {
30    /// Returns a byte slice representation of the object.
31    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    /// Parses a byte slice into a Header structure. Header must be ASCII, so also
44    /// UTF-8 valid.
45    ///
46    /// # Errors
47    /// `InvalidRequest` is returned if slice contains invalid utf8 characters.
48    /// `InvalidHeader` is returned if unsupported header found.
49    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/// Wrapper over the list of headers associated with a Request that we need
71/// in order to parse the request correctly and be able to respond to it.
72///
73/// The only `Content-Type`s supported are `text/plain` and `application/json`, which are both
74/// in plain text actually and don't influence our parsing process.
75///
76/// All the other possible header fields are not necessary in order to serve this connection
77/// and, thus, are not of interest to us. However, we still look for header fields that might
78/// invalidate our request as we don't support the full set of HTTP/1.1 specification.
79/// Such header entries are "Transfer-Encoding: identity; q=0", which means a compression
80/// algorithm is applied to the body of the request, or "Expect: 103-checkpoint".
81#[derive(Debug, Eq, PartialEq)]
82pub struct Headers {
83    /// The `Content-Length` header field tells us how many bytes we need to receive
84    /// from the source after the headers.
85    content_length: u32,
86    /// The `Expect` header field is set when the headers contain the entry "Expect: 100-continue".
87    /// This means that, per HTTP/1.1 specifications, we must send a response with the status code
88    /// 100 after we have received the headers in order to receive the body of the request. This
89    /// field should be known immediately after parsing the headers.
90    expect: bool,
91    /// `Chunked` is a possible value of the `Transfer-Encoding` header field and every HTTP/1.1
92    /// server must support it. It is useful only when receiving the body of the request and should
93    /// be known immediately after parsing the headers.
94    chunked: bool,
95    /// `Accept` header might be used by HTTP clients to enforce server responses with content
96    /// formatted in a specific way.
97    accept: MediaType,
98    /// Hashmap reserved for storing custom headers.
99    custom_entries: HashMap<String, String>,
100}
101
102impl Default for Headers {
103    /// By default Requests are created with no headers.
104    fn default() -> Self {
105        Self {
106            content_length: Default::default(),
107            expect: Default::default(),
108            chunked: Default::default(),
109            // The default `Accept` media type is plain text. This is inclusive enough
110            // for structured and unstructured text.
111            accept: MediaType::PlainText,
112            custom_entries: HashMap::default(),
113        }
114    }
115}
116
117impl Headers {
118    /// Expects one header line and parses it, updating the header structure or returning an
119    /// error if the header is invalid.
120    ///
121    /// # Errors
122    /// `UnsupportedHeader` is returned when the parsed header line is not of interest
123    /// to us or when it is unrecognizable.
124    /// `InvalidHeader` is returned when the parsed header is formatted incorrectly or suggests
125    /// that the client is using HTTP features that we do not support in this implementation,
126    /// which invalidates the request.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// use dbs_uhttp::Headers;
132    ///
133    /// let mut request_header = Headers::default();
134    /// assert!(request_header.parse_header_line(b"Content-Length: 24").is_ok());
135    /// assert!(request_header.parse_header_line(b"Content-Length: 24: 2").is_err());
136    /// ```
137    pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> {
138        // Headers must be ASCII, so also UTF-8 valid.
139        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    /// Returns the content length of the body.
227    pub fn content_length(&self) -> u32 {
228        self.content_length
229    }
230
231    /// Returns `true` if the transfer encoding is chunked.
232    #[allow(unused)]
233    pub fn chunked(&self) -> bool {
234        self.chunked
235    }
236
237    /// Returns `true` if the client is expecting the code 100.
238    #[allow(unused)]
239    pub fn expect(&self) -> bool {
240        self.expect
241    }
242
243    /// Returns the `Accept` header `MediaType`.
244    pub fn accept(&self) -> MediaType {
245        self.accept
246    }
247
248    /// Parses a byte slice into a Headers structure for a HTTP request.
249    ///
250    /// The byte slice is expected to have the following format: </br>
251    ///     * Request Header Lines "<header_line> CRLF"- Optional </br>
252    /// There can be any number of request headers, including none, followed by
253    /// an extra sequence of Carriage Return and Line Feed.
254    /// All header fields are parsed. However, only the ones present in the
255    /// [`Headers`](struct.Headers.html) struct are relevant to us and stored
256    /// for future use.
257    ///
258    /// # Errors
259    /// The function returns `InvalidHeader` when parsing the byte stream fails.
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// use dbs_uhttp::Headers;
265    ///
266    /// let request_headers = Headers::try_from(b"Content-Length: 55\r\n\r\n");
267    /// ```
268    pub fn try_from(bytes: &[u8]) -> Result<Headers, RequestError> {
269        // Headers must be ASCII, so also UTF-8 valid.
270        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    /// Accept header setter.
292    pub fn set_accept(&mut self, media_type: MediaType) {
293        self.accept = media_type;
294    }
295
296    /// Insert a new custom header and value pair into the `HashMap`.
297    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    /// Returns the custom header `HashMap`.
303    pub fn custom_entries(&self) -> &HashMap<String, String> {
304        &self.custom_entries
305    }
306}
307
308/// Wrapper over supported AcceptEncoding.
309#[derive(Clone, Copy, Debug, Eq, PartialEq)]
310pub struct Encoding {}
311
312impl Encoding {
313    /// Parses a byte slice and checks if identity encoding is invalidated. Encoding
314    /// must be ASCII, so also UTF-8 valid.
315    ///
316    /// # Errors
317    /// `InvalidRequest` is returned when the byte stream is empty.
318    ///
319    /// `InvalidValue` is returned when the identity encoding is invalidated.
320    ///
321    /// `InvalidUtf8String` is returned when the byte stream contains invalid characters.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// use dbs_uhttp::Encoding;
327    ///
328    /// assert!(Encoding::try_from(b"deflate").is_ok());
329    /// assert!(Encoding::try_from(b"identity;q=0").is_err());
330    /// ```
331    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/// Wrapper over supported Media Types.
366#[derive(Clone, Copy, Debug, Eq, PartialEq)]
367pub enum MediaType {
368    /// Media Type: "text/plain".
369    PlainText,
370    /// Media Type: "application/json".
371    ApplicationJson,
372}
373
374impl Default for MediaType {
375    /// Default value for MediaType is application/json
376    fn default() -> Self {
377        Self::ApplicationJson
378    }
379}
380
381impl MediaType {
382    /// Parses a byte slice into a MediaType structure for a HTTP request. MediaType
383    /// must be ASCII, so also UTF-8 valid.
384    ///
385    /// # Errors
386    /// The function returns `InvalidRequest` when parsing the byte stream fails or
387    /// unsupported MediaType found.
388    ///
389    /// # Examples
390    ///
391    /// ```
392    /// use dbs_uhttp::MediaType;
393    ///
394    /// assert!(MediaType::try_from(b"application/json").is_ok());
395    /// assert!(MediaType::try_from(b"application/json2").is_err());
396    /// ```
397    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    /// Returns a static string representation of the object.
411    ///
412    /// # Examples
413    ///
414    /// ```
415    /// use dbs_uhttp::MediaType;
416    ///
417    /// let media_type = MediaType::ApplicationJson;
418    /// assert_eq!(media_type.as_str(), "application/json");
419    /// ```
420    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        // Valid headers.
526        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        // Valid headers. (${HEADER_NAME} : WHITESPACE ${HEADER_VALUE})
539        // Any number of whitespace characters should be accepted including zero.
540        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        // Valid headers.
548        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        // Custom headers only.
555        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        // Valid headers, invalid value.
565        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        // Invalid headers.
578        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        // Invalid header syntax.
586        assert_eq!(
587            header.parse_header_line(b"Expect"),
588            Err(RequestError::HeaderError(HttpHeaderError::InvalidFormat(
589                "Expect".to_string()
590            )))
591        );
592
593        // Invalid content length.
594        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        // Invalid transfer encoding.
603        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        // Invalid expect.
614        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        // Unsupported media type.
625        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        // Invalid input format.
636        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        // Test valid transfer encoding.
645        assert!(header
646            .parse_header_line(b"Transfer-Encoding: chunked")
647            .is_ok());
648        assert!(header.chunked());
649
650        // Test valid expect.
651        assert!(header.parse_header_line(b"Expect: 100-continue").is_ok());
652        assert!(header.expect());
653
654        // Test valid media type.
655        assert!(header
656            .parse_header_line(b"Content-Type: application/json")
657            .is_ok());
658
659        // Test valid accept media type.
660        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        // Test invalid accept media type.
668        assert!(header
669            .parse_header_line(b"Accept: application/json-patch")
670            .is_err());
671
672        // Invalid content length.
673        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        // Test custom header.
693        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        // Test that any number of whitespace characters are accepted before the header value.
706        // For Content-Length
707        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        // For ContentType
711        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        // For Accept
719        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        // For Transfer-Encoding
725        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        // For Server
735        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        // For Expect
739        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        // Test that custom headers' names and values are trimmed before being stored
743        // inside the HashMap.
744        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        // Bad header.
753        assert_eq!(
754            Header::try_from(b"Encoding").unwrap_err(),
755            RequestError::HeaderError(HttpHeaderError::UnsupportedName("encoding".to_string()))
756        );
757
758        // Invalid encoding.
759        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        // Test valid headers.
766        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}