leda/gemini/
header.rs

1use std::str::FromStr;
2
3use super::Error;
4
5/// Represents the header sent back from a server's response.
6#[derive(Clone)]
7pub struct Header {
8    /// The status code of the response.
9    pub status: StatusCode,
10    /// The meta information if any is provided. If the server didn't send any additional info
11    /// this string will be empty.
12    pub meta: String,
13}
14
15/// Represents a status code from a server's response header.
16#[derive(Clone, Copy)]
17pub enum StatusCode {
18    Input(InputCode),
19    Success,
20    Redirect(RedirectCode),
21    FailTemporary(FailTemporaryCode),
22    FailPermanent(FailPermanentCode),
23    CertFail(CertFailCode),
24}
25
26/// Represents the subtypes of input a server can ask for.
27#[derive(Clone, Copy)]
28pub enum InputCode {
29    Input,
30    Sensitive,
31}
32
33/// Represents the subtypes of redirects a server can ask for.
34#[derive(Clone, Copy)]
35pub enum RedirectCode {
36    Temporary,
37    Permanent,
38}
39
40/// Represents the subtypes of temporary failure a server can have.
41#[derive(Clone, Copy)]
42pub enum FailTemporaryCode {
43    Temporary,
44    ServerUnavailable,
45    CGIError,
46    ProxyError,
47    SlowDown,
48}
49/// Represents the subtypes of permanent failure a server can have.
50#[derive(Clone, Copy)]
51pub enum FailPermanentCode {
52    Permanent,
53    NotFound,
54    Gone,
55    ProxyRefused,
56    BadRequest,
57}
58
59/// Represents the subtypes of certificate failure a server can have.
60#[derive(Clone, Copy)]
61pub enum CertFailCode {
62    CertRequired,
63    CertNotAuthorized,
64    CertNotValid,
65}
66
67impl TryFrom<String> for Header {
68    type Error = Error;
69
70    fn try_from(header: String) -> Result<Self, Error> {
71        // The proper format of a header is `<STATUS><SPACE><META><CR><LF>`.
72        // We must check everything is properly formatted before we interpret any part of it.
73
74        // The checking involves splitting the string <STATUS> is always 2 integers which is 2
75        // bytes, <SPACE> is defined as 0x20, one byte, <META> must be at least 1 byte, and
76        // then <CR> is 1 byte, and <LF> is 1 byte.
77
78        let space_index = 2;
79        // Check if space is where it should be and split on it
80        let (status, meta) = if header.chars().nth(space_index).unwrap() == ' ' {
81            // we don't want to split at the header index because then it will include the space in the meta info
82            (&header[0..2], &header[3..])
83        } else {
84            return Err(Error::HeaderFormat(format!(
85                "Missing space after status, provided header: {}",
86                header
87            )));
88        };
89
90        // The status code must be two integers, no more no less.
91        if status.len() != 2 {
92            return Err(Error::HeaderFormat(format!(
93                "The status must be exactly two integers, provided header: {}",
94                header
95            )));
96        }
97        // The status meta info must end in "\r\n".
98        if !meta.ends_with("\r\n") {
99            return Err(Error::HeaderFormat(String::from(
100                "Meta information for the header doesn't end in <CR><LF>",
101            )));
102        }
103
104        // Remove the CRLF, trim isn't what we want because trailing white space can be part of <META>.
105        let meta = &meta[0..meta.len() - 2];
106        // Header <META> cannot be longer than 1024, if its then the entire header is invalid.
107        if meta.len() > 1024 {
108            return Err(Error::HeaderFormat(format!(
109                "The header's meta info was too long, maximum length is 1024 bytes, actual
110                length was {} bytes",
111                meta.len()
112            )));
113        }
114
115        let status = StatusCode::from_str(status)?;
116
117        Ok(Header {
118            status,
119            meta: meta.to_string(),
120        })
121    }
122}
123
124impl StatusCode {
125    pub fn to_str(&self) -> &'static str {
126        match self {
127            StatusCode::Input(InputCode::Input) => "10",
128            StatusCode::Input(InputCode::Sensitive) => "11",
129            StatusCode::Success => "20",
130            StatusCode::Redirect(RedirectCode::Temporary) => "30",
131            StatusCode::Redirect(RedirectCode::Permanent) => "31",
132            StatusCode::FailTemporary(FailTemporaryCode::Temporary) => "40",
133            StatusCode::FailTemporary(FailTemporaryCode::ServerUnavailable) => "41",
134            StatusCode::FailTemporary(FailTemporaryCode::CGIError) => "42",
135            StatusCode::FailTemporary(FailTemporaryCode::ProxyError) => "43",
136            StatusCode::FailTemporary(FailTemporaryCode::SlowDown) => "44",
137            StatusCode::FailPermanent(FailPermanentCode::Permanent) => "50",
138            StatusCode::FailPermanent(FailPermanentCode::NotFound) => "51",
139            StatusCode::FailPermanent(FailPermanentCode::Gone) => "52",
140            StatusCode::FailPermanent(FailPermanentCode::ProxyRefused) => "53",
141            StatusCode::FailPermanent(FailPermanentCode::BadRequest) => "59",
142            StatusCode::CertFail(CertFailCode::CertRequired) => "60",
143            StatusCode::CertFail(CertFailCode::CertNotAuthorized) => "61",
144            StatusCode::CertFail(CertFailCode::CertNotValid) => "62",
145        }
146    }
147}
148
149impl FromStr for StatusCode {
150    type Err = Error;
151
152    /// parses a given string and returns its equivalent [`StatusCode`]
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if the given string wasn't an exact match to any status code.
157    fn from_str(s: &str) -> Result<Self, Self::Err> {
158        Ok(match s {
159            "10" => StatusCode::Input(InputCode::Input),
160            "11" => StatusCode::Input(InputCode::Sensitive),
161            "20" => StatusCode::Success,
162            "30" => StatusCode::Redirect(RedirectCode::Temporary),
163            "31" => StatusCode::Redirect(RedirectCode::Permanent),
164            "40" => StatusCode::FailTemporary(FailTemporaryCode::Temporary),
165            "41" => StatusCode::FailTemporary(FailTemporaryCode::ServerUnavailable),
166            "42" => StatusCode::FailTemporary(FailTemporaryCode::CGIError),
167            "43" => StatusCode::FailTemporary(FailTemporaryCode::ProxyError),
168            "44" => StatusCode::FailTemporary(FailTemporaryCode::SlowDown),
169            "50" => StatusCode::FailPermanent(FailPermanentCode::Permanent),
170            "51" => StatusCode::FailPermanent(FailPermanentCode::NotFound),
171            "52" => StatusCode::FailPermanent(FailPermanentCode::Gone),
172            "53" => StatusCode::FailPermanent(FailPermanentCode::ProxyRefused),
173            "59" => StatusCode::FailPermanent(FailPermanentCode::BadRequest),
174            "60" => StatusCode::CertFail(CertFailCode::CertRequired),
175            "61" => StatusCode::CertFail(CertFailCode::CertNotAuthorized),
176            "62" => StatusCode::CertFail(CertFailCode::CertNotValid),
177            _ => {
178                return Err(Error::HeaderFormat(format!(
179                    "Header status code ({}) was not recognized",
180                    s
181                )))
182            }
183        })
184    }
185}
186
187impl std::fmt::Display for StatusCode {
188    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
189        write!(f, "{}", self.to_str())
190    }
191}
192
193impl std::fmt::Display for InputCode {
194    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
195        write!(f, "{}", StatusCode::Input(*self))
196    }
197}
198
199impl std::fmt::Display for RedirectCode {
200    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
201        write!(f, "{}", StatusCode::Redirect(*self))
202    }
203}
204
205impl std::fmt::Display for FailTemporaryCode {
206    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
207        write!(f, "{}", StatusCode::FailTemporary(*self))
208    }
209}
210
211impl std::fmt::Display for FailPermanentCode {
212    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
213        write!(f, "{}", StatusCode::FailPermanent(*self))
214    }
215}
216
217impl std::fmt::Display for CertFailCode {
218    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
219        write!(f, "{}", StatusCode::CertFail(*self))
220    }
221}
222
223impl std::fmt::Display for Header {
224    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225        write!(f, "{}: {}", self.status, self.meta)
226    }
227}