Skip to main content

powerio_core/
bounded.rs

1//! Serde helpers that apply record limits while decoding.
2//!
3//! A hostile document must fail at the first excess element or byte, before an
4//! unbounded `Vec`, map, or `String` has been built. Every helper here refuses
5//! or truncates inside the visitor, so the only transient allocation is the
6//! JSON scanner's own token buffer, which is bounded by the input size the
7//! caller admitted.
8
9use std::fmt;
10use std::marker::PhantomData;
11
12use serde::de::{Deserialize, DeserializeSeed, Deserializer, Error as _, SeqAccess, Visitor};
13use serde_json::{Map, Value};
14
15/// A string field that is refused past `max_bytes`, checked before the text is
16/// retained.
17pub struct BoundedStr {
18    pub what: &'static str,
19    pub max_bytes: usize,
20}
21
22impl Visitor<'_> for BoundedStr {
23    type Value = String;
24
25    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(
27            formatter,
28            "a {} of at most {} bytes",
29            self.what, self.max_bytes
30        )
31    }
32
33    fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<String, E> {
34        if value.len() > self.max_bytes {
35            return Err(E::custom(format!(
36                "a stored {} exceeds {} bytes",
37                self.what, self.max_bytes
38            )));
39        }
40        Ok(value.to_owned())
41    }
42
43    fn visit_string<E: serde::de::Error>(self, value: String) -> Result<String, E> {
44        if value.len() > self.max_bytes {
45            return Err(E::custom(format!(
46                "a stored {} exceeds {} bytes",
47                self.what, self.max_bytes
48            )));
49        }
50        Ok(value)
51    }
52}
53
54impl<'de> DeserializeSeed<'de> for BoundedStr {
55    type Value = String;
56
57    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<String, D::Error> {
58        deserializer.deserialize_str(self)
59    }
60}
61
62/// A string field that is truncated at a character boundary once `max_bytes`
63/// have been retained. Used for message text whose semantic limit is already a
64/// truncation rule rather than a refusal.
65pub struct TruncatedStr {
66    pub max_bytes: usize,
67}
68
69impl Visitor<'_> for TruncatedStr {
70    type Value = String;
71
72    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(
74            formatter,
75            "a string retained up to {} bytes",
76            self.max_bytes
77        )
78    }
79
80    fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<String, E> {
81        if value.len() <= self.max_bytes {
82            return Ok(value.to_owned());
83        }
84        let mut end = self.max_bytes;
85        while !value.is_char_boundary(end) {
86            end -= 1;
87        }
88        Ok(value[..end].to_owned())
89    }
90
91    fn visit_string<E: serde::de::Error>(self, mut value: String) -> Result<String, E> {
92        if value.len() > self.max_bytes {
93            let mut end = self.max_bytes;
94            while !value.is_char_boundary(end) {
95                end -= 1;
96            }
97            value.truncate(end);
98        }
99        Ok(value)
100    }
101}
102
103/// A sequence field that is refused as soon as one element past `max_len`
104/// arrives.
105struct BoundedSeq<T> {
106    what: &'static str,
107    max_len: usize,
108    marker: PhantomData<T>,
109}
110
111impl<'de, T: Deserialize<'de>> Visitor<'de> for BoundedSeq<T> {
112    type Value = Vec<T>;
113
114    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115        write!(
116            formatter,
117            "a sequence of at most {} {}",
118            self.max_len, self.what
119        )
120    }
121
122    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<T>, A::Error> {
123        let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0).min(self.max_len));
124        while let Some(value) = seq.next_element::<T>()? {
125            if values.len() == self.max_len {
126                return Err(A::Error::custom(format!(
127                    "a stored record carries more than {} {}",
128                    self.max_len, self.what
129                )));
130            }
131            values.push(value);
132        }
133        Ok(values)
134    }
135}
136
137pub fn bounded_vec<'de, T: Deserialize<'de>, D: Deserializer<'de>>(
138    deserializer: D,
139    what: &'static str,
140    max_len: usize,
141) -> Result<Vec<T>, D::Error> {
142    deserializer.deserialize_seq(BoundedSeq {
143        what,
144        max_len,
145        marker: PhantomData,
146    })
147}
148
149/// A JSON object field whose key count, key lengths, and key texts are
150/// checked as each key arrives, before its value is read. The values remain
151/// `serde_json::Value` and are bounded by the input size the caller admitted.
152struct BoundedJsonMap {
153    what: &'static str,
154    max_keys: usize,
155    max_key_bytes: usize,
156    valid_key: fn(&str) -> bool,
157}
158
159impl<'de> Visitor<'de> for BoundedJsonMap {
160    type Value = Map<String, Value>;
161
162    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163        write!(
164            formatter,
165            "an object of at most {} {}",
166            self.max_keys, self.what
167        )
168    }
169
170    fn visit_map<A: serde::de::MapAccess<'de>>(
171        self,
172        mut access: A,
173    ) -> Result<Map<String, Value>, A::Error> {
174        let mut map = Map::new();
175        while let Some(key) = access.next_key_seed(BoundedStr {
176            what: self.what,
177            max_bytes: self.max_key_bytes,
178        })? {
179            if !(self.valid_key)(&key) {
180                return Err(A::Error::custom(format!(
181                    "a stored record carries an invalid {} key",
182                    self.what
183                )));
184            }
185            if map.len() == self.max_keys {
186                return Err(A::Error::custom(format!(
187                    "a stored record carries more than {} {}",
188                    self.max_keys, self.what
189                )));
190            }
191            map.insert(key, access.next_value()?);
192        }
193        Ok(map)
194    }
195}
196
197pub fn bounded_json_map<'de, D: Deserializer<'de>>(
198    deserializer: D,
199    what: &'static str,
200    max_keys: usize,
201    max_key_bytes: usize,
202    valid_key: fn(&str) -> bool,
203) -> Result<Map<String, Value>, D::Error> {
204    deserializer.deserialize_map(BoundedJsonMap {
205        what,
206        max_keys,
207        max_key_bytes,
208        valid_key,
209    })
210}
211
212#[cfg(test)]
213mod tests {
214    use serde::de::DeserializeSeed;
215    use serde::de::IntoDeserializer;
216
217    use super::*;
218
219    fn json_de(text: &str) -> serde_json::Deserializer<serde_json::de::StrRead<'_>> {
220        serde_json::Deserializer::from_str(text)
221    }
222
223    #[test]
224    fn oversized_strings_are_refused_before_retention() {
225        let seed = BoundedStr {
226            what: "identifier",
227            max_bytes: 4,
228        };
229        assert_eq!(
230            seed.deserialize(String::from("abcd").into_deserializer()
231                as serde::de::value::StringDeserializer<serde_json::Error>)
232                .unwrap(),
233            "abcd"
234        );
235        let seed = BoundedStr {
236            what: "identifier",
237            max_bytes: 4,
238        };
239        assert!(
240            seed.deserialize(String::from("abcde").into_deserializer()
241                as serde::de::value::StringDeserializer<serde_json::Error>)
242                .is_err()
243        );
244    }
245
246    #[test]
247    fn truncated_strings_stop_at_a_character_boundary() {
248        let visitor = TruncatedStr { max_bytes: 4 };
249        let text = "aééé";
250        let kept = visitor.visit_str::<serde_json::Error>(text).unwrap();
251        assert_eq!(kept, "aé");
252        assert!(kept.len() <= 4);
253    }
254
255    #[test]
256    fn a_sequence_fails_at_the_first_excess_element() {
257        let mut deserializer = json_de("[1,2,3,4]");
258        let result: Result<Vec<u32>, _> = bounded_vec(&mut deserializer, "entries", 3);
259        assert!(result.unwrap_err().to_string().contains("more than 3"));
260
261        let mut deserializer = json_de("[1,2,3]");
262        let values: Vec<u32> = bounded_vec(&mut deserializer, "entries", 3).unwrap();
263        assert_eq!(values, [1, 2, 3]);
264    }
265
266    #[test]
267    fn a_map_checks_key_count_length_and_text_while_decoding() {
268        let accept = |_: &str| true;
269        let mut deserializer = json_de(r#"{"a":1,"b":2}"#);
270        let map = bounded_json_map(&mut deserializer, "detail keys", 2, 8, accept).unwrap();
271        assert_eq!(map.len(), 2);
272
273        let mut deserializer = json_de(r#"{"a":1,"b":2,"c":3}"#);
274        assert!(bounded_json_map(&mut deserializer, "detail keys", 2, 8, accept).is_err());
275
276        let mut deserializer = json_de(r#"{"toolong":1}"#);
277        assert!(bounded_json_map(&mut deserializer, "detail keys", 8, 4, accept).is_err());
278
279        // The key predicate runs as the key is decoded, before its value.
280        let mut deserializer = json_de(r#"{"":1}"#);
281        assert!(
282            bounded_json_map(&mut deserializer, "detail keys", 8, 8, |key| !key
283                .is_empty())
284            .is_err()
285        );
286    }
287}