freeswitch_types/variables/
sip_passthrough.rs1use sip_header::SipHeader;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct InvalidHeaderName(pub String);
28
29impl std::fmt::Display for InvalidHeaderName {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 if self
32 .0
33 .is_empty()
34 {
35 return f.write_str("empty SIP header name");
36 }
37 write!(
38 f,
39 "invalid SIP header name ({} bytes): contains CR, LF or NUL",
40 self.0
41 .len()
42 )
43 }
44}
45
46impl std::error::Error for InvalidHeaderName {}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53#[non_exhaustive]
54pub enum SipHeaderPrefix {
55 Invite,
57 Request,
59 Response,
61 Provisional,
63 Bye,
65 NoBye,
67}
68
69impl SipHeaderPrefix {
70 pub const ALL: &'static [Self] = &[
72 Self::Invite,
73 Self::Request,
74 Self::Response,
75 Self::Provisional,
76 Self::Bye,
77 Self::NoBye,
78 ];
79
80 pub const fn as_str(&self) -> &'static str {
89 match self {
90 Self::Invite => "sip_i_",
91 Self::Request => "sip_h_",
92 Self::Response => "sip_rh_",
93 Self::Provisional => "sip_ph_",
94 Self::Bye => "sip_bye_h_",
95 Self::NoBye => "sip_nobye_h_",
96 }
97 }
98
99 #[cfg(feature = "esl")]
102 pub(crate) const fn emits_header(self) -> bool {
103 matches!(
104 self,
105 Self::Request | Self::Response | Self::Provisional | Self::Bye
106 )
107 }
108}
109
110#[cfg(feature = "esl")]
113pub(crate) fn names_outgoing_header(var: &str) -> bool {
114 SipHeaderPrefix::ALL
115 .iter()
116 .filter(|p| p.emits_header())
117 .any(|p| {
118 var.get(
119 ..p.as_str()
120 .len(),
121 )
122 .is_some_and(|head| head.eq_ignore_ascii_case(p.as_str()))
123 })
124}
125
126impl std::fmt::Display for SipHeaderPrefix {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 f.write_str(self.as_str())
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct ParseSipPassthroughError(pub String);
138
139impl std::fmt::Display for ParseSipPassthroughError {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 let len = self
142 .0
143 .len();
144 if PREFIX_PATTERNS
145 .iter()
146 .any(|p| {
147 self.0
148 .eq_ignore_ascii_case(p.as_str())
149 })
150 {
151 return write!(f, "SIP passthrough variable ({len} bytes): no header name");
152 }
153 write!(
154 f,
155 "not a SIP passthrough variable ({len} bytes): no recognised sip_*_ prefix"
156 )
157 }
158}
159
160impl std::error::Error for ParseSipPassthroughError {}
161
162#[derive(Debug, Clone, PartialEq, Eq, Hash)]
191pub struct SipPassthroughHeader {
192 prefix: SipHeaderPrefix,
193 canonical_name: String,
194 wire: String,
195}
196
197const PREFIX_PATTERNS: &[SipHeaderPrefix] = &[
199 SipHeaderPrefix::NoBye,
200 SipHeaderPrefix::Bye,
201 SipHeaderPrefix::Provisional,
202 SipHeaderPrefix::Response,
203 SipHeaderPrefix::Invite,
204 SipHeaderPrefix::Request,
205];
206
207fn canonical_from_suffix(prefix: SipHeaderPrefix, suffix: &str) -> String {
210 match prefix {
211 SipHeaderPrefix::Invite => {
212 let with_hyphens = suffix.replace('_', "-");
213 match with_hyphens.parse::<SipHeader>() {
214 Ok(h) => h
215 .as_str()
216 .to_string(),
217 Err(_) => with_hyphens,
218 }
219 }
220 _ => match suffix.parse::<SipHeader>() {
221 Ok(h) => h
222 .as_str()
223 .to_string(),
224 Err(_) => suffix.to_string(),
225 },
226 }
227}
228
229fn validate_header_name(name: &str) -> Result<(), InvalidHeaderName> {
230 if name.is_empty() || crate::wire_safety::contains_wire_terminator(name) {
231 return Err(InvalidHeaderName(name.to_string()));
232 }
233 Ok(())
234}
235
236fn build_wire(prefix: SipHeaderPrefix, canonical: &str) -> String {
237 match prefix {
238 SipHeaderPrefix::Invite => {
239 let pfx = SipHeaderPrefix::Invite.as_str();
240 let mut wire = String::with_capacity(pfx.len() + canonical.len());
241 wire.push_str(pfx);
242 for ch in canonical.chars() {
243 if ch == '-' {
244 wire.push('_');
245 } else {
246 wire.push(ch.to_ascii_lowercase());
247 }
248 }
249 wire
250 }
251 _ => {
252 let pfx = prefix.as_str();
253 let mut wire = String::with_capacity(pfx.len() + canonical.len());
254 wire.push_str(pfx);
255 wire.push_str(canonical);
256 wire
257 }
258 }
259}
260
261impl SipPassthroughHeader {
262 pub fn new(prefix: SipHeaderPrefix, header: SipHeader) -> Self {
264 let canonical = header
265 .as_str()
266 .to_string();
267 let wire = build_wire(prefix, &canonical);
268 Self {
269 prefix,
270 canonical_name: canonical,
271 wire,
272 }
273 }
274
275 pub fn new_raw(
279 prefix: SipHeaderPrefix,
280 name: impl Into<String>,
281 ) -> Result<Self, InvalidHeaderName> {
282 let canonical = name.into();
283 validate_header_name(&canonical)?;
284 let wire = build_wire(prefix, &canonical);
285 Ok(Self {
286 prefix,
287 canonical_name: canonical,
288 wire,
289 })
290 }
291
292 pub fn invite(header: SipHeader) -> Self {
294 Self::new(SipHeaderPrefix::Invite, header)
295 }
296
297 pub fn invite_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
299 Self::new_raw(SipHeaderPrefix::Invite, name)
300 }
301
302 pub fn request(header: SipHeader) -> Self {
304 Self::new(SipHeaderPrefix::Request, header)
305 }
306
307 pub fn request_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
309 Self::new_raw(SipHeaderPrefix::Request, name)
310 }
311
312 pub fn response(header: SipHeader) -> Self {
314 Self::new(SipHeaderPrefix::Response, header)
315 }
316
317 pub fn response_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
319 Self::new_raw(SipHeaderPrefix::Response, name)
320 }
321
322 pub fn provisional(header: SipHeader) -> Self {
324 Self::new(SipHeaderPrefix::Provisional, header)
325 }
326
327 pub fn provisional_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
329 Self::new_raw(SipHeaderPrefix::Provisional, name)
330 }
331
332 pub fn bye(header: SipHeader) -> Self {
334 Self::new(SipHeaderPrefix::Bye, header)
335 }
336
337 pub fn bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
339 Self::new_raw(SipHeaderPrefix::Bye, name)
340 }
341
342 pub fn no_bye(header: SipHeader) -> Self {
344 Self::new(SipHeaderPrefix::NoBye, header)
345 }
346
347 pub fn no_bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
349 Self::new_raw(SipHeaderPrefix::NoBye, name)
350 }
351
352 pub fn prefix(&self) -> SipHeaderPrefix {
354 self.prefix
355 }
356
357 pub fn canonical_name(&self) -> &str {
359 &self.canonical_name
360 }
361
362 pub fn as_str(&self) -> &str {
364 &self.wire
365 }
366
367 pub fn extract_from(&self, message: &str) -> Vec<String> {
372 sip_header::extract_header(message, &self.canonical_name)
373 }
374
375 pub fn is_array_header(&self) -> bool {
381 if self.prefix != SipHeaderPrefix::Invite {
382 return false;
383 }
384 self.canonical_name
385 .parse::<SipHeader>()
386 .map(|h| h.is_multi_valued())
387 .unwrap_or(false)
388 }
389}
390
391impl super::VariableName for SipPassthroughHeader {
392 fn as_str(&self) -> &str {
393 &self.wire
394 }
395}
396
397impl std::fmt::Display for SipPassthroughHeader {
398 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399 f.write_str(&self.wire)
400 }
401}
402
403impl AsRef<str> for SipPassthroughHeader {
404 fn as_ref(&self) -> &str {
405 &self.wire
406 }
407}
408
409impl From<SipPassthroughHeader> for String {
410 fn from(h: SipPassthroughHeader) -> Self {
411 h.wire
412 }
413}
414
415impl std::str::FromStr for SipPassthroughHeader {
416 type Err = ParseSipPassthroughError;
417
418 fn from_str(s: &str) -> Result<Self, Self::Err> {
419 for &prefix in PREFIX_PATTERNS {
420 let pfx = prefix.as_str();
421 if !s
422 .get(..pfx.len())
423 .is_some_and(|head| head.eq_ignore_ascii_case(pfx))
424 {
425 continue;
426 }
427 let suffix = &s[pfx.len()..];
428 if suffix.is_empty() {
429 return Err(ParseSipPassthroughError(s.to_string()));
430 }
431 let canonical = canonical_from_suffix(prefix, suffix);
432 let wire = match prefix {
433 SipHeaderPrefix::Invite => build_wire(prefix, &canonical),
434 _ => format!("{pfx}{suffix}"),
435 };
436 return Ok(Self {
437 prefix,
438 canonical_name: canonical,
439 wire,
440 });
441 }
442
443 Err(ParseSipPassthroughError(s.to_string()))
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
454 fn invite_typed_wire_format() {
455 assert_eq!(
456 SipPassthroughHeader::invite(SipHeader::CallInfo).as_str(),
457 "sip_i_call_info"
458 );
459 assert_eq!(
460 SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).as_str(),
461 "sip_i_p_asserted_identity"
462 );
463 assert_eq!(
464 SipPassthroughHeader::invite(SipHeader::Via).as_str(),
465 "sip_i_via"
466 );
467 assert_eq!(
468 SipPassthroughHeader::invite(SipHeader::CallId).as_str(),
469 "sip_i_call_id"
470 );
471 }
472
473 #[test]
474 fn request_typed_wire_format() {
475 assert_eq!(
476 SipPassthroughHeader::request(SipHeader::CallInfo).as_str(),
477 "sip_h_Call-Info"
478 );
479 assert_eq!(
480 SipPassthroughHeader::request(SipHeader::PAssertedIdentity).as_str(),
481 "sip_h_P-Asserted-Identity"
482 );
483 }
484
485 #[test]
486 fn response_typed_wire_format() {
487 assert_eq!(
488 SipPassthroughHeader::response(SipHeader::CallInfo).as_str(),
489 "sip_rh_Call-Info"
490 );
491 }
492
493 #[test]
494 fn provisional_typed_wire_format() {
495 assert_eq!(
496 SipPassthroughHeader::provisional(SipHeader::AlertInfo).as_str(),
497 "sip_ph_Alert-Info"
498 );
499 }
500
501 #[test]
502 fn bye_typed_wire_format() {
503 assert_eq!(
504 SipPassthroughHeader::bye(SipHeader::Reason).as_str(),
505 "sip_bye_h_Reason"
506 );
507 }
508
509 #[test]
510 fn no_bye_typed_wire_format() {
511 assert_eq!(
512 SipPassthroughHeader::no_bye(SipHeader::Reason).as_str(),
513 "sip_nobye_h_Reason"
514 );
515 }
516
517 #[test]
518 fn raw_custom_header() {
519 assert_eq!(
520 SipPassthroughHeader::request_raw("X-Tenant")
521 .unwrap()
522 .as_str(),
523 "sip_h_X-Tenant"
524 );
525 assert_eq!(
526 SipPassthroughHeader::invite_raw("X-Custom")
527 .unwrap()
528 .as_str(),
529 "sip_i_x_custom"
530 );
531 }
532
533 #[test]
534 fn raw_rejects_newlines() {
535 assert!(SipPassthroughHeader::request_raw("X-Bad\nHeader").is_err());
536 assert!(SipPassthroughHeader::request_raw("X-Bad\rHeader").is_err());
537 assert!(SipPassthroughHeader::request_raw("X-Bad\0Header").is_err());
538 assert!(SipPassthroughHeader::request_raw("").is_err());
539 }
540
541 #[test]
544 fn invalid_name_error_names_the_shape_not_the_value() {
545 let err = SipPassthroughHeader::request_raw("X-Tenant\nInjected").unwrap_err();
546 let msg = err.to_string();
547 assert!(!msg.contains("X-Tenant"), "error quoted its input: {msg}");
548 assert!(
549 msg.contains("17 bytes"),
550 "error does not give the byte length: {msg}"
551 );
552 assert_eq!(err.0, "X-Tenant\nInjected");
553 }
554
555 #[test]
556 fn empty_name_error_says_which_rule_it_broke() {
557 let msg = SipPassthroughHeader::request_raw("")
558 .unwrap_err()
559 .to_string();
560 assert!(msg.contains("empty"), "{msg}");
561 }
562
563 #[test]
564 fn parse_error_names_the_shape_not_the_value() {
565 let msg = "X-Tenant: secret"
566 .parse::<SipPassthroughHeader>()
567 .unwrap_err()
568 .to_string();
569 assert!(!msg.contains("secret"), "error quoted its input: {msg}");
570 assert!(
571 msg.contains("16 bytes"),
572 "error does not give the byte length: {msg}"
573 );
574 }
575
576 #[test]
577 fn parse_error_separates_a_missing_header_name_from_a_missing_prefix() {
578 let msg = "sip_h_"
579 .parse::<SipPassthroughHeader>()
580 .unwrap_err()
581 .to_string();
582 assert!(msg.contains("no header name"), "{msg}");
583 }
584
585 #[test]
588 fn display_matches_as_str() {
589 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
590 assert_eq!(h.to_string(), h.as_str());
591 }
592
593 #[test]
596 fn from_str_request() {
597 let parsed: SipPassthroughHeader = "sip_h_Call-Info"
598 .parse()
599 .unwrap();
600 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
601 assert_eq!(parsed.canonical_name(), "Call-Info");
602 assert_eq!(parsed.as_str(), "sip_h_Call-Info");
603 }
604
605 #[test]
606 fn from_str_invite() {
607 let parsed: SipPassthroughHeader = "sip_i_call_info"
608 .parse()
609 .unwrap();
610 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
611 assert_eq!(parsed.canonical_name(), "Call-Info");
612 assert_eq!(parsed.as_str(), "sip_i_call_info");
613 }
614
615 #[test]
616 fn from_str_invite_p_asserted_identity() {
617 let parsed: SipPassthroughHeader = "sip_i_p_asserted_identity"
618 .parse()
619 .unwrap();
620 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
621 assert_eq!(parsed.canonical_name(), "P-Asserted-Identity");
622 }
623
624 #[test]
625 fn from_str_response() {
626 let parsed: SipPassthroughHeader = "sip_rh_Call-Info"
627 .parse()
628 .unwrap();
629 assert_eq!(parsed.prefix(), SipHeaderPrefix::Response);
630 assert_eq!(parsed.canonical_name(), "Call-Info");
631 }
632
633 #[test]
634 fn from_str_bye() {
635 let parsed: SipPassthroughHeader = "sip_bye_h_Reason"
636 .parse()
637 .unwrap();
638 assert_eq!(parsed.prefix(), SipHeaderPrefix::Bye);
639 assert_eq!(parsed.canonical_name(), "Reason");
640 }
641
642 #[test]
643 fn from_str_no_bye() {
644 let parsed: SipPassthroughHeader = "sip_nobye_h_Reason"
645 .parse()
646 .unwrap();
647 assert_eq!(parsed.prefix(), SipHeaderPrefix::NoBye);
648 assert_eq!(parsed.canonical_name(), "Reason");
649 }
650
651 #[test]
652 fn from_str_unknown_custom_header() {
653 let parsed: SipPassthroughHeader = "sip_h_X-Tenant"
654 .parse()
655 .unwrap();
656 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
657 assert_eq!(parsed.canonical_name(), "X-Tenant");
658 }
659
660 #[test]
661 fn from_str_invite_unknown_custom() {
662 let parsed: SipPassthroughHeader = "sip_i_x_custom"
663 .parse()
664 .unwrap();
665 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
666 assert_eq!(parsed.canonical_name(), "x-custom");
667 }
668
669 #[test]
670 fn from_str_case_insensitive_invite() {
671 let parsed: SipPassthroughHeader = "SIP_I_CALL_INFO"
672 .parse()
673 .unwrap();
674 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
675 assert_eq!(parsed.canonical_name(), "Call-Info");
676 }
677
678 #[test]
679 fn from_str_case_insensitive_prefix_keeps_the_suffix_as_written() {
680 let parsed: SipPassthroughHeader = "SIP_H_X-Tenant"
681 .parse()
682 .unwrap();
683 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
684 assert_eq!(parsed.canonical_name(), "X-Tenant");
685 assert_eq!(parsed.as_str(), "sip_h_X-Tenant");
686 }
687
688 #[test]
689 fn from_str_rejects_no_prefix() {
690 assert!("call_info"
691 .parse::<SipPassthroughHeader>()
692 .is_err());
693 assert!("sip_call_info"
694 .parse::<SipPassthroughHeader>()
695 .is_err());
696 }
697
698 #[test]
699 fn from_str_rejects_empty_suffix() {
700 assert!("sip_h_"
701 .parse::<SipPassthroughHeader>()
702 .is_err());
703 assert!("sip_i_"
704 .parse::<SipPassthroughHeader>()
705 .is_err());
706 }
707
708 #[test]
709 fn from_str_round_trip_all_prefixes() {
710 let headers = [
711 SipPassthroughHeader::invite(SipHeader::CallInfo),
712 SipPassthroughHeader::request(SipHeader::CallInfo),
713 SipPassthroughHeader::response(SipHeader::CallInfo),
714 SipPassthroughHeader::provisional(SipHeader::AlertInfo),
715 SipPassthroughHeader::bye(SipHeader::Reason),
716 SipPassthroughHeader::no_bye(SipHeader::Reason),
717 ];
718 for h in &headers {
719 let parsed: SipPassthroughHeader = h
720 .as_str()
721 .parse()
722 .unwrap();
723 assert_eq!(&parsed, h, "round-trip failed for {}", h.as_str());
724 }
725 }
726
727 #[test]
730 fn prefix_accessor() {
731 assert_eq!(
732 SipPassthroughHeader::invite(SipHeader::Via).prefix(),
733 SipHeaderPrefix::Invite
734 );
735 assert_eq!(
736 SipPassthroughHeader::request(SipHeader::Via).prefix(),
737 SipHeaderPrefix::Request
738 );
739 }
740
741 #[test]
742 fn canonical_name_accessor() {
743 assert_eq!(
744 SipPassthroughHeader::invite(SipHeader::CallInfo).canonical_name(),
745 "Call-Info"
746 );
747 assert_eq!(
748 SipPassthroughHeader::request_raw("X-Tenant")
749 .unwrap()
750 .canonical_name(),
751 "X-Tenant"
752 );
753 }
754
755 #[test]
758 fn is_array_header_invite_multi_valued() {
759 assert!(SipPassthroughHeader::invite(SipHeader::Via).is_array_header());
760 assert!(SipPassthroughHeader::invite(SipHeader::CallInfo).is_array_header());
761 assert!(SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).is_array_header());
762 assert!(SipPassthroughHeader::invite(SipHeader::RecordRoute).is_array_header());
763 }
764
765 #[test]
766 fn is_array_header_invite_single_valued() {
767 assert!(!SipPassthroughHeader::invite(SipHeader::From).is_array_header());
768 assert!(!SipPassthroughHeader::invite(SipHeader::CallId).is_array_header());
769 assert!(!SipPassthroughHeader::invite(SipHeader::ContentType).is_array_header());
770 }
771
772 #[test]
773 fn is_array_header_non_invite_always_false() {
774 assert!(!SipPassthroughHeader::request(SipHeader::Via).is_array_header());
775 assert!(!SipPassthroughHeader::response(SipHeader::CallInfo).is_array_header());
776 }
777
778 #[test]
779 fn is_array_header_raw_unknown() {
780 assert!(!SipPassthroughHeader::invite_raw("X-Custom")
781 .unwrap()
782 .is_array_header());
783 }
784
785 #[test]
788 fn extract_from_sip_message() {
789 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
790 Call-Info: <sip:example.com>;answer-after=0\r\n\
791 \r\n";
792 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
793 assert_eq!(
794 h.extract_from(msg),
795 vec!["<sip:example.com>;answer-after=0"]
796 );
797 }
798
799 #[test]
800 fn extract_from_missing() {
801 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
802 From: Alice <sip:alice@example.com>\r\n\
803 \r\n";
804 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
805 assert!(h
806 .extract_from(msg)
807 .is_empty());
808 }
809
810 #[test]
813 fn prefix_patterns_covers_all_variants_exactly_once() {
814 for &variant in SipHeaderPrefix::ALL {
815 let count = PREFIX_PATTERNS
816 .iter()
817 .filter(|&&p| p == variant)
818 .count();
819 assert_eq!(
820 count, 1,
821 "{:?} appears {count} times in PREFIX_PATTERNS (expected exactly 1)",
822 variant
823 );
824 }
825 assert_eq!(
826 PREFIX_PATTERNS.len(),
827 SipHeaderPrefix::ALL.len(),
828 "PREFIX_PATTERNS length differs from SipHeaderPrefix::ALL"
829 );
830 }
831
832 #[test]
835 fn variable_name_trait() {
836 use crate::variables::VariableName;
837 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
838 let name: &str = VariableName::as_str(&h);
839 assert_eq!(name, "sip_h_Call-Info");
840 }
841}