ehttpd_querystring/
querystring.rs1use ehttpd::bytes::{Data, Parse};
4use ehttpd::err;
5use ehttpd::error::Error;
6use std::borrow::{Borrow, Cow};
7use std::collections::BTreeMap;
8use std::ops::Deref;
9use std::str::FromStr;
10
11#[derive(Debug, Clone)]
28pub struct QueryString<'a> {
29 url: &'a [u8],
31 fields: BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>,
33}
34impl<'a> QueryString<'a> {
35 #[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
37 pub fn decode(target: &'a Data) -> Result<Self, Error> {
38 let mut target = target.splitn(2, |b| *b == b'?');
40 let url = target.next().expect("first element of split iterator is empty?!");
41 let querystring = target.next().unwrap_or_default();
42
43 let fields = Self::decode_raw(querystring)?;
45 Ok(Self { url, fields })
46 }
47 #[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
49 #[allow(clippy::type_complexity, reason = "The type only looks complex but is not complex")]
50 pub fn decode_raw(querystring: &'a [u8]) -> Result<BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>, Error> {
51 let mut fields = BTreeMap::new();
53 for pair in querystring.split(|b| *b == b'&') {
54 let mut pair = pair.splitn(2, |b| *b == b'=');
56 let key = pair.next().map(Cow::Borrowed).expect("first element of split iterator is empty?!");
57 let value = pair.next().map(Cow::Borrowed).unwrap_or_default();
58
59 if !key.is_empty() {
61 let key = Self::percent_decode(key)?;
63 let value = Self::percent_decode(value)?;
64 fields.insert(key, value);
65 }
66 }
67 Ok(fields)
68 }
69
70 pub fn url(&self) -> &[u8] {
72 self.url
73 }
74
75 pub fn get_str<Key>(&self, key: &Key) -> Result<Option<&str>, Error>
77 where
78 Key: Ord + ?Sized,
79 Cow<'a, [u8]>: Borrow<Key> + Ord,
80 {
81 let Some(value) = self.get(key) else {
83 return Ok(None);
85 };
86
87 let value = str::from_utf8(value)?;
89 Ok(Some(value))
90 }
91
92 pub fn get_as<Type, Key>(&self, key: &Key) -> Result<Option<Type>, Error>
94 where
95 Type: FromStr,
96 Type::Err: std::error::Error + Send + 'static,
97 Key: Ord + ?Sized,
98 Cow<'a, [u8]>: Borrow<Key> + Ord,
99 {
100 let Some(value) = self.get(key) else {
102 return Ok(None);
104 };
105
106 let parsed = value.as_ref().parse()?;
108 Ok(Some(parsed))
109 }
110
111 pub fn percent_decode(encoded: Cow<[u8]>) -> Result<Cow<[u8]>, Error> {
113 let needs_decode = encoded.contains(&b'%');
115 if !needs_decode {
116 return Ok(encoded);
117 }
118
119 let mut source = encoded.iter().copied();
121 let mut decoded = Vec::new();
122 while let Some(mut byte) = source.next() {
123 if byte == b'%' {
125 let high = source.next().ok_or(err!("Truncated hex literal"))?;
127 let low = source.next().ok_or(err!("Truncated hex literal"))?;
128 byte = Self::percent_decode_byte(high, low)?;
129 }
130
131 decoded.push(byte);
133 }
134 Ok(Cow::Owned(decoded))
135 }
136
137 fn percent_decode_nibble(nibble: u8) -> Result<u8, Error> {
139 #[allow(clippy::arithmetic_side_effects, reason = "The range is validated by the match")]
141 match nibble {
142 b'0'..=b'9' => Ok(nibble - b'0'),
143 b'a'..=b'f' => Ok((nibble - b'a') + 0xA),
144 b'A'..=b'F' => Ok((nibble - b'A') + 0xA),
145 nibble => Err(err!("Invalid nibble 0x{nibble:01x}")),
146 }
147 }
148
149 fn percent_decode_byte(high: u8, low: u8) -> Result<u8, Error> {
151 Ok((Self::percent_decode_nibble(high)? << 4) | Self::percent_decode_nibble(low)?)
152 }
153}
154impl<'a> Deref for QueryString<'a> {
155 type Target = BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>;
156
157 fn deref(&self) -> &Self::Target {
158 &self.fields
159 }
160}
161impl<'a> IntoIterator for QueryString<'a> {
162 type Item = <BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>> as IntoIterator>::Item;
163 type IntoIter = <BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>> as IntoIterator>::IntoIter;
164
165 fn into_iter(self) -> Self::IntoIter {
166 self.fields.into_iter()
167 }
168}