1use std::borrow::Cow;
4use std::fmt;
5use std::str::{FromStr, Utf8Error};
6
7use percent_encoding::percent_decode_str;
8
9use crate::replaces::{SipReplaces, SipReplacesError};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
49#[non_exhaustive]
50pub struct SipHeaderAddr {
51 display_name: Option<String>,
52 uri: sip_uri::Uri,
53 params: Vec<(String, Option<String>)>,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct ParseSipHeaderAddrError(pub String);
59
60impl fmt::Display for ParseSipHeaderAddrError {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "invalid SIP header address: {}", self.0)
63 }
64}
65
66impl std::error::Error for ParseSipHeaderAddrError {}
67
68impl From<sip_uri::ParseUriError> for ParseSipHeaderAddrError {
69 fn from(e: sip_uri::ParseUriError) -> Self {
70 Self(e.to_string())
71 }
72}
73
74impl From<sip_uri::ParseSipUriError> for ParseSipHeaderAddrError {
75 fn from(e: sip_uri::ParseSipUriError) -> Self {
76 Self(e.to_string())
77 }
78}
79
80impl SipHeaderAddr {
81 pub fn new(uri: sip_uri::Uri) -> Self {
83 SipHeaderAddr {
84 display_name: None,
85 uri,
86 params: Vec::new(),
87 }
88 }
89
90 pub fn with_display_name(mut self, name: impl Into<String>) -> Self {
92 self.display_name = Some(name.into());
93 self
94 }
95
96 pub fn with_param(mut self, key: impl Into<String>, value: Option<impl Into<String>>) -> Self {
98 self.params
99 .push((
100 key.into()
101 .to_ascii_lowercase(),
102 value.map(Into::into),
103 ));
104 self
105 }
106
107 pub fn display_name(&self) -> Option<&str> {
109 self.display_name
110 .as_deref()
111 }
112
113 pub fn uri(&self) -> &sip_uri::Uri {
115 &self.uri
116 }
117
118 pub fn sip_uri(&self) -> Option<&sip_uri::SipUri> {
120 self.uri
121 .as_sip()
122 }
123
124 pub fn tel_uri(&self) -> Option<&sip_uri::TelUri> {
126 self.uri
127 .as_tel()
128 }
129
130 pub fn urn_uri(&self) -> Option<&sip_uri::UrnUri> {
132 self.uri
133 .as_urn()
134 }
135
136 pub fn params(&self) -> impl Iterator<Item = (&str, Option<&str>)> {
146 self.params
147 .iter()
148 .map(|(k, v)| (k.as_str(), v.as_deref()))
149 }
150
151 pub fn param(&self, name: &str) -> Option<Result<Option<Cow<'_, str>>, Utf8Error>> {
164 let needle = name.to_ascii_lowercase();
165 self.params
166 .iter()
167 .find(|(k, _)| *k == needle)
168 .map(|(_, v)| match v {
169 Some(raw) => percent_decode_str(raw)
170 .decode_utf8()
171 .map(Some),
172 None => Ok(None),
173 })
174 }
175
176 pub fn param_raw(&self, name: &str) -> Option<Option<&str>> {
181 let needle = name.to_ascii_lowercase();
182 self.params
183 .iter()
184 .find(|(k, _)| *k == needle)
185 .map(|(_, v)| v.as_deref())
186 }
187
188 pub fn replaces(&self) -> Option<Result<SipReplaces, SipReplacesError>> {
194 let value = self
195 .sip_uri()?
196 .header("Replaces")?;
197 Some(SipReplaces::parse_uri_header(value))
198 }
199
200 pub fn parse_list(raw: &str) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
206 if raw
207 .trim()
208 .is_empty()
209 {
210 return Ok(Vec::new());
211 }
212 crate::split_comma_entries(raw)
213 .into_iter()
214 .map(|entry| {
215 entry
216 .trim()
217 .parse()
218 })
219 .collect()
220 }
221
222 pub fn tag(&self) -> Option<&str> {
227 self.param_raw("tag")
228 .flatten()
229 }
230}
231
232fn parse_quoted_string(s: &str) -> Result<(String, &str), String> {
234 if !s.starts_with('"') {
235 return Err("expected opening quote".into());
236 }
237
238 let mut result = String::new();
239 let mut chars = s[1..].char_indices();
240
241 while let Some((i, c)) = chars.next() {
242 match c {
243 '"' => {
244 return Ok((result, &s[i + 2..]));
245 }
246 '\\' => {
247 let (_, escaped) = chars
248 .next()
249 .ok_or("unterminated escape in quoted string")?;
250 result.push(escaped);
251 }
252 _ => {
253 result.push(c);
254 }
255 }
256 }
257
258 Err("unterminated quoted string".into())
259}
260
261fn extract_angle_uri(s: &str) -> Option<(&str, &str)> {
263 let s = s.strip_prefix('<')?;
264 let end = s.find('>')?;
265 Some((&s[..end], &s[end + 1..]))
266}
267
268fn parse_header_params(s: &str) -> Vec<(String, Option<String>)> {
271 let mut params = Vec::new();
272 for segment in s.split(';') {
273 if segment.is_empty() {
274 continue;
275 }
276 if let Some((key, value)) = segment.split_once('=') {
277 params.push((key.to_ascii_lowercase(), Some(value.to_string())));
278 } else {
279 params.push((segment.to_ascii_lowercase(), None));
280 }
281 }
282 params
283}
284
285fn needs_quoting(name: &str) -> bool {
287 name.bytes()
288 .any(|b| {
289 matches!(
290 b,
291 b'"' | b'\\' | b'<' | b'>' | b',' | b';' | b':' | b'@' | b' ' | b'\t'
292 )
293 })
294}
295
296fn escape_display_name(name: &str) -> String {
298 let mut out = String::with_capacity(name.len());
299 for c in name.chars() {
300 if matches!(c, '"' | '\\') {
301 out.push('\\');
302 }
303 out.push(c);
304 }
305 out
306}
307
308impl FromStr for SipHeaderAddr {
309 type Err = ParseSipHeaderAddrError;
310
311 fn from_str(input: &str) -> Result<Self, Self::Err> {
312 let err = |msg: &str| ParseSipHeaderAddrError(msg.to_string());
313 let s = input.trim();
314
315 if s.is_empty() {
316 return Err(err("empty input"));
317 }
318
319 if s.starts_with('"') {
321 let (display_name, rest) = parse_quoted_string(s).map_err(|e| err(&e))?;
322 let rest = rest.trim_start();
323 let (uri_str, trailing) = extract_angle_uri(rest)
324 .ok_or_else(|| err("expected '<URI>' after quoted display name"))?;
325 let uri: sip_uri::Uri = uri_str.parse()?;
326 let display_name = if display_name.is_empty() {
327 None
328 } else {
329 Some(display_name)
330 };
331 let params = parse_header_params(trailing);
332 return Ok(SipHeaderAddr {
333 display_name,
334 uri,
335 params,
336 });
337 }
338
339 if s.starts_with('<') {
341 let (uri_str, trailing) = extract_angle_uri(s).ok_or_else(|| err("unclosed '<'"))?;
342 let uri: sip_uri::Uri = uri_str.parse()?;
343 let params = parse_header_params(trailing);
344 return Ok(SipHeaderAddr {
345 display_name: None,
346 uri,
347 params,
348 });
349 }
350
351 if let Some(angle_start) = s.find('<') {
353 let display_name = s[..angle_start].trim();
354 let display_name = if display_name.is_empty() {
355 None
356 } else {
357 Some(display_name.to_string())
358 };
359 let (uri_str, trailing) =
360 extract_angle_uri(&s[angle_start..]).ok_or_else(|| err("unclosed '<'"))?;
361 let uri: sip_uri::Uri = uri_str.parse()?;
362 let params = parse_header_params(trailing);
363 return Ok(SipHeaderAddr {
364 display_name,
365 uri,
366 params,
367 });
368 }
369
370 let uri: sip_uri::Uri = s.parse()?;
374 Ok(SipHeaderAddr {
375 display_name: None,
376 uri,
377 params: Vec::new(),
378 })
379 }
380}
381
382impl fmt::Display for SipHeaderAddr {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 match self
385 .display_name
386 .as_deref()
387 {
388 Some(name) if !name.is_empty() => {
389 if needs_quoting(name) {
390 write!(f, "\"{}\" ", escape_display_name(name))?;
391 } else {
392 write!(f, "{name} ")?;
393 }
394 write!(f, "<{}>", self.uri)?;
395 }
396 _ => {
397 write!(f, "<{}>", self.uri)?;
398 }
399 }
400 for (key, value) in &self.params {
401 match value {
402 Some(v) => write!(f, ";{key}={v}")?,
403 None => write!(f, ";{key}")?,
404 }
405 }
406 Ok(())
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use std::borrow::Cow;
413
414 use super::*;
415
416 #[test]
417 fn quoted_display_name_with_tag() {
418 let addr: SipHeaderAddr = r#""Alice" <sip:alice@example.com>;tag=abc123"#
419 .parse()
420 .unwrap();
421 assert_eq!(addr.display_name(), Some("Alice"));
422 assert!(addr
423 .sip_uri()
424 .is_some());
425 assert_eq!(addr.tag(), Some("abc123"));
426 }
427
428 #[test]
429 fn angle_bracket_no_name_multiple_params() {
430 let addr: SipHeaderAddr = "<sip:user@host>;tag=xyz;expires=3600"
431 .parse()
432 .unwrap();
433 assert_eq!(addr.display_name(), None);
434 assert_eq!(addr.tag(), Some("xyz"));
435 assert_eq!(
436 addr.param("expires")
437 .unwrap()
438 .unwrap(),
439 Some(Cow::from("3600")),
440 );
441 }
442
443 #[test]
444 fn bare_addr_spec_no_params() {
445 let addr: SipHeaderAddr = "sip:user@host"
446 .parse()
447 .unwrap();
448 assert_eq!(addr.display_name(), None);
449 assert!(addr
450 .sip_uri()
451 .is_some());
452 assert_eq!(
453 addr.params()
454 .count(),
455 0
456 );
457 }
458
459 #[test]
460 fn unquoted_display_name_with_params() {
461 let addr: SipHeaderAddr = "Alice <sip:alice@example.com>;tag=abc"
462 .parse()
463 .unwrap();
464 assert_eq!(addr.display_name(), Some("Alice"));
465 assert_eq!(addr.tag(), Some("abc"));
466 }
467
468 #[test]
469 fn ng911_refer_to_serviceurn() {
470 let input = "<sip:user@esrp.example.com?Call-Info=x>;serviceurn=urn%3Aservice%3Apolice";
471 let addr: SipHeaderAddr = input
472 .parse()
473 .unwrap();
474 assert_eq!(addr.display_name(), None);
475 assert_eq!(
476 addr.param("serviceurn")
477 .unwrap()
478 .unwrap(),
479 Some(Cow::from("urn:service:police")),
480 );
481 assert_eq!(
482 addr.param_raw("serviceurn"),
483 Some(Some("urn%3Aservice%3Apolice")),
484 );
485 let sip = addr
486 .sip_uri()
487 .unwrap();
488 assert_eq!(
489 sip.host()
490 .to_string(),
491 "esrp.example.com"
492 );
493 }
494
495 #[test]
496 fn p_asserted_identity_uri_params_no_header_params() {
497 let input = r#""EXAMPLE CO" <sip:+15551234567;cpc=emergency@198.51.100.1;user=phone>"#;
498 let addr: SipHeaderAddr = input
499 .parse()
500 .unwrap();
501 assert_eq!(addr.display_name(), Some("EXAMPLE CO"));
502 assert_eq!(
503 addr.params()
504 .count(),
505 0
506 );
507 let sip = addr
508 .sip_uri()
509 .unwrap();
510 assert_eq!(sip.user(), Some("+15551234567"));
511 assert_eq!(sip.param("user"), Some(&Some("phone".to_string())));
512 }
513
514 #[test]
515 fn tel_uri_with_header_params() {
516 let addr: SipHeaderAddr = "<tel:+15551234567>;expires=3600"
517 .parse()
518 .unwrap();
519 assert!(addr
520 .tel_uri()
521 .is_some());
522 assert_eq!(
523 addr.param("expires")
524 .unwrap()
525 .unwrap(),
526 Some(Cow::from("3600")),
527 );
528 }
529
530 #[test]
531 fn flag_param_no_value() {
532 let addr: SipHeaderAddr = "<sip:user@host>;lr;tag=abc"
533 .parse()
534 .unwrap();
535 assert_eq!(
536 addr.param("lr")
537 .unwrap()
538 .unwrap(),
539 None
540 );
541 assert_eq!(addr.tag(), Some("abc"));
542 }
543
544 #[test]
545 fn urn_uri_no_params() {
546 let addr: SipHeaderAddr = "<urn:service:sos>"
547 .parse()
548 .unwrap();
549 assert!(addr
550 .urn_uri()
551 .is_some());
552 assert_eq!(
553 addr.params()
554 .count(),
555 0
556 );
557 }
558
559 #[test]
560 fn empty_input_fails() {
561 assert!(""
562 .parse::<SipHeaderAddr>()
563 .is_err());
564 }
565
566 #[test]
567 fn display_roundtrip_quoted_name_with_params() {
568 let input = r#""Alice" <sip:alice@example.com>;tag=abc123"#;
570 let addr: SipHeaderAddr = input
571 .parse()
572 .unwrap();
573 assert_eq!(addr.to_string(), "Alice <sip:alice@example.com>;tag=abc123");
574 }
575
576 #[test]
577 fn display_roundtrip_name_requiring_quotes() {
578 let input = r#""Alice Smith" <sip:alice@example.com>;tag=abc123"#;
579 let addr: SipHeaderAddr = input
580 .parse()
581 .unwrap();
582 assert_eq!(addr.to_string(), input);
583 }
584
585 #[test]
586 fn display_roundtrip_no_name_with_params() {
587 let input = "<sip:user@host>;tag=xyz;expires=3600";
588 let addr: SipHeaderAddr = input
589 .parse()
590 .unwrap();
591 assert_eq!(addr.to_string(), input);
592 }
593
594 #[test]
595 fn display_roundtrip_bare_uri() {
596 let input = "sip:user@host";
597 let addr: SipHeaderAddr = input
598 .parse()
599 .unwrap();
600 assert_eq!(addr.to_string(), "<sip:user@host>");
602 }
603
604 #[test]
605 fn display_roundtrip_flag_param() {
606 let input = "<sip:user@host>;lr;tag=abc";
607 let addr: SipHeaderAddr = input
608 .parse()
609 .unwrap();
610 assert_eq!(addr.to_string(), input);
611 }
612
613 #[test]
614 fn case_insensitive_param_lookup() {
615 let addr: SipHeaderAddr = "<sip:user@host>;Tag=ABC;Expires=3600"
616 .parse()
617 .unwrap();
618 assert_eq!(
619 addr.param("tag")
620 .unwrap()
621 .unwrap(),
622 Some(Cow::from("ABC")),
623 );
624 assert_eq!(
625 addr.param("TAG")
626 .unwrap()
627 .unwrap(),
628 Some(Cow::from("ABC")),
629 );
630 assert_eq!(
631 addr.param("expires")
632 .unwrap()
633 .unwrap(),
634 Some(Cow::from("3600")),
635 );
636 }
637
638 #[test]
639 fn tag_convenience_accessor() {
640 let with_tag: SipHeaderAddr = "<sip:user@host>;tag=xyz"
641 .parse()
642 .unwrap();
643 assert_eq!(with_tag.tag(), Some("xyz"));
644
645 let without_tag: SipHeaderAddr = "<sip:user@host>"
646 .parse()
647 .unwrap();
648 assert_eq!(without_tag.tag(), None);
649 }
650
651 #[test]
652 fn builder_new() {
653 let uri: sip_uri::Uri = "sip:alice@example.com"
654 .parse()
655 .unwrap();
656 let addr = SipHeaderAddr::new(uri);
657 assert_eq!(addr.display_name(), None);
658 assert_eq!(
659 addr.params()
660 .count(),
661 0
662 );
663 assert_eq!(addr.to_string(), "<sip:alice@example.com>");
664 }
665
666 #[test]
667 fn builder_with_display_name_and_params() {
668 let uri: sip_uri::Uri = "sip:alice@example.com"
669 .parse()
670 .unwrap();
671 let addr = SipHeaderAddr::new(uri)
672 .with_display_name("Alice")
673 .with_param("tag", Some("abc123"));
674 assert_eq!(addr.display_name(), Some("Alice"));
675 assert_eq!(addr.tag(), Some("abc123"));
676 assert_eq!(addr.to_string(), "Alice <sip:alice@example.com>;tag=abc123");
677 }
678
679 #[test]
680 fn builder_flag_param() {
681 let uri: sip_uri::Uri = "sip:proxy@example.com"
682 .parse()
683 .unwrap();
684 let addr = SipHeaderAddr::new(uri).with_param("lr", None::<String>);
685 assert_eq!(
686 addr.param("lr")
687 .unwrap()
688 .unwrap(),
689 None
690 );
691 assert_eq!(addr.to_string(), "<sip:proxy@example.com>;lr");
692 }
693
694 #[test]
695 fn escaped_quotes_in_display_name() {
696 let input = r#""Say \"Hello\"" <sip:u@h>;tag=t"#;
697 let addr: SipHeaderAddr = input
698 .parse()
699 .unwrap();
700 assert_eq!(addr.display_name(), Some(r#"Say "Hello""#));
701 assert_eq!(addr.tag(), Some("t"));
702 }
703
704 #[test]
705 fn display_roundtrip_escaped_quotes() {
706 let input = r#""Say \"Hello\"" <sip:u@h>;tag=t"#;
707 let addr: SipHeaderAddr = input
708 .parse()
709 .unwrap();
710 assert_eq!(addr.to_string(), input);
711 }
712
713 #[test]
714 fn trailing_semicolon_ignored() {
715 let addr: SipHeaderAddr = "<sip:user@host>;tag=abc;"
716 .parse()
717 .unwrap();
718 assert_eq!(
719 addr.params()
720 .count(),
721 1
722 );
723 assert_eq!(addr.tag(), Some("abc"));
724 }
725
726 #[test]
727 fn display_roundtrip_percent_encoded_params() {
728 let input = "<sip:user@esrp.example.com>;serviceurn=urn%3Aservice%3Apolice";
729 let addr: SipHeaderAddr = input
730 .parse()
731 .unwrap();
732 assert_eq!(addr.to_string(), input);
733 }
734
735 #[test]
736 fn param_invalid_utf8_returns_err() {
737 let addr: SipHeaderAddr = "<sip:user@host>;data=%C0%80"
739 .parse()
740 .unwrap();
741 assert!(addr
742 .param("data")
743 .unwrap()
744 .is_err());
745 assert_eq!(addr.param_raw("data"), Some(Some("%C0%80")));
746 }
747
748 #[test]
749 fn param_iso_8859_fallback_to_raw() {
750 let addr: SipHeaderAddr = "<sip:user@host>;name=%E9"
752 .parse()
753 .unwrap();
754 assert!(addr
755 .param("name")
756 .unwrap()
757 .is_err());
758 assert_eq!(addr.param_raw("name"), Some(Some("%E9")));
759 }
760
761 #[test]
762 fn parse_list_multiple_entries() {
763 let input = r#""Alice" <sip:alice@example.com>;tag=a, <sip:bob@example.com>, sip:carol@example.com"#;
764 let addrs = SipHeaderAddr::parse_list(input).unwrap();
765 assert_eq!(addrs.len(), 3);
766 assert_eq!(addrs[0].display_name(), Some("Alice"));
767 assert_eq!(addrs[0].tag(), Some("a"));
768 assert_eq!(addrs[1].display_name(), None);
769 assert_eq!(
770 addrs[1]
771 .sip_uri()
772 .unwrap()
773 .user(),
774 Some("bob"),
775 );
776 assert_eq!(
777 addrs[2]
778 .sip_uri()
779 .unwrap()
780 .user(),
781 Some("carol"),
782 );
783 }
784
785 #[test]
786 fn parse_list_single_entry() {
787 let addrs = SipHeaderAddr::parse_list("<sip:alice@example.com>").unwrap();
788 assert_eq!(addrs.len(), 1);
789 }
790
791 #[test]
792 fn parse_list_empty_returns_empty() {
793 let addrs = SipHeaderAddr::parse_list("").unwrap();
794 assert!(addrs.is_empty());
795 }
796
797 #[test]
798 fn parse_list_propagates_parse_error() {
799 assert!(SipHeaderAddr::parse_list("not-a-uri, <sip:ok@example.com>").is_err());
800 }
801
802 #[test]
803 fn replaces_uri_header() {
804 let addr: SipHeaderAddr =
805 "<sip:bob@203.0.113.9?Replaces=abc123%40203.0.113.5%3Bto-tag%3Dt1%3Bfrom-tag%3Df1>"
806 .parse()
807 .unwrap();
808 let r = addr
809 .replaces()
810 .unwrap()
811 .unwrap();
812 assert_eq!(r.call_id(), "abc123@203.0.113.5");
813 assert_eq!(r.host(), Some("203.0.113.5"));
814 assert_eq!(r.to_tag(), "t1");
815 assert_eq!(r.from_tag(), "f1");
816 }
817
818 #[test]
819 fn replaces_uri_header_absent() {
820 let addr: SipHeaderAddr = "<sip:bob@203.0.113.9>"
821 .parse()
822 .unwrap();
823 assert!(addr
824 .replaces()
825 .is_none());
826 }
827
828 #[test]
829 fn host_only_ipv4_with_feature_tag_param() {
830 let input =
831 "<sip:198.51.100.7:5060;transport=udp>;+urn%3Aemergency%3Amedia-feature.psap-call-control";
832 let addr: SipHeaderAddr = input
833 .parse()
834 .unwrap();
835 let sip = addr
836 .sip_uri()
837 .unwrap();
838 assert_eq!(sip.user(), None);
839 assert_eq!(
840 sip.host()
841 .to_string(),
842 "198.51.100.7"
843 );
844 assert_eq!(sip.port(), Some(5060));
845 assert_eq!(sip.param("transport"), Some(&Some("udp".to_string())));
846 assert_eq!(
847 addr.param_raw("+urn%3Aemergency%3Amedia-feature.psap-call-control"),
848 Some(None),
849 );
850 }
851
852 #[test]
853 fn host_only_ipv6_with_feature_tag_param() {
854 let input =
855 "<sip:[2001:db8::7]:5060;transport=udp>;+urn%3Aemergency%3Amedia-feature.psap-call-control";
856 let addr: SipHeaderAddr = input
857 .parse()
858 .unwrap();
859 let sip = addr
860 .sip_uri()
861 .unwrap();
862 assert_eq!(sip.user(), None);
863 assert_eq!(
864 sip.host()
865 .to_string(),
866 "[2001:db8::7]"
867 );
868 assert_eq!(sip.port(), Some(5060));
869 assert_eq!(
870 addr.param_raw("+urn%3Aemergency%3Amedia-feature.psap-call-control"),
871 Some(None),
872 );
873 }
874
875 #[test]
876 fn parse_list_host_only_ipv4_with_feature_tag_param() {
877 let input =
878 "<sip:198.51.100.7:5060;transport=udp>;+urn%3Aemergency%3Amedia-feature.psap-call-control";
879 let addrs = SipHeaderAddr::parse_list(input).unwrap();
880 assert_eq!(addrs.len(), 1);
881 assert_eq!(
882 addrs[0]
883 .sip_uri()
884 .unwrap()
885 .host()
886 .to_string(),
887 "198.51.100.7"
888 );
889 }
890
891 #[test]
892 fn params_iterator() {
893 let addr: SipHeaderAddr = "<sip:user@host>;tag=abc;lr;expires=60"
894 .parse()
895 .unwrap();
896 let params: Vec<_> = addr
897 .params()
898 .collect();
899 assert_eq!(params.len(), 3);
900 assert_eq!(params[0], ("tag", Some("abc")));
901 assert_eq!(params[1], ("lr", None));
902 assert_eq!(params[2], ("expires", Some("60")));
903 }
904}