Skip to main content

relay_server/utils/
param_parser.rs

1use std::collections::BTreeMap;
2
3use serde_json::Value;
4
5use crate::services::processor::ProcessingError;
6
7/// Maximum number of nested keys accepted in a path like the form-data key path `sentry[a][b][c]…`.
8///
9/// This number needs to be limited due to the recursion taking place when building the JSON object
10/// in [`update_nested_value`], and when serializing, normalizing, and dropping it.
11const MAX_PATH_DEPTH: usize = 15;
12
13enum IndexingState {
14    LookingForLeftParenthesis,
15    Accumulating(usize),
16    Starting,
17}
18
19/// Updates a json Value at the specified path.
20pub fn update_nested_value<V>(
21    target: &mut Value,
22    path: &[&str],
23    value: V,
24) -> Result<(), ProcessingError>
25where
26    V: Into<String>,
27{
28    if path.len() > MAX_PATH_DEPTH {
29        return Err(ProcessingError::NestingTooDeep);
30    }
31    let map = match target {
32        Value::Object(map) => map,
33        _ => return Ok(()),
34    };
35
36    let (key, rest) = match path.split_first() {
37        Some(tuple) => tuple,
38        None => return Ok(()),
39    };
40
41    let entry = map.entry(key.to_owned());
42
43    if rest.is_empty() {
44        entry.or_insert_with(|| Value::String(value.into()));
45    } else {
46        let sub_object = entry.or_insert_with(|| Value::Object(Default::default()));
47        update_nested_value(sub_object, rest, value)?;
48    }
49
50    Ok(())
51}
52
53/// Merge two [`serde_json::Value`] items.
54///
55/// Fills the value `a` with values from `b`. This does not overwrite values from `a` with `b`.
56///
57/// Taken (with small changes) from stack overflow answer:
58/// <https://stackoverflow.com/questions/47070876/how-can-i-merge-two-json-objects-with-rust>.
59pub fn merge_values(a: &mut Value, b: Value) {
60    match (a, b) {
61        // Recursively merge dicts
62        (Value::Object(a), Value::Object(b)) => {
63            for (k, v) in b {
64                merge_values(a.entry(k).or_insert(Value::Null), v);
65            }
66        }
67        // Fill in missing left values
68        (a @ &mut Value::Null, b) => *a = b,
69        // Do not override existing values that are not maps
70        (_a, _b) => {}
71    }
72}
73
74/// Extracts indexes from a param string e.g. extracts `[String(abc),String(xyz)]` from `"sentry[abc][xyz]"`
75fn get_indexes(full_string: &str) -> Result<Vec<&str>, ()> {
76    let mut ret_vals = vec![];
77    let mut state = IndexingState::Starting;
78    //first iterate by byte (so we can get correct offsets)
79    for (idx, by) in full_string.bytes().enumerate() {
80        match state {
81            IndexingState::Starting => {
82                if by == b'[' {
83                    state = IndexingState::Accumulating(idx + 1)
84                }
85            }
86            IndexingState::LookingForLeftParenthesis => {
87                if by == b'[' {
88                    state = IndexingState::Accumulating(idx + 1);
89                } else if by == b'=' {
90                    return Ok(ret_vals);
91                } else {
92                    return Err(());
93                }
94            }
95            IndexingState::Accumulating(start_idx) => {
96                if by == b']' {
97                    let slice = &full_string[start_idx..idx];
98                    ret_vals.push(slice);
99                    state = IndexingState::LookingForLeftParenthesis;
100                }
101            }
102        }
103    }
104    Ok(ret_vals)
105}
106
107/// Extracts indexes from a param of the form `sentry[XXX][...]`.
108pub fn get_sentry_entry_indexes(param_name: &str) -> Option<Vec<&str>> {
109    if param_name.starts_with("sentry[") {
110        get_indexes(param_name).ok()
111    } else {
112        None
113    }
114}
115
116/// Extracts the chunk index of a key with the given prefix.
117///
118/// Electron SDK splits up long payloads into chunks starting at sentry__1 with an
119/// incrementing counter. Assemble these chunks here and then decode them below.
120///
121/// If the index is unparsable from `key`, `None` is returned.
122pub fn get_sentry_chunk_index(key: &str, prefix: &str) -> Option<usize> {
123    key.strip_prefix(prefix).and_then(|rest| rest.parse().ok())
124}
125
126/// Aggregates slices of strings in random order.
127#[derive(Clone, Debug, Default)]
128pub struct ChunkedFormDataAggregator<'a> {
129    parts: BTreeMap<usize, &'a str>,
130}
131
132impl<'a> ChunkedFormDataAggregator<'a> {
133    /// Creates a new empty aggregator.
134    pub fn new() -> Self {
135        Self::default()
136    }
137
138    /// Adds a part with the given index.
139    pub fn insert(&mut self, index: usize, value: &'a str) {
140        self.parts.insert(index, value);
141    }
142
143    /// Returns `true` if no parts have been added.
144    pub fn is_empty(&self) -> bool {
145        self.parts.is_empty()
146    }
147
148    /// Returns the string consisting of all parts.
149    pub fn join(&self) -> String {
150        self.parts.values().copied().collect()
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_index_parser() {
160        let examples: &[(&str, Option<&[&str]>)] = &[
161            ("fafdasd[a][b][33]", Some(&["a", "b", "33"])),
162            ("fafdasd[a]b[33]", None),
163            ("fafdasd[a][b33]xx", None),
164            ("[23a][234][abc123]", Some(&["23a", "234", "abc123"])),
165            ("sentry[abc][123][]=SomeVal", Some(&["abc", "123", ""])),
166            ("sentry[Grüße][Jürgen][❤]", Some(&["Grüße", "Jürgen", "❤"])),
167            (
168                "[农22历][新年][b新年c]",
169                Some(&["农22历", "新年", "b新年c"]),
170            ),
171            ("[ὈΔΥΣΣΕΎΣ][abc]", Some(&["ὈΔΥΣΣΕΎΣ", "abc"])),
172        ];
173
174        for &(example, expected_result) in examples {
175            let indexes = get_indexes(example).ok();
176            assert_eq!(indexes, expected_result.map(|vec| vec.into()));
177        }
178    }
179
180    #[test]
181    fn test_update_value() {
182        let mut val = Value::Object(serde_json::Map::new());
183
184        update_nested_value(&mut val, &["x", "y", "z"], "xx").unwrap();
185
186        insta::assert_json_snapshot!(val, @r###"
187        {
188          "x": {
189            "y": {
190              "z": "xx"
191            }
192          }
193        }
194        "###);
195
196        update_nested_value(&mut val, &["x", "y", "k"], "kk").unwrap();
197        update_nested_value(&mut val, &["w", ""], "w").unwrap();
198        update_nested_value(&mut val, &["z1"], "val1").unwrap();
199        insta::assert_json_snapshot!(val, @r###"
200        {
201          "w": {
202            "": "w"
203          },
204          "x": {
205            "y": {
206              "k": "kk",
207              "z": "xx"
208            }
209          },
210          "z1": "val1"
211        }
212        "###);
213    }
214
215    #[test]
216    fn test_merge_vals() {
217        let mut original = serde_json::json!({
218            "k1": "v1",
219            "k2": {
220                "k3": "v3",
221                "k4": "v4"
222            },
223            "k5": [ 1,2,3]
224        });
225
226        let modified = serde_json::json!({
227            "k1": "v1bis",
228            "k2": {
229                "k4": "v4bis",
230                "k4-1": "v4-1"
231            },
232            "k6": "v6"
233        });
234
235        merge_values(&mut original, modified);
236        insta::assert_json_snapshot!(original, @r###"
237        {
238          "k1": "v1",
239          "k2": {
240            "k3": "v3",
241            "k4": "v4",
242            "k4-1": "v4-1"
243          },
244          "k5": [
245            1,
246            2,
247            3
248          ],
249          "k6": "v6"
250        }
251        "###);
252    }
253
254    #[test]
255    fn test_chunk_index() {
256        assert_eq!(get_sentry_chunk_index("sentry__0", "sentry__"), Some(0));
257        assert_eq!(get_sentry_chunk_index("sentry__1", "sentry__"), Some(1));
258
259        assert_eq!(get_sentry_chunk_index("foo__0", "sentry__"), None);
260        assert_eq!(get_sentry_chunk_index("sentry__", "sentry__"), None);
261        assert_eq!(get_sentry_chunk_index("sentry__-1", "sentry__"), None);
262        assert_eq!(get_sentry_chunk_index("sentry__xx", "sentry__"), None);
263    }
264
265    #[test]
266    fn test_aggregator_empty() {
267        let aggregator = ChunkedFormDataAggregator::new();
268        assert!(aggregator.is_empty());
269        assert_eq!(aggregator.join(), "");
270    }
271
272    #[test]
273    fn test_aggregator_base_0() {
274        let mut aggregator = ChunkedFormDataAggregator::new();
275        aggregator.insert(0, "hello,");
276        aggregator.insert(1, " world");
277
278        assert!(!aggregator.is_empty());
279        assert_eq!(aggregator.join(), "hello, world");
280    }
281
282    #[test]
283    fn test_aggregator_base_1() {
284        let mut aggregator = ChunkedFormDataAggregator::new();
285        aggregator.insert(1, "hello,");
286        aggregator.insert(2, " world");
287
288        assert!(!aggregator.is_empty());
289        assert_eq!(aggregator.join(), "hello, world");
290    }
291
292    #[test]
293    fn test_aggregator_holes() {
294        let mut aggregator = ChunkedFormDataAggregator::new();
295        aggregator.insert(0, "hello,");
296        aggregator.insert(3, " world");
297
298        assert!(!aggregator.is_empty());
299        assert_eq!(aggregator.join(), "hello, world");
300    }
301
302    #[test]
303    fn test_aggregator_reversed() {
304        let mut aggregator = ChunkedFormDataAggregator::new();
305        aggregator.insert(1, " world");
306        aggregator.insert(0, "hello,");
307
308        assert!(!aggregator.is_empty());
309        assert_eq!(aggregator.join(), "hello, world");
310    }
311
312    #[test]
313    fn test_aggregator_override() {
314        let mut aggregator = ChunkedFormDataAggregator::new();
315        aggregator.insert(0, "hello,");
316        aggregator.insert(0, "bye");
317
318        assert!(!aggregator.is_empty());
319        assert_eq!(aggregator.join(), "bye");
320    }
321}