1
2use std::fmt::Display;
3use std::str::{self, Utf8Error, Chars};
4use std::error;
5
6use colored::{Colorize, ColoredString};
7
8#[derive(Debug)]
9pub enum ParseError {
10 ParamNameInvalid(String),
11 UrlDecodeNotUtf8(Utf8Error),
12 MalformedParams(String, String),
13}
14impl Display for ParseError {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "{:?}", self)
17 }
18}
19impl error::Error for ParseError {}
20
21pub fn code_color(code: &str) -> ColoredString {
23 match code.as_bytes().first() {
24 Some(n) => match n {
25 b'1' => code.white().bold(),
26 b'2' => code.green(),
27 b'3' => code.cyan().bold(),
28 b'4' => code.yellow(),
29 b'5' => code.red().bold(),
30 _ => code.normal(),
31 },
32 None => {
33 "<no response code>".red().bold()
34 },
35 }
36}
37
38pub fn escape_html(str: &str) -> String {
40 let mut out = String::new();
41 for ch in str.chars() {
42 match ch {
43 '&' => out.push_str("&"),
44 '<' => out.push_str("<"),
45 '>' => out.push_str(">"),
46 '"' => out.push_str("""),
47 '\'' => out.push_str("'"),
48 ';' => out.push_str(";"),
49 _ => out.push(ch),
50 }
51 }
52 out
53}
54
55pub fn strip_for_terminal(to_strip: &str) -> String {
57 to_strip.chars()
58 .filter(|chr| !matches!(chr, '\x07'..='\x0D'))
59 .collect::<String>()
60}
61
62fn from_hex(byte: u8) -> Option<u8> {
64 match byte {
65 b'a'..=b'f' => Some(byte - b'a' + 10),
66 b'A'..=b'F' => Some(byte - b'A' + 10),
67 b'0'..=b'9' => Some(byte - b'0'),
68 _ => None,
69 }
70}
71
72pub fn url_decode(to_decode: &str) -> Result<String, ParseError> {
74 let mut build: Vec<u8> = Vec::with_capacity(to_decode.len());
75 let mut bytes = to_decode.bytes();
76 while let Some(c) = bytes.next() {
77 match c {
78 b'%' => { match bytes.next() {
80 None => build.push(b'%'),
81 Some(top) => match from_hex(top) {
82 None => {
83 build.push(b'%');
84 build.push(top);
85 },
86 Some(t) => match bytes.next() {
87 None => {
88 build.push(b'%');
89 build.push(top);
90 },
91 Some(bottom) => match from_hex(bottom) {
92 None => { build.push(b'%');
94 build.push(top);
95 build.push(bottom);
96 },
97 Some(b) => {
98 build.push((t << 4) | b);
100 },
101 },
102 },
103 },
104 };
105 },
106 b'+' => build.push(b' '),
107 b'\0' => break,
108 other => build.push(other),
109 }
110 }
111
112 Ok(str::from_utf8(&build)
114 .map_err(ParseError::UrlDecodeNotUtf8)?.to_string())
115}
116
117const EOF_CHAR: char = '\0';
118pub(crate) struct KVParser<'buf> {
120 len_remaining: usize,
121 chars: Chars<'buf>,
122}
123
124impl<'buf> KVParser<'buf> {
125 pub(crate) fn new(input: &'buf str) -> KVParser<'buf> {
126 KVParser {
127 len_remaining: input.len(),
128 chars: input.chars(),
129 }
130 }
131
132 pub(crate) fn first(&self) -> char {
133 self.chars.clone().next().unwrap_or(EOF_CHAR)
134 }
135
136 pub(crate) fn is_eof(&self) -> bool {
137 self.chars.as_str().is_empty()
138 }
139
140 pub(crate) fn pos_within_token(&self) -> usize {
141 self.len_remaining - self.chars.as_str().len()
142 }
143
144 pub(crate) fn reset_pos_within_token(&mut self) {
145 self.len_remaining = self.chars.as_str().len();
146 }
147
148 pub(crate) fn advance(&mut self) -> Option<char> {
149 Some(self.chars.next()?)
150 }
151
152 pub(crate) fn consume_while(&mut self, mut predicate: impl FnMut(char) -> bool) {
153 while predicate(self.first()) && !self.is_eof() {
154 self.advance();
155 }
156 }
157
158 pub(crate) fn query_kv_pair(&mut self) -> Option<(&'buf str, &'buf str)> {
160 if !self.chars.as_str().is_ascii() {
161 return None;
162 }
163 let iter = self.chars.clone();
164 let first_char = match self.advance() {
165 Some(c) => c,
166 None => return None,
167 };
168
169 fn is_id_start(c: char) -> bool {
170 (c == '_' || c == '-' || c == '&')
171 || ('a' <= c && c <= 'z')
172 || ('A' <= c && c <= 'Z')
173 }
174 fn is_id_continue(c: char) -> bool {
175 (c == '_' || c == '-' || c == '&')
176 || ('a' <= c && c <= 'z')
177 || ('A' <= c && c <= 'Z')
178 || ('0' <= c && c <= '9')
179 }
180
181 if is_id_start(first_char) {
183 self.consume_while(|c| is_id_continue(c) && c != '=');
184 } else {
185 return None;
186 }
187 let key_len = self.pos_within_token();
188
189 match self.advance() {
190 Some('=') => (),
192 _ => {
194 let key = &iter.as_str()[..key_len];
195 return Some((key, ""));
196 },
197 }
198 self.reset_pos_within_token();
199
200 let first_char = match self.advance() {
201 Some(c) => c,
202 None => {
204 let key = &iter.as_str()[..key_len];
205 return Some((key, ""));
206 },
207 };
208
209 if first_char != '&' {
210 self.consume_while(|c| c != '&');
211 } else {
212 let key = &iter.as_str()[..key_len];
213 return Some((key, ""));
214 }
215 let val_len = self.pos_within_token();
216 self.advance();
217 self.reset_pos_within_token();
218
219 let iter_str = iter.as_str();
220 let key = &iter_str[..key_len];
221 let value = &iter_str[(key_len+1)..(key_len+1+val_len)];
222 return Some((key, value));
223 }
224
225 pub(crate) fn cookie_kv_pair(&mut self) -> Option<(&'buf str, &'buf str)> {
227 if !self.chars.as_str().is_ascii() {
228 return None;
229 }
230 let iter = self.chars.clone();
231 let first_char = match self.advance() {
232 Some(c) => c,
233 None => return None,
234 };
235
236 fn is_id_start(c: char) -> bool {
237 (c == '_')
238 || ('a' <= c && c <= 'z')
239 || ('A' <= c && c <= 'Z')
240 }
241 fn is_id_continue(c: char) -> bool {
242 (c == '_' || c == '-')
243 || ('a' <= c && c <= 'z')
244 || ('A' <= c && c <= 'Z')
245 || ('0' <= c && c <= '9')
246 }
247
248 if is_id_start(first_char) {
249 self.consume_while(|c| is_id_continue(c) && c != '=');
250 } else {
251 return None;
252 }
253 let key_len = self.pos_within_token();
254
255 match self.advance() {
256 Some('=') => (),
258 _ => {
260 let key = &iter.as_str()[..key_len];
261 return Some((key, ""));
262 },
263 }
264 self.reset_pos_within_token();
265
266 let first_char = match self.advance() {
267 Some(c) => c,
268 None => {
270 let key = &iter.as_str()[..key_len];
271 return Some((key, ""));
272 },
273 };
274
275 if (first_char.is_ascii_alphanumeric() || first_char.is_ascii_punctuation()) && first_char != ';' {
276 self.consume_while(|c| c != ';');
277 } else {
278 let key = &iter.as_str()[..key_len];
279 return Some((key, ""));
280 }
281 let val_len = self.pos_within_token();
282 self.advance();
283 self.reset_pos_within_token();
284
285 let iter_str = iter.as_str();
286 let key = &iter_str[..key_len];
287 let value = &iter_str[(key_len+1)..(key_len+1+val_len)];
288 return Some((key, value));
289 }
290}
291
292pub fn parse_parameters<'buf>(to_parse: &'buf str) -> Result<Vec<(&'buf str, &'buf str)>, ParseError> {
295 if to_parse.is_empty() {
296 return Ok(Vec::new());
297 }
298
299 let mut pp = KVParser::new(to_parse);
300 let mut params = Vec::new();
301
302 while let Some((key, value)) = pp.query_kv_pair() {
303 params.push((key, value));
304 }
305
306 return Ok(params);
307}
308
309pub fn parse_header<'buf>(raw_header: &'buf str) -> Option<(&'buf str, &'buf str)> {
311 if raw_header.is_empty() {
312 return None;
313 }
314
315 match raw_header.split_once(": ") {
316 None => return None,
317 Some((key, value)) => {
318 if value.is_empty() || !is_param_name_valid(key) {
319 return None;
320 } else {
321 return Some((key, value));
322 }
323 },
324 }
325}
326
327pub fn is_param_name_valid(param: &str) -> bool {
330 if param.is_empty() { return false }
331
332 for (idx, chr) in param.chars().enumerate() {
333 if idx == 0 { if let '0'..='9' = chr { return false }
335 }
336 match chr { 'a'..='z' => continue,
338 'A'..='Z' => continue,
339 '0'..='9' => continue,
340 '-' => continue,
341 '_' => continue,
342 _ => return false,
343 }
344 }
345 true
346}
347
348pub fn split_once(to_split: &[u8], by: u8) -> (&[u8], Option<&[u8]>) {
355 let mut found_idx = 0;
356
357 for (idx, byte) in to_split.iter().enumerate() {
359 if *byte == by {
360 found_idx = idx;
361 break;
362 }
363 }
364
365 if found_idx == 0 {
367 return (to_split, None);
368 }
369
370 let (item, rest) = to_split.split_at(found_idx);
372 let rest = rest.split_at(1).1;
373 if rest == b"" {
374 (item, None)
375 } else {
376 (item, Some(rest))
377 }
378}
379