relay_server/utils/
param_parser.rs

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