1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
//! Implements query-string decoding
use ehttpd::{bytes::Data, error, error::Error};
use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
/// A query string
///
/// ## Important
/// The query parser is pretty simple and basically parses any `key` or `key=` or `key=value` component without further
/// validation.
///
/// The following rules apply:
/// - the query string _MUST NOT_ begin with a `?` – it's not a bug, it's a feature: this allows the parser to parse
/// raw query strings in the body (e.g. from HTML forms)
/// - keys should be unique, non-unique keys are overwritten (i.e. `key0=ignored&key0=value` evaluates to
/// `["key0": "value"]`)
/// - keys don't need a value (i.e. `key0&key1` is valid)
/// - keys can have an empty value (i.e. `key0=&key1=` is valid)
/// - keys can have a non-empty value (i.e. `key0=value0&key1=value1` is valid)
/// - empty keys/key-value pairs are ignored (i.e. `&` evaluates to `[]`, `key0&&key1` evaluates to
/// `["key0": "", "key1": ""]` and `=value0&key1=value1&` evaluates to `["key1": "value1"]`)
#[derive(Debug, Clone)]
pub struct QueryString<'a> {
/// The request base URL
url: &'a [u8],
/// The querystring key-value pairs
fields: BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>,
}
impl<'a> QueryString<'a> {
/// Splits a request target into its base URL and the query string
#[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
pub fn decode(target: &'a Data) -> Result<Self, Error> {
// Split the URL
let mut target = target.splitn(2, |b| *b == b'?');
let url = target.next().expect("first element of split iterator is empty?!");
let querystring = target.next().unwrap_or_default();
// Parse the query string
let fields = Self::decode_raw(querystring)?;
Ok(Self { url, fields })
}
/// Decodes a raw query string without the leading `?` into its key-value pairs
#[allow(clippy::missing_panics_doc, reason = "Panic should never occur")]
#[allow(clippy::type_complexity, reason = "The type only looks complex but is not complex")]
pub fn decode_raw(querystring: &'a [u8]) -> Result<BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>, Error> {
// Parse the query components
let mut fields = BTreeMap::new();
for pair in querystring.split(|b| *b == b'&') {
// Read the next pair
let mut pair = pair.splitn(2, |b| *b == b'=');
let key = pair.next().map(Cow::Borrowed).expect("first element of split iterator is empty?!");
let value = pair.next().map(Cow::Borrowed).unwrap_or_default();
// Insert the key if it is not empty
if !key.is_empty() {
// Decode key and value and insert it
let key = Self::percent_decode(key)?;
let value = Self::percent_decode(value)?;
fields.insert(key, value);
}
}
Ok(fields)
}
/// The request base URL
pub fn url(&self) -> &[u8] {
self.url
}
/// Percent-decodes the encoded data
pub fn percent_decode(encoded: Cow<[u8]>) -> Result<Cow<[u8]>, Error> {
// Check if we need some decoding
let needs_decode = encoded.iter().any(|b| *b == b'%');
if !needs_decode {
return Ok(encoded);
}
// Perform decoding
let mut source = encoded.iter().copied();
let mut decoded = Vec::new();
while let Some(mut byte) = source.next() {
// Decode percent literal if necessary
if byte == b'%' {
// Get the encoded bytes
let high = source.next().ok_or(error!("Truncated hex literal"))?;
let low = source.next().ok_or(error!("Truncated hex literal"))?;
byte = Self::percent_decode_byte(high, low)?;
}
// Write byte
decoded.push(byte);
}
Ok(Cow::Owned(decoded))
}
/// Encodes a nibble into a hex char
fn percent_decode_nibble(nibble: u8) -> Result<u8, Error> {
// Note: All operations are safe since they are implicitly validated by the range comparisons
#[allow(clippy::arithmetic_side_effects, reason = "The range is validated by the match")]
match nibble {
b'0'..=b'9' => Ok(nibble - b'0'),
b'a'..=b'f' => Ok((nibble - b'a') + 0xA),
b'A'..=b'F' => Ok((nibble - b'A') + 0xA),
nibble => Err(error!("Invalid nibble 0x{nibble:01x}")),
}
}
/// Encodes a byte
fn percent_decode_byte(high: u8, low: u8) -> Result<u8, Error> {
Ok(Self::percent_decode_nibble(high)? << 4 | Self::percent_decode_nibble(low)?)
}
}
impl<'a> Deref for QueryString<'a> {
type Target = BTreeMap<Cow<'a, [u8]>, Cow<'a, [u8]>>;
fn deref(&self) -> &Self::Target {
&self.fields
}
}