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