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 or LF",
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
100impl std::fmt::Display for SipHeaderPrefix {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.write_str(self.as_str())
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct ParseSipPassthroughError(pub String);
112
113impl std::fmt::Display for ParseSipPassthroughError {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 let len = self
116 .0
117 .len();
118 if PREFIX_PATTERNS
119 .iter()
120 .any(|p| {
121 self.0
122 .eq_ignore_ascii_case(p.as_str())
123 })
124 {
125 return write!(f, "SIP passthrough variable ({len} bytes): no header name");
126 }
127 write!(
128 f,
129 "not a SIP passthrough variable ({len} bytes): no recognised sip_*_ prefix"
130 )
131 }
132}
133
134impl std::error::Error for ParseSipPassthroughError {}
135
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
165pub struct SipPassthroughHeader {
166 prefix: SipHeaderPrefix,
167 canonical_name: String,
168 wire: String,
169}
170
171const PREFIX_PATTERNS: &[SipHeaderPrefix] = &[
173 SipHeaderPrefix::NoBye,
174 SipHeaderPrefix::Bye,
175 SipHeaderPrefix::Provisional,
176 SipHeaderPrefix::Response,
177 SipHeaderPrefix::Invite,
178 SipHeaderPrefix::Request,
179];
180
181fn canonical_from_suffix(prefix: SipHeaderPrefix, suffix: &str) -> String {
184 match prefix {
185 SipHeaderPrefix::Invite => {
186 let with_hyphens = suffix.replace('_', "-");
187 match with_hyphens.parse::<SipHeader>() {
188 Ok(h) => h
189 .as_str()
190 .to_string(),
191 Err(_) => with_hyphens,
192 }
193 }
194 _ => match suffix.parse::<SipHeader>() {
195 Ok(h) => h
196 .as_str()
197 .to_string(),
198 Err(_) => suffix.to_string(),
199 },
200 }
201}
202
203fn validate_header_name(name: &str) -> Result<(), InvalidHeaderName> {
204 if name.is_empty() || crate::wire_safety::contains_wire_terminator(name) {
205 return Err(InvalidHeaderName(name.to_string()));
206 }
207 Ok(())
208}
209
210fn build_wire(prefix: SipHeaderPrefix, canonical: &str) -> String {
211 match prefix {
212 SipHeaderPrefix::Invite => {
213 let pfx = SipHeaderPrefix::Invite.as_str();
214 let mut wire = String::with_capacity(pfx.len() + canonical.len());
215 wire.push_str(pfx);
216 for ch in canonical.chars() {
217 if ch == '-' {
218 wire.push('_');
219 } else {
220 wire.push(ch.to_ascii_lowercase());
221 }
222 }
223 wire
224 }
225 _ => {
226 let pfx = prefix.as_str();
227 let mut wire = String::with_capacity(pfx.len() + canonical.len());
228 wire.push_str(pfx);
229 wire.push_str(canonical);
230 wire
231 }
232 }
233}
234
235impl SipPassthroughHeader {
236 pub fn new(prefix: SipHeaderPrefix, header: SipHeader) -> Self {
238 let canonical = header
239 .as_str()
240 .to_string();
241 let wire = build_wire(prefix, &canonical);
242 Self {
243 prefix,
244 canonical_name: canonical,
245 wire,
246 }
247 }
248
249 pub fn new_raw(
253 prefix: SipHeaderPrefix,
254 name: impl Into<String>,
255 ) -> Result<Self, InvalidHeaderName> {
256 let canonical = name.into();
257 validate_header_name(&canonical)?;
258 let wire = build_wire(prefix, &canonical);
259 Ok(Self {
260 prefix,
261 canonical_name: canonical,
262 wire,
263 })
264 }
265
266 pub fn invite(header: SipHeader) -> Self {
268 Self::new(SipHeaderPrefix::Invite, header)
269 }
270
271 pub fn invite_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
273 Self::new_raw(SipHeaderPrefix::Invite, name)
274 }
275
276 pub fn request(header: SipHeader) -> Self {
278 Self::new(SipHeaderPrefix::Request, header)
279 }
280
281 pub fn request_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
283 Self::new_raw(SipHeaderPrefix::Request, name)
284 }
285
286 pub fn response(header: SipHeader) -> Self {
288 Self::new(SipHeaderPrefix::Response, header)
289 }
290
291 pub fn response_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
293 Self::new_raw(SipHeaderPrefix::Response, name)
294 }
295
296 pub fn provisional(header: SipHeader) -> Self {
298 Self::new(SipHeaderPrefix::Provisional, header)
299 }
300
301 pub fn provisional_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
303 Self::new_raw(SipHeaderPrefix::Provisional, name)
304 }
305
306 pub fn bye(header: SipHeader) -> Self {
308 Self::new(SipHeaderPrefix::Bye, header)
309 }
310
311 pub fn bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
313 Self::new_raw(SipHeaderPrefix::Bye, name)
314 }
315
316 pub fn no_bye(header: SipHeader) -> Self {
318 Self::new(SipHeaderPrefix::NoBye, header)
319 }
320
321 pub fn no_bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
323 Self::new_raw(SipHeaderPrefix::NoBye, name)
324 }
325
326 pub fn prefix(&self) -> SipHeaderPrefix {
328 self.prefix
329 }
330
331 pub fn canonical_name(&self) -> &str {
333 &self.canonical_name
334 }
335
336 pub fn as_str(&self) -> &str {
338 &self.wire
339 }
340
341 pub fn extract_from(&self, message: &str) -> Vec<String> {
346 sip_header::extract_header(message, &self.canonical_name)
347 }
348
349 pub fn is_array_header(&self) -> bool {
355 if self.prefix != SipHeaderPrefix::Invite {
356 return false;
357 }
358 self.canonical_name
359 .parse::<SipHeader>()
360 .map(|h| h.is_multi_valued())
361 .unwrap_or(false)
362 }
363}
364
365impl super::VariableName for SipPassthroughHeader {
366 fn as_str(&self) -> &str {
367 &self.wire
368 }
369}
370
371impl std::fmt::Display for SipPassthroughHeader {
372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373 f.write_str(&self.wire)
374 }
375}
376
377impl AsRef<str> for SipPassthroughHeader {
378 fn as_ref(&self) -> &str {
379 &self.wire
380 }
381}
382
383impl From<SipPassthroughHeader> for String {
384 fn from(h: SipPassthroughHeader) -> Self {
385 h.wire
386 }
387}
388
389impl std::str::FromStr for SipPassthroughHeader {
390 type Err = ParseSipPassthroughError;
391
392 fn from_str(s: &str) -> Result<Self, Self::Err> {
393 for &prefix in PREFIX_PATTERNS {
394 let pfx = prefix.as_str();
395 if !s
396 .get(..pfx.len())
397 .is_some_and(|head| head.eq_ignore_ascii_case(pfx))
398 {
399 continue;
400 }
401 let suffix = &s[pfx.len()..];
402 if suffix.is_empty() {
403 return Err(ParseSipPassthroughError(s.to_string()));
404 }
405 let canonical = canonical_from_suffix(prefix, suffix);
406 let wire = match prefix {
407 SipHeaderPrefix::Invite => build_wire(prefix, &canonical),
408 _ => format!("{pfx}{suffix}"),
409 };
410 return Ok(Self {
411 prefix,
412 canonical_name: canonical,
413 wire,
414 });
415 }
416
417 Err(ParseSipPassthroughError(s.to_string()))
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
428 fn invite_typed_wire_format() {
429 assert_eq!(
430 SipPassthroughHeader::invite(SipHeader::CallInfo).as_str(),
431 "sip_i_call_info"
432 );
433 assert_eq!(
434 SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).as_str(),
435 "sip_i_p_asserted_identity"
436 );
437 assert_eq!(
438 SipPassthroughHeader::invite(SipHeader::Via).as_str(),
439 "sip_i_via"
440 );
441 assert_eq!(
442 SipPassthroughHeader::invite(SipHeader::CallId).as_str(),
443 "sip_i_call_id"
444 );
445 }
446
447 #[test]
448 fn request_typed_wire_format() {
449 assert_eq!(
450 SipPassthroughHeader::request(SipHeader::CallInfo).as_str(),
451 "sip_h_Call-Info"
452 );
453 assert_eq!(
454 SipPassthroughHeader::request(SipHeader::PAssertedIdentity).as_str(),
455 "sip_h_P-Asserted-Identity"
456 );
457 }
458
459 #[test]
460 fn response_typed_wire_format() {
461 assert_eq!(
462 SipPassthroughHeader::response(SipHeader::CallInfo).as_str(),
463 "sip_rh_Call-Info"
464 );
465 }
466
467 #[test]
468 fn provisional_typed_wire_format() {
469 assert_eq!(
470 SipPassthroughHeader::provisional(SipHeader::AlertInfo).as_str(),
471 "sip_ph_Alert-Info"
472 );
473 }
474
475 #[test]
476 fn bye_typed_wire_format() {
477 assert_eq!(
478 SipPassthroughHeader::bye(SipHeader::Reason).as_str(),
479 "sip_bye_h_Reason"
480 );
481 }
482
483 #[test]
484 fn no_bye_typed_wire_format() {
485 assert_eq!(
486 SipPassthroughHeader::no_bye(SipHeader::Reason).as_str(),
487 "sip_nobye_h_Reason"
488 );
489 }
490
491 #[test]
492 fn raw_custom_header() {
493 assert_eq!(
494 SipPassthroughHeader::request_raw("X-Tenant")
495 .unwrap()
496 .as_str(),
497 "sip_h_X-Tenant"
498 );
499 assert_eq!(
500 SipPassthroughHeader::invite_raw("X-Custom")
501 .unwrap()
502 .as_str(),
503 "sip_i_x_custom"
504 );
505 }
506
507 #[test]
508 fn raw_rejects_newlines() {
509 assert!(SipPassthroughHeader::request_raw("X-Bad\nHeader").is_err());
510 assert!(SipPassthroughHeader::request_raw("X-Bad\rHeader").is_err());
511 assert!(SipPassthroughHeader::request_raw("").is_err());
512 }
513
514 #[test]
517 fn invalid_name_error_names_the_shape_not_the_value() {
518 let err = SipPassthroughHeader::request_raw("X-Tenant\nInjected").unwrap_err();
519 let msg = err.to_string();
520 assert!(!msg.contains("X-Tenant"), "error quoted its input: {msg}");
521 assert!(
522 msg.contains("17 bytes"),
523 "error does not give the byte length: {msg}"
524 );
525 assert_eq!(err.0, "X-Tenant\nInjected");
526 }
527
528 #[test]
529 fn empty_name_error_says_which_rule_it_broke() {
530 let msg = SipPassthroughHeader::request_raw("")
531 .unwrap_err()
532 .to_string();
533 assert!(msg.contains("empty"), "{msg}");
534 }
535
536 #[test]
537 fn parse_error_names_the_shape_not_the_value() {
538 let msg = "X-Tenant: secret"
539 .parse::<SipPassthroughHeader>()
540 .unwrap_err()
541 .to_string();
542 assert!(!msg.contains("secret"), "error quoted its input: {msg}");
543 assert!(
544 msg.contains("16 bytes"),
545 "error does not give the byte length: {msg}"
546 );
547 }
548
549 #[test]
550 fn parse_error_separates_a_missing_header_name_from_a_missing_prefix() {
551 let msg = "sip_h_"
552 .parse::<SipPassthroughHeader>()
553 .unwrap_err()
554 .to_string();
555 assert!(msg.contains("no header name"), "{msg}");
556 }
557
558 #[test]
561 fn display_matches_as_str() {
562 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
563 assert_eq!(h.to_string(), h.as_str());
564 }
565
566 #[test]
569 fn from_str_request() {
570 let parsed: SipPassthroughHeader = "sip_h_Call-Info"
571 .parse()
572 .unwrap();
573 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
574 assert_eq!(parsed.canonical_name(), "Call-Info");
575 assert_eq!(parsed.as_str(), "sip_h_Call-Info");
576 }
577
578 #[test]
579 fn from_str_invite() {
580 let parsed: SipPassthroughHeader = "sip_i_call_info"
581 .parse()
582 .unwrap();
583 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
584 assert_eq!(parsed.canonical_name(), "Call-Info");
585 assert_eq!(parsed.as_str(), "sip_i_call_info");
586 }
587
588 #[test]
589 fn from_str_invite_p_asserted_identity() {
590 let parsed: SipPassthroughHeader = "sip_i_p_asserted_identity"
591 .parse()
592 .unwrap();
593 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
594 assert_eq!(parsed.canonical_name(), "P-Asserted-Identity");
595 }
596
597 #[test]
598 fn from_str_response() {
599 let parsed: SipPassthroughHeader = "sip_rh_Call-Info"
600 .parse()
601 .unwrap();
602 assert_eq!(parsed.prefix(), SipHeaderPrefix::Response);
603 assert_eq!(parsed.canonical_name(), "Call-Info");
604 }
605
606 #[test]
607 fn from_str_bye() {
608 let parsed: SipPassthroughHeader = "sip_bye_h_Reason"
609 .parse()
610 .unwrap();
611 assert_eq!(parsed.prefix(), SipHeaderPrefix::Bye);
612 assert_eq!(parsed.canonical_name(), "Reason");
613 }
614
615 #[test]
616 fn from_str_no_bye() {
617 let parsed: SipPassthroughHeader = "sip_nobye_h_Reason"
618 .parse()
619 .unwrap();
620 assert_eq!(parsed.prefix(), SipHeaderPrefix::NoBye);
621 assert_eq!(parsed.canonical_name(), "Reason");
622 }
623
624 #[test]
625 fn from_str_unknown_custom_header() {
626 let parsed: SipPassthroughHeader = "sip_h_X-Tenant"
627 .parse()
628 .unwrap();
629 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
630 assert_eq!(parsed.canonical_name(), "X-Tenant");
631 }
632
633 #[test]
634 fn from_str_invite_unknown_custom() {
635 let parsed: SipPassthroughHeader = "sip_i_x_custom"
636 .parse()
637 .unwrap();
638 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
639 assert_eq!(parsed.canonical_name(), "x-custom");
640 }
641
642 #[test]
643 fn from_str_case_insensitive_invite() {
644 let parsed: SipPassthroughHeader = "SIP_I_CALL_INFO"
645 .parse()
646 .unwrap();
647 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
648 assert_eq!(parsed.canonical_name(), "Call-Info");
649 }
650
651 #[test]
652 fn from_str_case_insensitive_prefix_keeps_the_suffix_as_written() {
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 assert_eq!(parsed.as_str(), "sip_h_X-Tenant");
659 }
660
661 #[test]
662 fn from_str_rejects_no_prefix() {
663 assert!("call_info"
664 .parse::<SipPassthroughHeader>()
665 .is_err());
666 assert!("sip_call_info"
667 .parse::<SipPassthroughHeader>()
668 .is_err());
669 }
670
671 #[test]
672 fn from_str_rejects_empty_suffix() {
673 assert!("sip_h_"
674 .parse::<SipPassthroughHeader>()
675 .is_err());
676 assert!("sip_i_"
677 .parse::<SipPassthroughHeader>()
678 .is_err());
679 }
680
681 #[test]
682 fn from_str_round_trip_all_prefixes() {
683 let headers = [
684 SipPassthroughHeader::invite(SipHeader::CallInfo),
685 SipPassthroughHeader::request(SipHeader::CallInfo),
686 SipPassthroughHeader::response(SipHeader::CallInfo),
687 SipPassthroughHeader::provisional(SipHeader::AlertInfo),
688 SipPassthroughHeader::bye(SipHeader::Reason),
689 SipPassthroughHeader::no_bye(SipHeader::Reason),
690 ];
691 for h in &headers {
692 let parsed: SipPassthroughHeader = h
693 .as_str()
694 .parse()
695 .unwrap();
696 assert_eq!(&parsed, h, "round-trip failed for {}", h.as_str());
697 }
698 }
699
700 #[test]
703 fn prefix_accessor() {
704 assert_eq!(
705 SipPassthroughHeader::invite(SipHeader::Via).prefix(),
706 SipHeaderPrefix::Invite
707 );
708 assert_eq!(
709 SipPassthroughHeader::request(SipHeader::Via).prefix(),
710 SipHeaderPrefix::Request
711 );
712 }
713
714 #[test]
715 fn canonical_name_accessor() {
716 assert_eq!(
717 SipPassthroughHeader::invite(SipHeader::CallInfo).canonical_name(),
718 "Call-Info"
719 );
720 assert_eq!(
721 SipPassthroughHeader::request_raw("X-Tenant")
722 .unwrap()
723 .canonical_name(),
724 "X-Tenant"
725 );
726 }
727
728 #[test]
731 fn is_array_header_invite_multi_valued() {
732 assert!(SipPassthroughHeader::invite(SipHeader::Via).is_array_header());
733 assert!(SipPassthroughHeader::invite(SipHeader::CallInfo).is_array_header());
734 assert!(SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).is_array_header());
735 assert!(SipPassthroughHeader::invite(SipHeader::RecordRoute).is_array_header());
736 }
737
738 #[test]
739 fn is_array_header_invite_single_valued() {
740 assert!(!SipPassthroughHeader::invite(SipHeader::From).is_array_header());
741 assert!(!SipPassthroughHeader::invite(SipHeader::CallId).is_array_header());
742 assert!(!SipPassthroughHeader::invite(SipHeader::ContentType).is_array_header());
743 }
744
745 #[test]
746 fn is_array_header_non_invite_always_false() {
747 assert!(!SipPassthroughHeader::request(SipHeader::Via).is_array_header());
748 assert!(!SipPassthroughHeader::response(SipHeader::CallInfo).is_array_header());
749 }
750
751 #[test]
752 fn is_array_header_raw_unknown() {
753 assert!(!SipPassthroughHeader::invite_raw("X-Custom")
754 .unwrap()
755 .is_array_header());
756 }
757
758 #[test]
761 fn extract_from_sip_message() {
762 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
763 Call-Info: <sip:example.com>;answer-after=0\r\n\
764 \r\n";
765 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
766 assert_eq!(
767 h.extract_from(msg),
768 vec!["<sip:example.com>;answer-after=0"]
769 );
770 }
771
772 #[test]
773 fn extract_from_missing() {
774 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
775 From: Alice <sip:alice@example.com>\r\n\
776 \r\n";
777 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
778 assert!(h
779 .extract_from(msg)
780 .is_empty());
781 }
782
783 #[test]
786 fn prefix_patterns_covers_all_variants_exactly_once() {
787 for &variant in SipHeaderPrefix::ALL {
788 let count = PREFIX_PATTERNS
789 .iter()
790 .filter(|&&p| p == variant)
791 .count();
792 assert_eq!(
793 count, 1,
794 "{:?} appears {count} times in PREFIX_PATTERNS (expected exactly 1)",
795 variant
796 );
797 }
798 assert_eq!(
799 PREFIX_PATTERNS.len(),
800 SipHeaderPrefix::ALL.len(),
801 "PREFIX_PATTERNS length differs from SipHeaderPrefix::ALL"
802 );
803 }
804
805 #[test]
808 fn variable_name_trait() {
809 use crate::variables::VariableName;
810 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
811 let name: &str = VariableName::as_str(&h);
812 assert_eq!(name, "sip_h_Call-Info");
813 }
814}