http1_spec/
body_framing.rs

1use std::io::{Error as IoError, ErrorKind as IoErrorKind};
2
3use http::{
4    header::{CONTENT_LENGTH, TRANSFER_ENCODING},
5    HeaderMap, HeaderValue, Version,
6};
7
8use crate::CHUNKED;
9
10//
11//
12//
13// ref https://github.com/apple/swift-nio/blob/2.20.2/Sources/NIOHTTP1/HTTPEncoder.swift#L89
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub enum BodyFraming {
16    ContentLength(usize),
17    Chunked,
18    Neither,
19}
20
21impl BodyFraming {
22    pub fn update_content_length_value(&mut self, value: usize) -> Result<(), IoError> {
23        match self {
24            Self::ContentLength(n) => {
25                *n = value;
26                Ok(())
27            }
28            _ => Err(IoError::new(
29                IoErrorKind::InvalidInput,
30                "Not in ContentLength",
31            )),
32        }
33    }
34}
35
36pub trait BodyFramingDetector {
37    fn detect(&self) -> Result<BodyFraming, IoError>;
38}
39impl BodyFramingDetector for (&HeaderMap<HeaderValue>, &Version) {
40    fn detect(&self) -> Result<BodyFraming, IoError> {
41        let (headers, version) = *self;
42
43        if let Some(header_value) = headers.get(CONTENT_LENGTH) {
44            let value_str = header_value
45                .to_str()
46                .map_err(|err| IoError::new(IoErrorKind::InvalidInput, err))?;
47            let value: usize = value_str
48                .parse()
49                .map_err(|err| IoError::new(IoErrorKind::InvalidInput, err))?;
50            return Ok(BodyFraming::ContentLength(value));
51        }
52
53        if version == &Version::HTTP_11 {
54            if let Some(header_value) = headers.get(TRANSFER_ENCODING) {
55                if header_value == CHUNKED {
56                    return Ok(BodyFraming::Chunked);
57                }
58            }
59        }
60
61        Ok(BodyFraming::Neither)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn detect() -> Result<(), Box<dyn std::error::Error>> {
71        let mut header_map = HeaderMap::new();
72
73        header_map.insert("Content-Length", "1".parse().unwrap());
74        assert_eq!(
75            (&header_map, &Version::HTTP_11).detect()?,
76            BodyFraming::ContentLength(1)
77        );
78
79        header_map.clear();
80        header_map.insert("Transfer-Encoding", "chunked".parse().unwrap());
81        assert_eq!(
82            (&header_map, &Version::HTTP_10).detect()?,
83            BodyFraming::Neither
84        );
85        assert_eq!(
86            (&header_map, &Version::HTTP_11).detect()?,
87            BodyFraming::Chunked
88        );
89
90        header_map.clear();
91        assert_eq!(
92            (&header_map, &Version::HTTP_11).detect()?,
93            BodyFraming::Neither
94        );
95
96        Ok(())
97    }
98}