deser_incomplete/random_trailer/
mod.rs

1#![allow(clippy::len_without_is_empty)]
2
3#[cfg(feature = "serde_json")]
4pub(crate) mod json;
5#[cfg(feature = "serde_yaml")]
6pub(crate) mod yaml;
7
8/// For some data formats, we like to append some randomized trailer
9/// to the input before deserializing.
10///
11/// - For JSON, this lets us support incomplete strings.
12///
13/// - For YAML, we need this otherwise any incomplete string seems to
14///   cause deserialization to fail.
15///
16/// However, such a trailer then needs to be removed from any decoded
17/// strings.
18pub trait RandomTrailer {
19    /// Given a random tag, add the corresponding trailer to the input.
20    fn prepare_string_with_tag(&self, input: &mut String, tag: &str);
21
22    /// Given a random tag, add the corresponding trailer to the input.
23    fn prepare_vec_with_tag(&self, input: &mut Vec<u8>, tag: &str);
24
25    /// Given a parsed string or bytes, detect whether the random tag was
26    /// parsed,
27    /// then take off the suffix of the parsed string that may not have been
28    /// present in the original input.
29    ///
30    /// Return whether the tag was detected. This simultaneously represents that
31    /// we have encountered the end of the input, and we should probably stop
32    /// trying to deserialize more.
33    ///
34    /// This method should only modify the input if the tag is detected.
35    ///
36    /// This method will not be called when parsing was done without a random tag.
37    #[must_use]
38    fn remove_trailer(&self, string_like: &mut impl StringLike, random_tag: &str) -> bool;
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct NoopRandomTrailer;
43
44impl RandomTrailer for NoopRandomTrailer {
45    fn prepare_string_with_tag(&self, _input: &mut String, _tag: &str) {}
46
47    fn prepare_vec_with_tag(&self, _input: &mut Vec<u8>, _tag: &str) {}
48
49    fn remove_trailer(&self, _string_like: &mut impl StringLike, _random_tag: &str) -> bool {
50        false
51    }
52}
53
54/// A prepared input for deserialization with a random trailer.
55pub struct InputPlusTrailer<SliceType>(pub SliceType);
56
57/// Bytes and string types, which for serde_json may suffer from trailing data
58/// that wasn't present in the input.
59pub trait StringLike: std::fmt::Debug {
60    /// Length in bytes
61    fn len(&self) -> usize;
62    fn ends_with_string(&self, string: &str) -> bool;
63    /// Check whether this ends in `s1 + s2`.
64    fn ends_with_2_strings(&self, s1: &str, s2: &str) -> bool;
65    fn truncate_to_bytes(&mut self, target_len: usize);
66}
67
68impl StringLike for &str {
69    fn len(&self) -> usize {
70        (*self).len()
71    }
72
73    fn ends_with_string(&self, string: &str) -> bool {
74        self.ends_with(string)
75    }
76
77    fn ends_with_2_strings(&self, s1: &str, s2: &str) -> bool {
78        self.ends_with(s2) && self[0..self.len() - s2.len()].ends_with(s1)
79    }
80
81    fn truncate_to_bytes(&mut self, target_len: usize) {
82        *self = &self[..target_len];
83    }
84}
85
86impl StringLike for &[u8] {
87    fn len(&self) -> usize {
88        (*self).len()
89    }
90
91    fn ends_with_string(&self, string: &str) -> bool {
92        self.ends_with(string.as_bytes())
93    }
94
95    fn ends_with_2_strings(&self, s1: &str, s2: &str) -> bool {
96        self.ends_with(s2.as_bytes()) && self[0..self.len() - s2.len()].ends_with(s1.as_bytes())
97    }
98
99    fn truncate_to_bytes(&mut self, target_len: usize) {
100        *self = &self[..target_len];
101    }
102}
103
104impl StringLike for String {
105    fn len(&self) -> usize {
106        self.len()
107    }
108
109    fn ends_with_string(&self, string: &str) -> bool {
110        self.ends_with(string)
111    }
112
113    fn ends_with_2_strings(&self, s1: &str, s2: &str) -> bool {
114        self.ends_with(s2) && self[0..self.len() - s2.len()].ends_with(s1)
115    }
116
117    fn truncate_to_bytes(&mut self, target_len: usize) {
118        self.truncate(target_len);
119    }
120}
121
122impl StringLike for Vec<u8> {
123    fn len(&self) -> usize {
124        self.len()
125    }
126
127    fn ends_with_string(&self, string: &str) -> bool {
128        self.ends_with(string.as_bytes())
129    }
130
131    fn ends_with_2_strings(&self, s1: &str, s2: &str) -> bool {
132        self.ends_with(s2.as_bytes()) && self[0..self.len() - s2.len()].ends_with(s1.as_bytes())
133    }
134
135    fn truncate_to_bytes(&mut self, target_len: usize) {
136        self.truncate(target_len);
137    }
138}
139
140#[cfg(test)]
141impl StringLike for std::borrow::Cow<'_, str> {
142    fn len(&self) -> usize {
143        (**self).len()
144    }
145
146    fn ends_with_string(&self, string: &str) -> bool {
147        self.ends_with(string)
148    }
149
150    fn ends_with_2_strings(&self, s1: &str, s2: &str) -> bool {
151        self.ends_with(s2) && self[0..self.len() - s2.len()].ends_with(s1)
152    }
153
154    fn truncate_to_bytes(&mut self, target_len: usize) {
155        match self {
156            std::borrow::Cow::Borrowed(slice) => {
157                slice.truncate_to_bytes(target_len);
158            }
159            std::borrow::Cow::Owned(string) => {
160                string.truncate_to_bytes(target_len);
161            }
162        }
163    }
164}