ehttpd_querystring/
querystring.rs1use ehttpd::{bytes::Data, error, error::Error};
4use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
5
6#[derive(Debug, Clone)]
23pub struct QueryString<'a> {
24 url: &'a [u8],
26 fields: BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>,
28}
29impl<'a> QueryString<'a> {
30 #[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
32 pub fn decode(target: &'a Data) -> Result<Self, Error> {
33 let mut target = target.splitn(2, |b| *b == b'?');
35 let url = target.next().expect("first element of split iterator is empty?!");
36 let querystring = target.next().unwrap_or_default();
37
38 let fields = Self::decode_raw(querystring)?;
40 Ok(Self { url, fields })
41 }
42 #[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
44 #[allow(clippy::type_complexity, reason = "The type only looks complex but is not complex")]
45 pub fn decode_raw(querystring: &'a [u8]) -> Result<BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>, Error> {
46 let mut fields = BTreeMap::new();
48 for pair in querystring.split(|b| *b == b'&') {
49 let mut pair = pair.splitn(2, |b| *b == b'=');
51 let key = pair.next().map(Cow::Borrowed).expect("first element of split iterator is empty?!");
52 let value = pair.next().map(Cow::Borrowed).unwrap_or_default();
53
54 if !key.is_empty() {
56 let key = Self::percent_decode(key)?;
58 let value = Self::percent_decode(value)?;
59 fields.insert(key, value);
60 }
61 }
62 Ok(fields)
63 }
64
65 pub fn url(&self) -> &[u8] {
67 self.url
68 }
69
70 pub fn percent_decode(encoded: Cow<[u8]>) -> Result<Cow<[u8]>, Error> {
72 let needs_decode = encoded.iter().any(|b| *b == b'%');
74 if !needs_decode {
75 return Ok(encoded);
76 }
77
78 let mut source = encoded.iter().copied();
80 let mut decoded = Vec::new();
81 while let Some(mut byte) = source.next() {
82 if byte == b'%' {
84 let high = source.next().ok_or(error!("Truncated hex literal"))?;
86 let low = source.next().ok_or(error!("Truncated hex literal"))?;
87 byte = Self::percent_decode_byte(high, low)?;
88 }
89
90 decoded.push(byte);
92 }
93 Ok(Cow::Owned(decoded))
94 }
95
96 fn percent_decode_nibble(nibble: u8) -> Result<u8, Error> {
98 #[allow(clippy::arithmetic_side_effects, reason = "The range is validated by the match")]
100 match nibble {
101 b'0'..=b'9' => Ok(nibble - b'0'),
102 b'a'..=b'f' => Ok((nibble - b'a') + 0xA),
103 b'A'..=b'F' => Ok((nibble - b'A') + 0xA),
104 nibble => Err(error!("Invalid nibble 0x{nibble:01x}")),
105 }
106 }
107
108 fn percent_decode_byte(high: u8, low: u8) -> Result<u8, Error> {
110 Ok((Self::percent_decode_nibble(high)? << 4) | Self::percent_decode_nibble(low)?)
111 }
112}
113impl<'a> Deref for QueryString<'a> {
114 type Target = BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>;
115
116 fn deref(&self) -> &Self::Target {
117 &self.fields
118 }
119}
120impl<'a> IntoIterator for QueryString<'a> {
121 type Item = <BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>> as IntoIterator>::Item;
122 type IntoIter = <BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>> as IntoIterator>::IntoIter;
123
124 fn into_iter(self) -> Self::IntoIter {
125 self.fields.into_iter()
126 }
127}