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_rows(value: &str) -> Result<Vec<&str>, EslArrayError> {
197 let value = strip_brackets(value);
198 let Some(body) = value.strip_prefix(EslArray::PREFIX) else {
199 return Ok(vec![value]);
200 };
201 let items: Vec<&str> = body
202 .split(EslArray::SEPARATOR)
203 .take(MAX_ARRAY_ITEMS + 1)
204 .collect();
205 if items.len() > MAX_ARRAY_ITEMS {
206 return Err(EslArrayError::TooManyItems {
207 count: body
208 .split(EslArray::SEPARATOR)
209 .count(),
210 max: MAX_ARRAY_ITEMS,
211 });
212 }
213 Ok(items)
214}
215
216fn split_entries_ref(value: &str) -> Result<Vec<&str>, EslArrayError> {
219 Ok(split_rows(value)?
220 .into_iter()
221 .filter(|row| {
222 !row.trim()
223 .is_empty()
224 })
225 .flat_map(sip_header::split_comma_entries)
226 .collect())
227}
228
229fn rows_or_whole(value: &str) -> Vec<&str> {
232 split_rows(value).unwrap_or_else(|_| vec![value])
233}
234
235impl EslHeaders {
236 pub fn parse_uri_info(value: &str) -> Result<UriInfo, UriInfoError> {
269 let entries =
270 Self::split_entries(value).map_err(|e| UriInfoError::Malformed(e.to_string()))?;
271 UriInfo::from_entries(
272 entries
273 .iter()
274 .map(String::as_str),
275 )
276 }
277
278 pub fn split_entries(value: &str) -> Result<Vec<String>, EslArrayError> {
303 Ok(split_entries_ref(value)?
304 .into_iter()
305 .map(str::to_string)
306 .collect())
307 }
308
309 #[doc(hidden)]
313 pub fn split_multi_value<'a>(value: Option<&'a str>, name: &str) -> Vec<&'a str> {
314 let Some(value) = value else {
315 return Vec::new();
316 };
317 let multi = name
318 .parse::<sip_header::SipHeader>()
319 .is_ok_and(|h| h.is_multi_valued());
320 if multi {
321 rows_or_whole(value)
322 } else {
323 vec![value]
324 }
325 }
326
327 pub fn parse_history_info(value: &str) -> Result<HistoryInfo, HistoryInfoError> {
337 let entries =
338 Self::split_entries(value).map_err(|e| HistoryInfoError::Malformed(e.to_string()))?;
339 HistoryInfo::from_entries(
340 entries
341 .iter()
342 .map(String::as_str),
343 )
344 }
345}
346
347#[doc(hidden)]
352#[macro_export]
353macro_rules! esl_sip_header_overrides {
354 () => {
355 fn sip_header_all_str<'a>(&'a self, name: &str) -> Vec<&'a str> {
356 $crate::variables::EslHeaders::split_multi_value(self.sip_header_str(name), name)
357 }
358 };
359}
360
361impl SipHeaderLookup for EslHeaders {
362 fn sip_header_str(&self, name: &str) -> Option<&str> {
363 self.get(name)
364 }
365
366 crate::esl_sip_header_overrides!();
367}
368
369impl HeaderLookup for EslHeaders {
370 fn header_str(&self, name: &str) -> Option<&str> {
371 self.get(name)
372 }
373
374 fn variable_str(&self, name: &str) -> Option<&str> {
375 self.get(&variable_key(name))
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::headers::EventHeader;
383
384 #[test]
385 fn every_write_path_normalizes_the_key() {
386 let mut inserted = EslHeaders::new();
387 inserted.insert("unique-id", "abc-123");
388
389 let mut extended = EslHeaders::new();
390 extended.extend([("unique-id", "abc-123")]);
391
392 let map: IndexMap<String, String> = [("unique-id".to_string(), "abc-123".to_string())]
393 .into_iter()
394 .collect();
395
396 for h in [
397 inserted,
398 extended,
399 [("unique-id", "abc-123")]
400 .into_iter()
401 .collect(),
402 EslHeaders::from_map(map.clone()),
403 EslHeaders::from(map),
404 ] {
405 assert_eq!(
406 h.as_map()
407 .keys()
408 .collect::<Vec<_>>(),
409 vec!["Unique-ID"]
410 );
411 assert_eq!(h.unique_id(), Some("abc-123"));
412 }
413 }
414
415 #[test]
416 fn lookup_resolves_a_non_canonical_spelling() {
417 let mut h = EslHeaders::new();
418 h.insert("Unique-ID", "abc-123");
419 h.insert("Call-Info", "<sip:a@example.test>;purpose=icon");
420 assert_eq!(h.header_str("unique-id"), Some("abc-123"));
421 assert_eq!(h.header_str("UNIQUE-ID"), Some("abc-123"));
422 assert_eq!(
423 h.sip_header_str("call-info"),
424 Some("<sip:a@example.test>;purpose=icon")
425 );
426 }
427
428 #[test]
429 fn underscore_keys_keep_their_wire_casing() {
430 let mut h = EslHeaders::new();
431 h.insert("variable_sip_h_X-My-CUSTOM-Header", "val");
432 assert_eq!(
433 h.header_str("variable_sip_h_X-My-CUSTOM-Header"),
434 Some("val")
435 );
436 assert_eq!(h.header_str("variable_sip_h_x-my-custom-header"), None);
437 }
438
439 #[test]
440 fn two_spellings_of_one_header_are_one_entry() {
441 let mut h = EslHeaders::new();
442 h.insert("Channel-Read-Codec-Bit-Rate", "first");
443 h.insert("channel-read-codec-bit-rate", "second");
444 assert_eq!(h.len(), 1);
445 assert_eq!(h.header_str("Channel-Read-Codec-Bit-Rate"), Some("second"));
446 }
447
448 #[test]
449 fn remove_accepts_a_non_canonical_spelling() {
450 let mut h = EslHeaders::new();
451 h.insert("Unique-ID", "abc-123");
452 assert_eq!(h.remove("unique-id"), Some("abc-123".to_string()));
453 assert!(h.is_empty());
454 assert_eq!(h.header_str("Unique-ID"), None);
455 assert_eq!(h.remove("unique-id"), None);
456 }
457
458 #[test]
459 fn remove_by_canonical_key_drops_the_alias_too() {
460 let mut h = EslHeaders::new();
461 h.insert("unique-id", "abc-123");
462 assert_eq!(h.remove("Unique-ID"), Some("abc-123".to_string()));
463 assert_eq!(h.header_str("unique-id"), None);
464 }
465
466 #[cfg(feature = "serde")]
467 #[test]
468 fn deserialization_normalizes_like_the_wire_does() {
469 let json = r#"{"unique-id":"abc-123","variable_sip_call_id":"call-1"}"#;
470 let h: EslHeaders = serde_json::from_str(json).expect("map payload");
471 assert_eq!(h.unique_id(), Some("abc-123"));
472 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
473 assert_eq!(
474 h.as_map()
475 .keys()
476 .collect::<Vec<_>>(),
477 vec!["Unique-ID", "variable_sip_call_id"]
478 );
479
480 let round_tripped: EslHeaders =
481 serde_json::from_str(&serde_json::to_string(&h).expect("serialize"))
482 .expect("round trip");
483 assert_eq!(round_tripped, h);
484 }
485
486 #[test]
487 fn array_encoding_decodes_for_every_multi_value_header() {
488 let mut h = EslHeaders::new();
489 h.insert(
490 "P-Asserted-Identity",
491 "ARRAY::<sip:alice@example.test>|:<tel:+15551234567>",
492 );
493 h.insert(
494 "Error-Info",
495 "ARRAY::<sip:busy@example.test>|:<http://example.test/why.html>",
496 );
497 h.insert(
498 "Contact",
499 "ARRAY::<sip:a@192.0.2.1>;expires=60|:<sip:b@192.0.2.2>",
500 );
501 h.insert(
502 "Via",
503 "ARRAY::SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bK1|:SIP/2.0/TCP 192.0.2.2;branch=z9hG4bK2",
504 );
505 h.insert(
506 "Warning",
507 "ARRAY::370 192.0.2.1 \"Insufficient bandwidth\"|:399 192.0.2.2 \"Misc, warning\"",
508 );
509 h.insert("Accept", "ARRAY::application/sdp|:text/plain;q=0.5");
510
511 assert_eq!(
512 h.via()
513 .expect("ARRAY value must decode")
514 .expect("header is present")
515 .len(),
516 2
517 );
518 assert_eq!(
519 h.warning()
520 .expect("ARRAY value must decode")
521 .expect("header is present")
522 .len(),
523 2
524 );
525 assert_eq!(
526 h.accept()
527 .expect("ARRAY value must decode")
528 .expect("header is present")
529 .len(),
530 2
531 );
532 assert_eq!(
533 h.p_asserted_identity()
534 .expect("ARRAY value must decode")
535 .len(),
536 2
537 );
538 assert_eq!(
539 h.error_info()
540 .expect("ARRAY value must decode")
541 .expect("header is present")
542 .entries()
543 .len(),
544 2
545 );
546 assert_eq!(
547 h.contact()
548 .expect("ARRAY value must decode")
549 .len(),
550 2
551 );
552 }
553
554 #[test]
555 fn a_single_valued_header_keeps_its_commas() {
556 let mut h = EslHeaders::new();
557 h.insert("Subject", "one, two");
558 assert_eq!(h.sip_header_all_str("Subject"), vec!["one, two"]);
559 }
560
561 #[test]
564 fn an_auth_header_keeps_its_parameter_commas() {
565 let digest = "Digest username=\"alice\", realm=\"example.com\", nonce=\"abc\"";
566 let mut h = EslHeaders::new();
567 h.insert("Authorization", digest);
568 h.insert("Proxy-Authorization", digest);
569 h.insert("WWW-Authenticate", digest);
570 h.insert("Proxy-Authenticate", digest);
571
572 for values in [
573 h.authorization(),
574 h.proxy_authorization(),
575 h.www_authenticate(),
576 h.proxy_authenticate(),
577 ] {
578 let values = values.expect("one Digest value parses");
579 assert_eq!(values.len(), 1);
580 assert_eq!(values[0].username(), Some("alice"));
581 assert_eq!(values[0].realm(), Some("example.com"));
582 }
583 }
584
585 #[test]
586 fn an_array_of_auth_headers_is_one_row_per_occurrence() {
587 let mut h = EslHeaders::new();
588 h.insert(
589 "Authorization",
590 "ARRAY::Digest username=\"a\", realm=\"r1\"|:Digest username=\"b\", realm=\"r2\"",
591 );
592 let values = h
593 .authorization()
594 .expect("both Digest values parse");
595 assert_eq!(values.len(), 2);
596 assert_eq!(values[1].realm(), Some("r2"));
597 }
598
599 #[test]
600 fn a_list_header_still_splits_on_commas() {
601 let mut h = EslHeaders::new();
602 h.insert("Call-Info", "<sip:a@example.com>, <sip:b@example.com>");
603 assert_eq!(
604 h.call_info()
605 .expect("both entries parse")
606 .expect("header is present")
607 .len(),
608 2
609 );
610 }
611
612 #[test]
613 fn header_str_passthrough() {
614 let mut h = EslHeaders::new();
615 h.insert("Unique-ID", "abc-123");
616 assert_eq!(h.header_str("Unique-ID"), Some("abc-123"));
617 }
618
619 #[test]
620 fn variable_str_prepends_variable_prefix() {
621 let mut h = EslHeaders::new();
622 h.insert("variable_sip_call_id", "call-1");
623 assert_eq!(h.variable_str("sip_call_id"), Some("call-1"));
624 assert_eq!(h.variable_str("missing"), None);
625 }
626
627 #[test]
630 fn both_entry_points_decode_every_transport_form() {
631 let forms = [
632 (
633 "rfc",
634 "<sip:alice@example.test>;purpose=icon",
635 vec![Some("icon")],
636 ),
637 (
638 "array",
639 "ARRAY::<sip:a@example.test>;purpose=icon|:<sip:b@example.test>;purpose=info",
640 vec![Some("icon"), Some("info")],
641 ),
642 (
643 "bracketed",
644 "[<sip:alice@example.test>;purpose=icon]",
645 vec![Some("icon")],
646 ),
647 (
648 "bracketed array",
649 "[ARRAY::<sip:a@example.test>;purpose=icon|:<sip:b@example.test>]",
650 vec![Some("icon"), None],
651 ),
652 ];
653
654 for (label, value, purposes) in forms {
655 let mut h = EslHeaders::new();
656 h.insert("Call-Info", value);
657 let from_header = h
658 .call_info()
659 .unwrap_or_else(|e| panic!("{label}: {e}"))
660 .unwrap_or_else(|| panic!("{label}: header is present"));
661 let from_value = EslHeaders::parse_uri_info(value)
662 .unwrap_or_else(|e| panic!("{label} as a raw value: {e}"));
663 assert_eq!(from_header, from_value, "{label}");
664 assert_eq!(
665 from_header
666 .entries()
667 .iter()
668 .map(|e| e.purpose())
669 .collect::<Vec<_>>(),
670 purposes,
671 "{label}"
672 );
673 }
674 }
675
676 #[test]
677 fn call_info_absent_is_ok_none() {
678 let h = EslHeaders::new();
679 assert!(h
680 .call_info()
681 .unwrap()
682 .is_none());
683 }
684
685 #[test]
686 fn history_info_array_encoding() {
687 let mut h = EslHeaders::new();
688 h.insert(
689 "History-Info",
690 "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1",
691 );
692 let hi = h
693 .history_info()
694 .unwrap()
695 .expect("present");
696 assert_eq!(
697 hi.entries()
698 .len(),
699 2
700 );
701 }
702
703 #[test]
704 fn header_lookup_typed_accessors() {
705 let mut h = EslHeaders::new();
706 h.insert(EventHeader::UniqueId.as_str(), "uuid-1");
707 h.insert(EventHeader::ChannelName.as_str(), "sofia/a/b");
708 assert_eq!(h.unique_id(), Some("uuid-1"));
709 assert_eq!(h.channel_name(), Some("sofia/a/b"));
710 }
711
712 #[test]
713 fn parse_uri_info_empty_value() {
714 assert!(EslHeaders::parse_uri_info("").is_err());
715 }
716
717 #[test]
718 fn parse_uri_info_malformed_no_panic() {
719 let info = EslHeaders::parse_uri_info("sip:bare@example.test").expect("lenient parse");
721 assert_eq!(
722 info.entries()
723 .len(),
724 1
725 );
726 }
727
728 #[test]
729 fn parse_uri_info_structural_failure_is_malformed() {
730 let value = format!(
731 "ARRAY::{}",
732 vec!["<sip:a@example.com>"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
733 );
734 let err = EslHeaders::parse_uri_info(&value).expect_err("over-limit array must fail");
735 assert!(matches!(err, UriInfoError::Malformed(_)), "got {err:?}");
736 }
737
738 #[test]
739 fn parse_history_info_structural_failure_is_malformed() {
740 let value = format!(
741 "ARRAY::{}",
742 vec!["<sip:a@example.com>;index=1"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
743 );
744 let err = EslHeaders::parse_history_info(&value).expect_err("over-limit array must fail");
745 assert!(matches!(err, HistoryInfoError::Malformed(_)), "got {err:?}");
746 }
747
748 #[test]
749 fn collects_from_key_value_pairs() {
750 let h: EslHeaders = [
751 ("Unique-ID", "uuid-1"),
752 ("Caller-Destination-Number", "911"),
753 ]
754 .into_iter()
755 .collect();
756 assert_eq!(h.header_str("Unique-ID"), Some("uuid-1"));
757 }
758
759 #[test]
760 fn extend_adds_pairs() {
761 let mut h = EslHeaders::new();
762 h.insert("Unique-ID", "uuid-1");
763 h.extend([("Channel-Name", "sofia/a/b")]);
764 assert_eq!(h.len(), 2);
765 }
766
767 #[test]
768 fn split_entries_array_form() {
769 let entries =
770 EslHeaders::split_entries("ARRAY::<sip:a@example.test>|:<sip:b@example.test>")
771 .expect("ARRAY form");
772 assert_eq!(entries, ["<sip:a@example.test>", "<sip:b@example.test>"]);
773 }
774
775 #[test]
776 fn split_entries_rfc_comma_form() {
777 let entries = EslHeaders::split_entries(
778 "<sip:a@example.test>;text=\"one, two\",<sip:b@example.test>",
779 )
780 .expect("RFC form");
781 assert_eq!(entries.len(), 2);
782 assert_eq!(entries[0], "<sip:a@example.test>;text=\"one, two\"");
783 }
784
785 #[test]
786 fn split_entries_bracket_wrapped() {
787 let entries =
788 EslHeaders::split_entries("[<sip:a@example.test>;purpose=icon]").expect("bracket form");
789 assert_eq!(entries, ["<sip:a@example.test>;purpose=icon"]);
790 }
791
792 #[test]
793 fn split_entries_blank_value_yields_no_entries() {
794 let entries = EslHeaders::split_entries(" ").expect("blank value");
795 assert!(entries.is_empty());
796 }
797
798 #[test]
799 fn parse_history_info_blank_value_is_empty_error() {
800 let err = EslHeaders::parse_history_info(" ").expect_err("blank value must fail");
801 assert!(matches!(err, HistoryInfoError::Empty), "got {err:?}");
802 }
803
804 #[test]
805 fn split_entries_too_many_items_is_err() {
806 let value = format!(
807 "ARRAY::{}",
808 vec!["<sip:a@example.test>"; crate::variables::MAX_ARRAY_ITEMS + 1].join("|:")
809 );
810 let err = EslHeaders::split_entries(&value).expect_err("over-limit array must fail");
811 assert!(
812 matches!(err, EslArrayError::TooManyItems { .. }),
813 "got {err:?}"
814 );
815 }
816
817 #[test]
818 fn parse_history_info_array_form() {
819 let value = "ARRAY::<sip:a@example.com>;index=1|:<sip:b@example.com>;index=1.1";
820 let info = EslHeaders::parse_history_info(value).expect("parse ARRAY form");
821 assert_eq!(
822 info.entries()
823 .len(),
824 2
825 );
826 }
827}