Skip to main content

mail_auth/dkim2/
recipe_serializer.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use crate::dkim2::recipe::{BodyRecipe, HeaderRecipe, Recipe, Step};
8use serde::de::{Deserialize, Deserializer, IgnoredAny, MapAccess, SeqAccess, Unexpected, Visitor};
9use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
10use std::borrow::Cow;
11use std::fmt;
12
13impl Serialize for Recipe {
14    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
15        let mut len = usize::from(!self.headers.is_empty());
16        if !matches!(self.body, BodyRecipe::None) {
17            len += 1;
18        }
19
20        let mut map = serializer.serialize_map(Some(len))?;
21        if !self.headers.is_empty() {
22            map.serialize_entry("h", &HeaderMapRef(&self.headers))?;
23        }
24        match &self.body {
25            BodyRecipe::None => {}
26            BodyRecipe::Unreconstructable => map.serialize_entry("b", &())?,
27            BodyRecipe::Steps(steps) => map.serialize_entry("b", &StepsRef(steps))?,
28        }
29        map.end()
30    }
31}
32
33struct HeaderMapRef<'x>(&'x [HeaderRecipe]);
34
35impl Serialize for HeaderMapRef<'_> {
36    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37        let mut map = serializer.serialize_map(Some(self.0.len()))?;
38        for header in self.0 {
39            map.serialize_entry(header.name.as_str(), &StepsRef(&header.steps))?;
40        }
41        map.end()
42    }
43}
44
45struct StepsRef<'x>(&'x [Step]);
46
47impl Serialize for StepsRef<'_> {
48    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
49        let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
50        for step in self.0 {
51            seq.serialize_element(&StepRef(step))?;
52        }
53        seq.end()
54    }
55}
56
57struct StepRef<'x>(&'x Step);
58
59impl Serialize for StepRef<'_> {
60    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
61        let mut map = serializer.serialize_map(Some(1))?;
62        match self.0 {
63            Step::Copy { start, end } => map.serialize_entry("c", &[*start, *end])?,
64            Step::Data(values) => map.serialize_entry("d", values)?,
65        }
66        map.end()
67    }
68}
69
70impl<'de> Deserialize<'de> for Recipe {
71    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
72        deserializer.deserialize_map(RecipeVisitor)
73    }
74}
75
76struct RecipeVisitor;
77
78impl<'de> Visitor<'de> for RecipeVisitor {
79    type Value = Recipe;
80
81    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        f.write_str("a recipe object")
83    }
84
85    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Recipe, A::Error> {
86        let mut headers = Vec::new();
87        let mut body = BodyRecipe::None;
88        while let Some(key) = map.next_key::<Cow<str>>()? {
89            match key.as_ref() {
90                "h" => headers = map.next_value::<HeaderMap>()?.0,
91                "b" => {
92                    body = match map.next_value::<Option<Steps>>()? {
93                        None => BodyRecipe::Unreconstructable,
94                        Some(steps) => BodyRecipe::Steps(steps.0),
95                    };
96                }
97                _ => {
98                    map.next_value::<IgnoredAny>()?;
99                }
100            }
101        }
102        Ok(Recipe { headers, body })
103    }
104}
105
106struct HeaderMap(Vec<HeaderRecipe>);
107
108impl<'de> Deserialize<'de> for HeaderMap {
109    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
110        deserializer.deserialize_map(HeaderMapVisitor)
111    }
112}
113
114struct HeaderMapVisitor;
115
116impl<'de> Visitor<'de> for HeaderMapVisitor {
117    type Value = HeaderMap;
118
119    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        f.write_str("a map of header names to step arrays")
121    }
122
123    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<HeaderMap, A::Error> {
124        let mut headers = Vec::new();
125        while let Some(name) = map.next_key::<String>()? {
126            let steps = map.next_value::<Steps>()?;
127            headers.push(HeaderRecipe {
128                name,
129                steps: steps.0,
130            });
131        }
132        Ok(HeaderMap(headers))
133    }
134}
135
136struct Steps(Vec<Step>);
137
138impl<'de> Deserialize<'de> for Steps {
139    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
140        deserializer.deserialize_seq(StepsVisitor)
141    }
142}
143
144struct StepsVisitor;
145
146impl<'de> Visitor<'de> for StepsVisitor {
147    type Value = Steps;
148
149    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        f.write_str("an array of recipe steps")
151    }
152
153    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Steps, A::Error> {
154        let mut steps = Vec::with_capacity(seq.size_hint().unwrap_or(0));
155        while let Some(StepEntry(step)) = seq.next_element::<StepEntry>()? {
156            if let Some(step) = step {
157                steps.push(step);
158            }
159        }
160        Ok(Steps(steps))
161    }
162}
163
164struct StepEntry(Option<Step>);
165
166impl<'de> Deserialize<'de> for StepEntry {
167    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
168        deserializer.deserialize_any(StepEntryVisitor)
169    }
170}
171
172struct StepEntryVisitor;
173
174impl<'de> Visitor<'de> for StepEntryVisitor {
175    type Value = StepEntry;
176
177    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
178        f.write_str("a recipe step")
179    }
180
181    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<StepEntry, A::Error> {
182        let mut copy: Option<(u32, u32)> = None;
183        let mut data: Option<Vec<String>> = None;
184        while let Some(key) = map.next_key::<Cow<str>>()? {
185            match key.as_ref() {
186                "c" if copy.is_none() => {
187                    if let CopyField::Range(start, end) = map.next_value::<CopyField>()? {
188                        copy = Some((start, end));
189                    }
190                }
191                "d" if data.is_none() => {
192                    if let DataField::Values(values) = map.next_value::<DataField>()? {
193                        data = Some(values);
194                    }
195                }
196                _ => {
197                    map.next_value::<IgnoredAny>()?;
198                }
199            }
200        }
201
202        let step = if let Some((start, end)) = copy {
203            Some(Step::Copy { start, end })
204        } else {
205            data.map(Step::Data)
206        };
207        Ok(StepEntry(step))
208    }
209
210    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<StepEntry, A::Error> {
211        while seq.next_element::<IgnoredAny>()?.is_some() {}
212        Ok(StepEntry(None))
213    }
214
215    fn visit_bool<E>(self, _: bool) -> Result<StepEntry, E> {
216        Ok(StepEntry(None))
217    }
218
219    fn visit_i64<E>(self, _: i64) -> Result<StepEntry, E> {
220        Ok(StepEntry(None))
221    }
222
223    fn visit_u64<E>(self, _: u64) -> Result<StepEntry, E> {
224        Ok(StepEntry(None))
225    }
226
227    fn visit_f64<E>(self, _: f64) -> Result<StepEntry, E> {
228        Ok(StepEntry(None))
229    }
230
231    fn visit_str<E>(self, _: &str) -> Result<StepEntry, E> {
232        Ok(StepEntry(None))
233    }
234
235    fn visit_none<E>(self) -> Result<StepEntry, E> {
236        Ok(StepEntry(None))
237    }
238
239    fn visit_unit<E>(self) -> Result<StepEntry, E> {
240        Ok(StepEntry(None))
241    }
242}
243
244enum CopyField {
245    Range(u32, u32),
246    Other,
247}
248
249impl<'de> Deserialize<'de> for CopyField {
250    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
251        deserializer.deserialize_any(CopyFieldVisitor)
252    }
253}
254
255struct CopyFieldVisitor;
256
257impl<'de> Visitor<'de> for CopyFieldVisitor {
258    type Value = CopyField;
259
260    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
261        f.write_str("a [start, end] array")
262    }
263
264    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<CopyField, A::Error> {
265        let start = seq
266            .next_element::<CopyIndex>()?
267            .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
268        let end = seq
269            .next_element::<CopyIndex>()?
270            .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
271        while seq.next_element::<IgnoredAny>()?.is_some() {}
272        Ok(CopyField::Range(start.0, end.0))
273    }
274
275    fn visit_bool<E>(self, _: bool) -> Result<CopyField, E> {
276        Ok(CopyField::Other)
277    }
278
279    fn visit_i64<E>(self, _: i64) -> Result<CopyField, E> {
280        Ok(CopyField::Other)
281    }
282
283    fn visit_u64<E>(self, _: u64) -> Result<CopyField, E> {
284        Ok(CopyField::Other)
285    }
286
287    fn visit_f64<E>(self, _: f64) -> Result<CopyField, E> {
288        Ok(CopyField::Other)
289    }
290
291    fn visit_str<E>(self, _: &str) -> Result<CopyField, E> {
292        Ok(CopyField::Other)
293    }
294
295    fn visit_none<E>(self) -> Result<CopyField, E> {
296        Ok(CopyField::Other)
297    }
298
299    fn visit_unit<E>(self) -> Result<CopyField, E> {
300        Ok(CopyField::Other)
301    }
302
303    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<CopyField, A::Error> {
304        while map.next_entry::<IgnoredAny, IgnoredAny>()?.is_some() {}
305        Ok(CopyField::Other)
306    }
307}
308
309struct CopyIndex(u32);
310
311impl<'de> Deserialize<'de> for CopyIndex {
312    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
313        deserializer.deserialize_any(CopyIndexVisitor)
314    }
315}
316
317struct CopyIndexVisitor;
318
319impl Visitor<'_> for CopyIndexVisitor {
320    type Value = CopyIndex;
321
322    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
323        f.write_str("a non-negative integer")
324    }
325
326    fn visit_u64<E>(self, value: u64) -> Result<CopyIndex, E> {
327        Ok(CopyIndex(value as u32))
328    }
329
330    fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<CopyIndex, E> {
331        if value >= 0 {
332            Ok(CopyIndex(value as u32))
333        } else {
334            Err(E::invalid_value(Unexpected::Signed(value), &self))
335        }
336    }
337}
338
339enum DataField {
340    Values(Vec<String>),
341    Other,
342}
343
344impl<'de> Deserialize<'de> for DataField {
345    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
346        deserializer.deserialize_any(DataFieldVisitor)
347    }
348}
349
350struct DataFieldVisitor;
351
352impl<'de> Visitor<'de> for DataFieldVisitor {
353    type Value = DataField;
354
355    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
356        f.write_str("an array of strings")
357    }
358
359    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<DataField, A::Error> {
360        let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0));
361        while let Some(value) = seq.next_element::<String>()? {
362            values.push(value);
363        }
364        Ok(DataField::Values(values))
365    }
366
367    fn visit_bool<E>(self, _: bool) -> Result<DataField, E> {
368        Ok(DataField::Other)
369    }
370
371    fn visit_i64<E>(self, _: i64) -> Result<DataField, E> {
372        Ok(DataField::Other)
373    }
374
375    fn visit_u64<E>(self, _: u64) -> Result<DataField, E> {
376        Ok(DataField::Other)
377    }
378
379    fn visit_f64<E>(self, _: f64) -> Result<DataField, E> {
380        Ok(DataField::Other)
381    }
382
383    fn visit_str<E>(self, _: &str) -> Result<DataField, E> {
384        Ok(DataField::Other)
385    }
386
387    fn visit_none<E>(self) -> Result<DataField, E> {
388        Ok(DataField::Other)
389    }
390
391    fn visit_unit<E>(self) -> Result<DataField, E> {
392        Ok(DataField::Other)
393    }
394
395    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<DataField, A::Error> {
396        while map.next_entry::<IgnoredAny, IgnoredAny>()?.is_some() {}
397        Ok(DataField::Other)
398    }
399}
400
401#[cfg(test)]
402mod test {
403    use super::*;
404
405    fn json(recipe: &Recipe) -> Vec<u8> {
406        let mut out = Vec::new();
407        recipe.to_json(&mut out).unwrap();
408        out
409    }
410
411    fn r_headers(name: &str, steps: Vec<Step>) -> Recipe {
412        Recipe {
413            headers: vec![hr(name, steps)],
414            body: BodyRecipe::None,
415        }
416    }
417
418    fn hr(name: &str, steps: Vec<Step>) -> HeaderRecipe {
419        HeaderRecipe {
420            name: name.to_string(),
421            steps,
422        }
423    }
424
425    fn copy(start: u32, end: u32) -> Step {
426        Step::Copy { start, end }
427    }
428
429    fn data(values: &[&str]) -> Step {
430        Step::Data(values.iter().map(|v| v.to_string()).collect())
431    }
432
433    fn with_body(steps: Vec<Step>) -> Recipe {
434        Recipe {
435            headers: vec![],
436            body: BodyRecipe::Steps(steps),
437        }
438    }
439
440    /// Asserts `input` deserializes to exactly `expected`, and that re-encoding the
441    /// canonical form and decoding it again yields the same value.
442    fn check_decode(input: &[u8], expected: Recipe) {
443        let label = String::from_utf8_lossy(input).into_owned();
444        let recipe = Recipe::from_json(input).unwrap_or_else(|_| panic!("decode failed: {label}"));
445        assert_eq!(recipe, expected, "decoding {label}");
446        let reparsed = Recipe::from_json(&json(&recipe)).expect("re-decode failed");
447        assert_eq!(reparsed, expected, "re-decoding canonical form of {label}");
448    }
449
450    /// Asserts `recipe` serializes to exactly `expected` bytes, and that those bytes
451    /// deserialize back to the same value.
452    fn check_encode(recipe: Recipe, expected: &[u8]) {
453        assert_eq!(json(&recipe), expected, "encoding {recipe:?}");
454        let decoded = Recipe::from_json(expected).expect("decode of encoded form failed");
455        assert_eq!(
456            decoded,
457            recipe,
458            "decoding {}",
459            String::from_utf8_lossy(expected)
460        );
461    }
462
463    #[test]
464    fn serialize_byte_equality() {
465        check_encode(
466            r_headers("list-unsubscribe", vec![]),
467            b"{\"h\":{\"list-unsubscribe\":[]}}",
468        );
469        check_encode(with_body(vec![copy(1, 1)]), b"{\"b\":[{\"c\":[1,1]}]}");
470        check_encode(
471            r_headers("subject", vec![data(&[" Simple test message"])]),
472            b"{\"h\":{\"subject\":[{\"d\":[\" Simple test message\"]}]}}",
473        );
474        check_encode(
475            r_headers("authentication-results", vec![copy(1, 3)]),
476            b"{\"h\":{\"authentication-results\":[{\"c\":[1,3]}]}}",
477        );
478        check_encode(
479            Recipe {
480                headers: vec![],
481                body: BodyRecipe::Unreconstructable,
482            },
483            b"{\"b\":null}",
484        );
485        check_encode(Recipe::default(), b"{}");
486    }
487
488    #[test]
489    fn serialize_preserves_insertion_order() {
490        check_encode(
491            Recipe {
492                headers: vec![hr("to", vec![copy(1, 1)]), hr("from", vec![copy(2, 2)])],
493                body: BodyRecipe::Steps(vec![copy(1, 2)]),
494            },
495            b"{\"h\":{\"to\":[{\"c\":[1,1]}],\"from\":[{\"c\":[2,2]}]},\"b\":[{\"c\":[1,2]}]}",
496        );
497    }
498
499    #[test]
500    fn round_trips_combined_headers_and_body() {
501        check_encode(
502            Recipe {
503                headers: vec![hr("x", vec![copy(1, 2), data(&["a", "b"])])],
504                body: BodyRecipe::Steps(vec![data(&[""])]),
505            },
506            b"{\"h\":{\"x\":[{\"c\":[1,2]},{\"d\":[\"a\",\"b\"]}]},\"b\":[{\"d\":[\"\"]}]}",
507        );
508    }
509
510    #[test]
511    fn deserialize_preserves_header_name_case() {
512        check_decode(
513            b"{\"h\":{\"SUBject\":[{\"c\":[1,1]}]}}",
514            r_headers("SUBject", vec![copy(1, 1)]),
515        );
516    }
517
518    #[test]
519    fn deserialize_body_absent_is_none() {
520        check_decode(b"{\"h\":{\"x\":[]}}", r_headers("x", vec![]));
521    }
522
523    #[test]
524    fn deserialize_body_null_is_unreconstructable() {
525        check_decode(
526            b"{\"b\":null}",
527            Recipe {
528                headers: vec![],
529                body: BodyRecipe::Unreconstructable,
530            },
531        );
532    }
533
534    #[test]
535    fn deserialize_rejects_null_header_value() {
536        assert!(Recipe::from_json(b"{\"h\":null}").is_err());
537        assert!(Recipe::from_json(b"{\"h\":{\"subject\":null}}").is_err());
538    }
539
540    #[test]
541    fn deserialize_rejects_non_object_top_level() {
542        assert!(Recipe::from_json(b"[]").is_err());
543        assert!(Recipe::from_json(b"null").is_err());
544        assert!(Recipe::from_json(b"\"x\"").is_err());
545        assert!(Recipe::from_json(b"42").is_err());
546    }
547
548    #[test]
549    fn deserialize_rejects_non_array_steps() {
550        assert!(Recipe::from_json(b"{\"b\":5}").is_err());
551        assert!(Recipe::from_json(b"{\"b\":\"x\"}").is_err());
552        assert!(Recipe::from_json(b"{\"h\":{\"x\":5}}").is_err());
553    }
554
555    #[test]
556    fn deserialize_ignores_unknown_top_level_keys() {
557        check_decode(
558            b"{\"z\":123,\"b\":[{\"c\":[1,1]}]}",
559            with_body(vec![copy(1, 1)]),
560        );
561    }
562
563    #[test]
564    fn deserialize_ignores_unknown_step_objects() {
565        check_decode(
566            b"{\"b\":[{\"c\":[1,2]},{\"z\":true}]}",
567            with_body(vec![copy(1, 2)]),
568        );
569    }
570
571    #[test]
572    fn deserialize_skips_non_object_step_elements() {
573        check_decode(
574            b"{\"b\":[1,\"x\",[9],null,{\"c\":[3,4]}]}",
575            with_body(vec![copy(3, 4)]),
576        );
577    }
578
579    #[test]
580    fn deserialize_copy_precedence_over_data() {
581        check_decode(
582            b"{\"b\":[{\"d\":[\"x\"],\"c\":[1,2]}]}",
583            with_body(vec![copy(1, 2)]),
584        );
585    }
586
587    #[test]
588    fn deserialize_non_array_copy_falls_through_to_data() {
589        check_decode(
590            b"{\"b\":[{\"c\":5,\"d\":[\"x\"]}]}",
591            with_body(vec![data(&["x"])]),
592        );
593    }
594
595    #[test]
596    fn deserialize_extra_ignored_step_keys() {
597        check_decode(
598            b"{\"b\":[{\"c\":[1,2],\"x\":7,\"y\":[1,2,3]}]}",
599            with_body(vec![copy(1, 2)]),
600        );
601    }
602
603    #[test]
604    fn deserialize_copy_extra_elements_ignored() {
605        check_decode(b"{\"b\":[{\"c\":[1,2,3,4]}]}", with_body(vec![copy(1, 2)]));
606    }
607
608    #[test]
609    fn deserialize_rejects_short_copy_array() {
610        assert!(Recipe::from_json(b"{\"b\":[{\"c\":[1]}]}").is_err());
611        assert!(Recipe::from_json(b"{\"b\":[{\"c\":[]}]}").is_err());
612    }
613
614    #[test]
615    fn deserialize_rejects_non_integer_copy() {
616        assert!(Recipe::from_json(b"{\"b\":[{\"c\":[1.5,2]}]}").is_err());
617        assert!(Recipe::from_json(b"{\"b\":[{\"c\":[-1,2]}]}").is_err());
618        assert!(Recipe::from_json(b"{\"b\":[{\"c\":[\"1\",\"2\"]}]}").is_err());
619    }
620
621    #[test]
622    fn deserialize_rejects_non_string_data() {
623        assert!(Recipe::from_json(b"{\"b\":[{\"d\":[1]}]}").is_err());
624        assert!(Recipe::from_json(b"{\"b\":[{\"d\":[null]}]}").is_err());
625    }
626
627    #[test]
628    fn deserialize_max_copy_index() {
629        check_decode(
630            b"{\"b\":[{\"c\":[4294967295,4294967295]}]}",
631            with_body(vec![copy(u32::MAX, u32::MAX)]),
632        );
633    }
634
635    #[test]
636    fn deserialize_empty_data_array() {
637        check_decode(b"{\"b\":[{\"d\":[]}]}", with_body(vec![data(&[])]));
638    }
639
640    #[test]
641    fn deserialize_escaped_keys() {
642        check_decode(
643            b"{\"\\u0062\":[{\"\\u0063\":[1,2]}]}",
644            with_body(vec![copy(1, 2)]),
645        );
646    }
647
648    #[test]
649    fn non_ascii_round_trips() {
650        check_encode(
651            r_headers("subject", vec![data(&["café \u{1}"])]),
652            "{\"h\":{\"subject\":[{\"d\":[\"café \\u0001\"]}]}}".as_bytes(),
653        );
654    }
655
656    #[test]
657    fn deserialize_rejects_trailing_garbage() {
658        assert!(Recipe::from_json(b"{\"b\":null} trailing").is_err());
659        assert!(Recipe::from_json(b"{}{}").is_err());
660    }
661}