1use 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 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 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}