1use indexmap::IndexMap;
20use sip_header::{HistoryInfo, HistoryInfoError, SipHeaderLookup, UriInfo, UriInfoError};
21
22use crate::headers::{case_alias_key, normalize_header_key};
23use crate::lookup::{variable_key, HeaderLookup};
24use crate::variables::{EslArray, EslArrayError, MAX_ARRAY_ITEMS};
25
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
60pub struct EslHeaders {
61 map: IndexMap<String, String>,
62 #[cfg_attr(feature = "serde", serde(skip))]
65 aliases: IndexMap<String, String>,
66}
67
68impl EslHeaders {
69 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn from_map(map: IndexMap<String, String>) -> Self {
76 map.into_iter()
77 .collect()
78 }
79
80 pub fn as_map(&self) -> &IndexMap<String, String> {
82 &self.map
83 }
84
85 pub fn into_map(self) -> IndexMap<String, String> {
87 self.map
88 }
89
90 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
92 let key = normalize_header_key(&key.into());
93 if let Some(alias) = case_alias_key(&key) {
94 self.aliases
95 .insert(alias, key.clone());
96 }
97 self.map
98 .insert(key, value.into());
99 }
100
101 pub fn remove(&mut self, key: &str) -> Option<String> {
103 let canonical = if self
104 .map
105 .contains_key(key)
106 {
107 key.to_string()
108 } else {
109 let alias = case_alias_key(key)?;
110 self.aliases
111 .get(&alias)?
112 .clone()
113 };
114 if let Some(alias) = case_alias_key(&canonical) {
115 self.aliases
116 .shift_remove(&alias);
117 }
118 self.map
119 .shift_remove(&canonical)
120 }
121
122 pub fn len(&self) -> usize {
124 self.map
125 .len()
126 }
127
128 pub fn is_empty(&self) -> bool {
130 self.map
131 .is_empty()
132 }
133
134 fn get(&self, name: &str) -> Option<&str> {
135 if let Some(value) = self
136 .map
137 .get(name)
138 {
139 return Some(value.as_str());
140 }
141 self.aliases
142 .get(&case_alias_key(name)?)
143 .and_then(|canonical| {
144 self.map
145 .get(canonical)
146 })
147 .map(|s| s.as_str())
148 }
149}
150
151impl<K: Into<String>, V: Into<String>> FromIterator<(K, V)> for EslHeaders {
152 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
153 let mut headers = Self::new();
154 headers.extend(iter);
155 headers
156 }
157}
158
159impl<K: Into<String>, V: Into<String>> Extend<(K, V)> for EslHeaders {
160 fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
161 for (key, value) in iter {
162 self.insert(key, value);
163 }
164 }
165}
166
167impl From<IndexMap<String, String>> for EslHeaders {
168 fn from(map: IndexMap<String, String>) -> Self {
169 Self::from_map(map)
170 }
171}
172
173#[cfg(feature = "serde")]
174impl<'de> serde::Deserialize<'de> for EslHeaders {
175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
176 where
177 D: serde::Deserializer<'de>,
178 {
179 Ok(Self::from_map(IndexMap::deserialize(deserializer)?))
180 }
181}
182
183fn strip_brackets(s: &str) -> &str {
186 if let Some(inner) = s.strip_prefix('[') {
187 if let Some(inner) = inner.strip_suffix(']') {
188 return inner;
189 }
190 }
191 s
192}
193
194fn split_entries_ref(value: &str) -> Result<Vec<&str>, EslArrayError> {
197 let value = strip_brackets(value);
198 match value.strip_prefix(EslArray::PREFIX) {
199 Some(body) => {
200 let items: Vec<&str> = body
201 .split(EslArray::SEPARATOR)
202 .collect();
203 if items.len() > MAX_ARRAY_ITEMS {
204 return Err(EslArrayError::TooManyItems {
205 count: items.len(),
206 max: MAX_ARRAY_ITEMS,
207 });
208 }
209 Ok(items)
210 }
211 None => {
212 let value = value.trim();
213 if value.is_empty() {
214 Ok(Vec::new())
215 } else {
216 Ok(sip_header::split_comma_entries(value))
217 }
218 }
219 }
220}
221
222fn entries_or_whole(value: &str) -> Vec<&str> {
225 split_entries_ref(value).unwrap_or_else(|_| vec![value])
226}
227
228impl EslHeaders {
229 pub fn parse_uri_info(value: &str) -> Result<UriInfo, UriInfoError> {
262 let entries =
263 Self::split_entries(value).map_err(|e| UriInfoError::Malformed(e.to_string()))?;
264 UriInfo::from_entries(
265 entries
266 .iter()
267 .map(String::as_str),
268 )
269 }
270
271 pub fn split_entries(value: &str) -> Result<Vec<String>, EslArrayError> {
296 Ok(split_entries_ref(value)?
297 .into_iter()
298 .map(str::to_string)
299 .collect())
300 }
301
302 #[doc(hidden)]
306 pub fn split_multi_value<'a>(value: Option<&'a str>, name: &str) -> Vec<&'a str> {
307 let Some(value) = value else {
308 return Vec::new();
309 };
310 let multi = name
311 .parse::<sip_header::SipHeader>()
312 .is_ok_and(|h| h.is_multi_valued());
313 if multi {
314 entries_or_whole(value)
315 } else {
316 vec![value]
317 }
318 }
319
320 pub fn parse_history_info(value: &str) -> Result<HistoryInfo, HistoryInfoError> {
330 let entries =
331 Self::split_entries(value).map_err(|e| HistoryInfoError::Malformed(e.to_string()))?;
332 HistoryInfo::from_entries(
333 entries
334 .iter()
335 .map(String::as_str),
336 )
337 }
338}
339
340#[doc(hidden)]
345#[macro_export]
346macro_rules! esl_sip_header_overrides {
347 () => {
348 fn sip_header_all_str<'a>(&'a self, name: &str) -> Vec<&'a str> {
349 $crate::variables::EslHeaders::split_multi_value(self.sip_header_str(name), name)
350 }
351 };
352}
353
354impl SipHeaderLookup for EslHeaders {
355 fn sip_header_str(&self, name: &str) -> Option<&str> {
356 self.get(name)
357 }
358
359 crate::esl_sip_header_overrides!();
360}
361
362impl HeaderLookup for EslHeaders {
363 fn header_str(&self, name: &str) -> Option<&str> {
364 self.get(name)
365 }
366
367 fn variable_str(&self, name: &str) -> Option<&str> {
368 self.get(&variable_key(name))
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use crate::headers::EventHeader;
376
377 #[test]
378 fn every_write_path_normalizes_the_key() {
379 let mut inserted = EslHeaders::new();
380 inserted.insert("unique-id", "abc-123");
381
382 let mut extended = EslHeaders::new();
383 extended.extend([("unique-id", "abc-123")]);
384
385 let map: IndexMap<String, String> = [("unique-id".to_string(), "abc-123".to_string())]
386 .into_iter()
387 .collect();
388
389 for h in [
390 inserted,
391 extended,
392 [("unique-id", "abc-123")]
393 .into_iter()
394 .collect(),
395 EslHeaders::from_map(map.clone()),
396 EslHeaders::from(map),
397 ] {
398 assert_eq!(
399 h.as_map()
400 .keys()
401 .collect::<Vec<_>>(),
402 vec!["Unique-ID"]
403 );
404 assert_eq!(h.unique_id(), Some("abc-123"));
405 }
406 }
407
408 #[test]
409 fn lookup_resolves_a_non_canonical_spelling() {
410 let mut h = EslHeaders::new();
411 h.insert("Unique-ID", "abc-123");
412 h.insert("Call-Info", "<sip:a@example.test>;purpose=icon");
413 assert_eq!(h.header_str("unique-id"), Some("abc-123"));
414 assert_eq!(h.header_str("UNIQUE-ID"), Some("abc-123"));
415 assert_eq!(
416 h.sip_header_str("call-info"),
417 Some("<sip:a@example.test>;purpose=icon")
418 );
419 }
420
421 #[test]
422 fn underscore_keys_keep_their_wire_casing() {
423 let mut h = EslHeaders::new();
424 h.insert("variable_sip_h_X-My-CUSTOM-Header", "val");
425 assert_eq!(
426 h.header_str("variable_sip_h_X-My-CUSTOM-Header"),
427 Some("val")
428 );
429 assert_eq!(h.header_str("variable_sip_h_x-my-custom-header"), None);
430 }
431
432 #[test]
433 fn two_spellings_of_one_header_are_one_entry() {
434 let mut h = EslHeaders::new();
435 h.insert("Channel-Read-Codec-Bit-Rate", "first");
436 h.insert("channel-read-codec-bit-rate", "second");
437 assert_eq!(h.len(), 1);
438 assert_eq!(h.header_str("Channel-Read-Codec-Bit-Rate"), Some("second"));
439 }
440
441 #[test]
442 fn remove_accepts_a_non_canonical_spelling() {
443 let mut h = EslHeaders::new();
444 h.insert("Unique-ID", "abc-123");
445 assert_eq!(h.remove("unique-id"), Some("abc-123".to_string()));
446 assert!(h.is_empty());
447 assert_eq!(h.header_str("Unique-ID"), None);
448 assert_eq!(h.remove("unique-id"), None);
449 }
450
451 #[test]
452 fn remove_by_canonical_key_drops_the_alias_too() {
453 let mut h = EslHeaders::new();
454 h.insert("unique-id", "abc-123");
455 assert_eq!(h.remove("Unique-ID"), Some("abc-123".to_string()));
456 assert_eq!(h.header_str("unique-id"), None);
457 }
458
459 #[cfg(feature = "serde")]
460 #[test]
461 fn deserialization_normalizes_like_the_wire_does() {
462 let json = r#"{"unique-id":"abc-123","variable_sip_call_id":"call-1"}"#;
463 let h: EslHeaders = serde_json::from_str(json).expect("map payload");
464 assert_eq!(h.unique_id(), Some("abc-123"));
465 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
466 assert_eq!(
467 h.as_map()
468 .keys()
469 .collect::<Vec<_>>(),
470 vec!["Unique-ID", "variable_sip_call_id"]
471 );
472
473 let round_tripped: EslHeaders =
474 serde_json::from_str(&serde_json::to_string(&h).expect("serialize"))
475 .expect("round trip");
476 assert_eq!(round_tripped, h);
477 }
478
479 #[test]
480 fn array_encoding_decodes_for_every_multi_value_header() {
481 let mut h = EslHeaders::new();
482 h.insert(
483 "P-Asserted-Identity",
484 "ARRAY::<sip:alice@example.test>|:<tel:+15551234567>",
485 );
486 h.insert(
487 "Error-Info",
488 "ARRAY::<sip:busy@example.test>|:<http://example.test/why.html>",
489 );
490 h.insert(
491 "Contact",
492 "ARRAY::<sip:a@192.0.2.1>;expires=60|:<sip:b@192.0.2.2>",
493 );
494 h.insert(
495 "Via",
496 "ARRAY::SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bK1|:SIP/2.0/TCP 192.0.2.2;branch=z9hG4bK2",
497 );
498 h.insert(
499 "Warning",
500 "ARRAY::370 192.0.2.1 \"Insufficient bandwidth\"|:399 192.0.2.2 \"Misc, warning\"",
501 );
502 h.insert("Accept", "ARRAY::application/sdp|:text/plain;q=0.5");
503
504 assert_eq!(
505 h.via()
506 .expect("ARRAY value must decode")
507 .expect("header is present")
508 .len(),
509 2
510 );
511 assert_eq!(
512 h.warning()
513 .expect("ARRAY value must decode")
514 .expect("header is present")
515 .len(),
516 2
517 );
518 assert_eq!(
519 h.accept()
520 .expect("ARRAY value must decode")
521 .expect("header is present")
522 .len(),
523 2
524 );
525 assert_eq!(
526 h.p_asserted_identity()
527 .expect("ARRAY value must decode")
528 .len(),
529 2
530 );
531 assert_eq!(
532 h.error_info()
533 .expect("ARRAY value must decode")
534 .expect("header is present")
535 .entries()
536 .len(),
537 2
538 );
539 assert_eq!(
540 h.contact()
541 .expect("ARRAY value must decode")
542 .len(),
543 2
544 );
545 }
546
547 #[test]
548 fn a_single_valued_header_keeps_its_commas() {
549 let mut h = EslHeaders::new();
550 h.insert("Subject", "one, two");
551 assert_eq!(h.sip_header_all_str("Subject"), vec!["one, two"]);
552 }
553
554 #[test]
555 fn header_str_passthrough() {
556 let mut h = EslHeaders::new();
557 h.insert("Unique-ID", "abc-123");
558 assert_eq!(h.header_str("Unique-ID"), Some("abc-123"));
559 }
560
561 #[test]
562 fn variable_str_prepends_variable_prefix() {
563 let mut h = EslHeaders::new();
564 h.insert("variable_sip_call_id", "call-1");
565 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
566 assert_eq!(h.variable_str("missing"), None);
567 }
568
569 #[test]
572 fn both_entry_points_decode_every_transport_form() {
573 let forms = [
574 (
575 "rfc",
576 "<sip:alice@example.test>;purpose=icon",
577 vec![Some("icon")],
578 ),
579 (
580 "array",
581 "ARRAY::<sip:a@example.test>;purpose=icon|:<sip:b@example.test>;purpose=info",
582 vec![Some("icon"), Some("info")],
583 ),
584 (
585 "bracketed",
586 "[<sip:alice@example.test>;purpose=icon]",
587 vec![Some("icon")],
588 ),
589 (
590 "bracketed array",
591 "[ARRAY::<sip:a@example.test>;purpose=icon|:<sip:b@example.test>]",
592 vec![Some("icon"), None],
593 ),
594 ];
595
596 for (label, value, purposes) in forms {
597 let mut h = EslHeaders::new();
598 h.insert("Call-Info", value);
599 let from_header = h
600 .call_info()
601 .unwrap_or_else(|e| panic!("{label}: {e}"))
602 .unwrap_or_else(|| panic!("{label}: header is present"));
603 let from_value = EslHeaders::parse_uri_info(value)
604 .unwrap_or_else(|e| panic!("{label} as a raw value: {e}"));
605 assert_eq!(from_header, from_value, "{label}");
606 assert_eq!(
607 from_header
608 .entries()
609 .iter()
610 .map(|e| e.purpose())
611 .collect::<Vec<_>>(),
612 purposes,
613 "{label}"
614 );
615 }
616 }
617
618 #[test]
619 fn call_info_absent_is_ok_none() {
620 let h = EslHeaders::new();
621 assert!(h
622 .call_info()
623 .unwrap()
624 .is_none());
625 }
626
627 #[test]
628 fn history_info_array_encoding() {
629 let mut h = EslHeaders::new();
630 h.insert(
631 "History-Info",
632 "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1",
633 );
634 let hi = h
635 .history_info()
636 .unwrap()
637 .expect("present");
638 assert_eq!(
639 hi.entries()
640 .len(),
641 2
642 );
643 }
644
645 #[test]
646 fn header_lookup_typed_accessors() {
647 let mut h = EslHeaders::new();
648 h.insert(EventHeader::UniqueId.as_str(), "uuid-1");
649 h.insert(EventHeader::ChannelName.as_str(), "sofia/a/b");
650 assert_eq!(h.unique_id(), Some("uuid-1"));
651 assert_eq!(h.channel_name(), Some("sofia/a/b"));
652 }
653
654 #[test]
655 fn parse_uri_info_empty_value() {
656 assert!(EslHeaders::parse_uri_info("").is_err());
657 }
658
659 #[test]
660 fn parse_uri_info_malformed_no_panic() {
661 let info = EslHeaders::parse_uri_info("sip:bare@example.test").expect("lenient parse");
663 assert_eq!(
664 info.entries()
665 .len(),
666 1
667 );
668 }
669
670 #[test]
671 fn parse_uri_info_structural_failure_is_malformed() {
672 let value = format!(
673 "ARRAY::{}",
674 vec!["<sip:a@example.com>"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
675 );
676 let err = EslHeaders::parse_uri_info(&value).expect_err("over-limit array must fail");
677 assert!(matches!(err, UriInfoError::Malformed(_)), "got {err:?}");
678 }
679
680 #[test]
681 fn parse_history_info_structural_failure_is_malformed() {
682 let value = format!(
683 "ARRAY::{}",
684 vec!["<sip:a@example.com>;index=1"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
685 );
686 let err = EslHeaders::parse_history_info(&value).expect_err("over-limit array must fail");
687 assert!(matches!(err, HistoryInfoError::Malformed(_)), "got {err:?}");
688 }
689
690 #[test]
691 fn collects_from_key_value_pairs() {
692 let h: EslHeaders = [
693 ("Unique-ID", "uuid-1"),
694 ("Caller-Destination-Number", "911"),
695 ]
696 .into_iter()
697 .collect();
698 assert_eq!(h.header_str("Unique-ID"), Some("uuid-1"));
699 }
700
701 #[test]
702 fn extend_adds_pairs() {
703 let mut h = EslHeaders::new();
704 h.insert("Unique-ID", "uuid-1");
705 h.extend([("Channel-Name", "sofia/a/b")]);
706 assert_eq!(h.len(), 2);
707 }
708
709 #[test]
710 fn split_entries_array_form() {
711 let entries =
712 EslHeaders::split_entries("ARRAY::<sip:a@example.test>|:<sip:b@example.test>")
713 .expect("ARRAY form");
714 assert_eq!(entries, ["<sip:a@example.test>", "<sip:b@example.test>"]);
715 }
716
717 #[test]
718 fn split_entries_rfc_comma_form() {
719 let entries = EslHeaders::split_entries(
720 "<sip:a@example.test>;text=\"one, two\",<sip:b@example.test>",
721 )
722 .expect("RFC form");
723 assert_eq!(entries.len(), 2);
724 assert_eq!(entries[0], "<sip:a@example.test>;text=\"one, two\"");
725 }
726
727 #[test]
728 fn split_entries_bracket_wrapped() {
729 let entries =
730 EslHeaders::split_entries("[<sip:a@example.test>;purpose=icon]").expect("bracket form");
731 assert_eq!(entries, ["<sip:a@example.test>;purpose=icon"]);
732 }
733
734 #[test]
735 fn split_entries_blank_value_yields_no_entries() {
736 let entries = EslHeaders::split_entries(" ").expect("blank value");
737 assert!(entries.is_empty());
738 }
739
740 #[test]
741 fn parse_history_info_blank_value_is_empty_error() {
742 let err = EslHeaders::parse_history_info(" ").expect_err("blank value must fail");
743 assert!(matches!(err, HistoryInfoError::Empty), "got {err:?}");
744 }
745
746 #[test]
747 fn split_entries_too_many_items_is_err() {
748 let value = format!(
749 "ARRAY::{}",
750 vec!["<sip:a@example.test>"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
751 );
752 let err = EslHeaders::split_entries(&value).expect_err("over-limit array must fail");
753 assert!(
754 matches!(err, EslArrayError::TooManyItems { .. }),
755 "got {err:?}"
756 );
757 }
758
759 #[test]
760 fn parse_history_info_array_form() {
761 let value = "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1";
762 let info = EslHeaders::parse_history_info(value).expect("parse ARRAY form");
763 assert_eq!(
764 info.entries()
765 .len(),
766 2
767 );
768 }
769}