1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// SPDX-License-Identifier: MIT
// Copyright 2023 IROX Contributors
//

use irox_tools::options::MaybeMap;
use irox_tools::read::Buffer;
use irox_tools::scanner::{LineEnding, ReadToken, Scanner};
use log::debug;
use std::collections::BTreeMap;
use std::io::{BufReader, Read, Write};

#[derive(Default, Debug, Clone)]
pub struct HttpHeaders {
    pub(crate) headers: BTreeMap<String, Vec<String>>,
}

pub struct HeaderResponse<T> {
    pub headers: HttpHeaders,
    pub remaining: Buffer<BufReader<T>>,
}

impl HttpHeaders {
    pub fn new_empty() -> Self {
        HttpHeaders::default()
    }
    pub fn new_request() -> Self {
        let mut resp = HttpHeaders::new_empty();
        resp.add_header("Accept", "*/*");
        resp.add_header("Connection", "close");
        resp
    }
    pub fn add_header<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, value: V) {
        let key = key.as_ref().to_string();
        let val = value.as_ref().to_string();
        self.headers.entry(key).or_default().push(val);
    }

    pub fn get_header<K: AsRef<str>>(&self, key: K) -> Option<&String> {
        let key = key.as_ref().to_string();
        self.headers.get(&key).maybe_map(|v| v.get(0))
    }

    pub fn get_headers<K: AsRef<str>>(&self, key: K) -> Option<&Vec<String>> {
        let key = key.as_ref().to_string();
        self.headers.get(&key)
    }
    pub fn headers(&self) -> &BTreeMap<String, Vec<String>> {
        &self.headers
    }
    pub fn contains_header<K: AsRef<str>>(&self, key: K) -> bool {
        self.headers.contains_key(key.as_ref())
    }
    pub fn maybe_add<K: AsRef<str>, V: AsRef<str>>(&mut self, key: K, val: V) {
        let key = key.as_ref();
        if !self.contains_header(key) {
            self.add_header(key, val);
        }
    }

    pub fn write_to<T: Write>(&self, out: &mut T) -> Result<(), std::io::Error> {
        for (name, value) in &self.headers {
            let vals = value.join(" ");
            let hdr = format!("{name}: {vals}");
            debug!("{hdr}");
            write!(out, "{hdr}\r\n")?;
        }
        debug!("");
        write!(out, "\r\n")?;
        Ok(())
    }

    pub fn add_line(&mut self, val: &[u8]) {
        let val = String::from_utf8_lossy(val);
        if let Some(idx) = val.find(':') {
            let (name, val) = val.split_at(idx);
            let val = val.get(2..).unwrap_or_default();
            self.add_header(name, val);
        }
    }

    pub fn create_from<T: Read + Sized>(input: T) -> Result<HeaderResponse<T>, std::io::Error> {
        let mut headers = HttpHeaders::new_empty();
        let mut scanner = Scanner::new_crlf(input);
        loop {
            let next = scanner.read_next()?;
            match next {
                ReadToken::Found { data, token } => {
                    if *token.get_response() != LineEnding::CarriageReturnLineFeed {
                        return Err(std::io::ErrorKind::InvalidData.into());
                    }
                    if data.is_empty() {
                        break;
                    }
                    headers.add_line(&data);
                }
                ReadToken::EndOfData { data } => {
                    headers.add_line(&data);
                }
                ReadToken::NotFound => {}
            }
        }
        Ok(HeaderResponse {
            headers,
            remaining: scanner.take_back(),
        })
    }
}