Skip to main content

ehttpd_querystring/
querystring.rs

1//! Implements query-string decoding
2
3use 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/// A query string
12///
13/// ## Important
14/// The query parser is pretty simple and basically parses any `key` or `key=` or `key=value` component without further
15/// validation.
16///
17/// The following rules apply:
18///  - the query string _MUST NOT_ begin with a `?` – it's not a bug, it's a feature: this allows the parser to parse
19///    raw query strings in the body (e.g. from HTML forms)
20///  - keys should be unique, non-unique keys are overwritten (i.e. `key0=ignored&key0=value` evaluates to
21///    `["key0": "value"]`)
22///  - keys don't need a value (i.e. `key0&key1` is valid)
23///  - keys can have an empty value (i.e. `key0=&key1=` is valid)
24///  - keys can have a non-empty value (i.e. `key0=value0&key1=value1` is valid)
25///  - empty keys/key-value pairs are ignored (i.e. `&` evaluates to `[]`, `key0&&key1` evaluates to
26///    `["key0": "", "key1": ""]` and `=value0&key1=value1&` evaluates to `["key1": "value1"]`)
27#[derive(Debug, Clone)]
28pub struct QueryString<'a> {
29    /// The request base URL
30    url: &'a [u8],
31    /// The querystring key-value pairs
32    fields: BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>,
33}
34impl<'a> QueryString<'a> {
35    /// Splits a request target into its base URL and the query string
36    #[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
37    pub fn decode(target: &'a Data) -> Result<Self, Error> {
38        // Split the URL
39        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        // Parse the query string
44        let fields = Self::decode_raw(querystring)?;
45        Ok(Self { url, fields })
46    }
47    /// Decodes a raw query string without the leading `?` into its key-value pairs
48    #[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        // Parse the query components
52        let mut fields = BTreeMap::new();
53        for pair in querystring.split(|b| *b == b'&') {
54            // Read the next pair
55            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            // Insert the key if it is not empty
60            if !key.is_empty() {
61                // Decode key and value and insert it
62                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    /// The request base URL
71    pub fn url(&self) -> &[u8] {
72        self.url
73    }
74
75    /// Gets the value as string slice
76    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        // Get the value
82        let Some(value) = self.get(key) else {
83            // No value for this key
84            return Ok(None);
85        };
86
87        // Parse the value to a string
88        let value = str::from_utf8(value)?;
89        Ok(Some(value))
90    }
91
92    /// Gets a value and converts it to the requested type
93    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        // Get the value
101        let Some(value) = self.get(key) else {
102            // No value for this key
103            return Ok(None);
104        };
105
106        // Parse the value
107        let parsed = value.as_ref().parse()?;
108        Ok(Some(parsed))
109    }
110
111    /// Percent-decodes the encoded data
112    pub fn percent_decode(encoded: Cow<[u8]>) -> Result<Cow<[u8]>, Error> {
113        // Check if we need some decoding
114        let needs_decode = encoded.contains(&b'%');
115        if !needs_decode {
116            return Ok(encoded);
117        }
118
119        // Perform decoding
120        let mut source = encoded.iter().copied();
121        let mut decoded = Vec::new();
122        while let Some(mut byte) = source.next() {
123            // Decode percent literal if necessary
124            if byte == b'%' {
125                // Get the encoded bytes
126                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            // Write byte
132            decoded.push(byte);
133        }
134        Ok(Cow::Owned(decoded))
135    }
136
137    /// Encodes a nibble into a hex char
138    fn percent_decode_nibble(nibble: u8) -> Result<u8, Error> {
139        // Note: All operations are safe since they are implicitly validated by the range comparisons
140        #[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    /// Encodes a byte
150    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}