irox_networking/http/
headers.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3//
4
5use irox_tools::options::MaybeMap;
6use irox_tools::read::Buffer;
7use irox_tools::scanner::{LineEnding, ReadToken, Scanner};
8use log::debug;
9use std::collections::BTreeMap;
10use std::io::{BufReader, Read, Write};
11
12#[derive(Default, Debug, Clone)]
13pub struct HttpHeaders {
14    pub(crate) headers: BTreeMap<String, Vec<String>>,
15}
16
17pub struct HeaderResponse<T> {
18    pub headers: HttpHeaders,
19    pub remaining: Buffer<BufReader<T>>,
20}
21
22impl HttpHeaders {
23    pub fn new_empty() -> Self {
24        HttpHeaders::default()
25    }
26    pub fn new_request() -> Self {
27        let mut resp = HttpHeaders::new_empty();
28        resp.add_header("Accept", "*/*");
29        resp.add_header("Connection", "close");
30        resp
31    }
32    pub fn add_header<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) {
33        let key = key.as_ref().to_string();
34        let val = value.as_ref().to_string();
35        self.headers.entry(key).or_default().push(val);
36    }
37
38    pub fn get_header<K: AsRef<str>>(&self, key: K) -> Option<&String> {
39        let key = key.as_ref().to_string();
40        self.headers.get(&key).maybe_map(|v| v.first())
41    }
42
43    pub fn get_headers<K: AsRef<str>>(&self, key: K) -> Option<&Vec<String>> {
44        let key = key.as_ref().to_string();
45        self.headers.get(&key)
46    }
47    pub fn headers(&self) -> &BTreeMap<String, Vec<String>> {
48        &self.headers
49    }
50    pub fn contains_header<K: AsRef<str>>(&self, key: K) -> bool {
51        self.headers.contains_key(key.as_ref())
52    }
53    pub fn maybe_add<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, val: V) {
54        let key = key.as_ref();
55        if !self.contains_header(key) {
56            self.add_header(key, val);
57        }
58    }
59
60    pub fn write_to<T: Write>(&self, out: &mut T) -> Result<(), std::io::Error> {
61        for (name, value) in &self.headers {
62            let vals = value.join(" ");
63            let hdr = format!("{name}: {vals}");
64            debug!("{hdr}");
65            write!(out, "{hdr}\r\n")?;
66        }
67        debug!("");
68        write!(out, "\r\n")?;
69        Ok(())
70    }
71
72    pub fn add_line(&mut self, val: &[u8]) {
73        let val = String::from_utf8_lossy(val);
74        if let Some(idx) = val.find(':') {
75            let (name, val) = val.split_at(idx);
76            let val = val.get(2..).unwrap_or_default();
77            self.add_header(name, val);
78        }
79    }
80
81    pub fn create_from<T: Read + Sized>(input: T) -> Result<HeaderResponse<T>, std::io::Error> {
82        let mut headers = HttpHeaders::new_empty();
83        let mut scanner = Scanner::new_crlf(input);
84        loop {
85            let next = scanner.read_next()?;
86            match next {
87                ReadToken::Found { data, token } => {
88                    if *token.get_response() != LineEnding::CarriageReturnLineFeed {
89                        return Err(std::io::ErrorKind::InvalidData.into());
90                    }
91                    if data.is_empty() {
92                        break;
93                    }
94                    headers.add_line(&data);
95                }
96                ReadToken::EndOfData { data } => {
97                    headers.add_line(&data);
98                }
99                ReadToken::NotFound => {}
100            }
101        }
102        Ok(HeaderResponse {
103            headers,
104            remaining: scanner.take_back(),
105        })
106    }
107}