freeswitch_types/variables/
sip_passthrough.rs1use sip_header::SipHeader;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct InvalidHeaderName(String);
25
26impl std::fmt::Display for InvalidHeaderName {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(
29 f,
30 "invalid SIP header name {:?}: contains \\n or \\r",
31 self.0
32 )
33 }
34}
35
36impl std::error::Error for InvalidHeaderName {}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum SipHeaderPrefix {
45 Invite,
47 Request,
49 Response,
51 Provisional,
53 Bye,
55 NoBye,
57}
58
59impl SipHeaderPrefix {
60 pub fn as_str(&self) -> &'static str {
62 match self {
63 Self::Invite => "sip_i_",
64 Self::Request => "sip_h_",
65 Self::Response => "sip_rh_",
66 Self::Provisional => "sip_ph_",
67 Self::Bye => "sip_bye_h_",
68 Self::NoBye => "sip_nobye_h_",
69 }
70 }
71}
72
73impl std::fmt::Display for SipHeaderPrefix {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.write_str(self.as_str())
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct ParseSipPassthroughError(pub String);
82
83impl std::fmt::Display for ParseSipPassthroughError {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "not a SIP passthrough variable: {}", self.0)
86 }
87}
88
89impl std::error::Error for ParseSipPassthroughError {}
90
91#[derive(Debug, Clone, PartialEq, Eq, Hash)]
120pub struct SipPassthroughHeader {
121 prefix: SipHeaderPrefix,
122 canonical_name: String,
123 wire: String,
124}
125
126const PREFIX_PATTERNS: &[(SipHeaderPrefix, &str)] = &[
128 (SipHeaderPrefix::NoBye, "sip_nobye_h_"),
129 (SipHeaderPrefix::Bye, "sip_bye_h_"),
130 (SipHeaderPrefix::Provisional, "sip_ph_"),
131 (SipHeaderPrefix::Response, "sip_rh_"),
132 (SipHeaderPrefix::Invite, "sip_i_"),
133 (SipHeaderPrefix::Request, "sip_h_"),
134];
135
136fn validate_header_name(name: &str) -> Result<(), InvalidHeaderName> {
137 if name.is_empty() || name.contains('\n') || name.contains('\r') {
138 return Err(InvalidHeaderName(name.to_string()));
139 }
140 Ok(())
141}
142
143fn build_wire(prefix: SipHeaderPrefix, canonical: &str) -> String {
144 match prefix {
145 SipHeaderPrefix::Invite => {
146 let mut wire = String::with_capacity(6 + canonical.len());
147 wire.push_str("sip_i_");
148 for ch in canonical.chars() {
149 if ch == '-' {
150 wire.push('_');
151 } else {
152 wire.push(ch.to_ascii_lowercase());
153 }
154 }
155 wire
156 }
157 _ => {
158 let pfx = prefix.as_str();
159 let mut wire = String::with_capacity(pfx.len() + canonical.len());
160 wire.push_str(pfx);
161 wire.push_str(canonical);
162 wire
163 }
164 }
165}
166
167impl SipPassthroughHeader {
168 pub fn new(prefix: SipHeaderPrefix, header: SipHeader) -> Self {
170 let canonical = header
171 .as_str()
172 .to_string();
173 let wire = build_wire(prefix, &canonical);
174 Self {
175 prefix,
176 canonical_name: canonical,
177 wire,
178 }
179 }
180
181 pub fn new_raw(
185 prefix: SipHeaderPrefix,
186 name: impl Into<String>,
187 ) -> Result<Self, InvalidHeaderName> {
188 let canonical = name.into();
189 validate_header_name(&canonical)?;
190 let wire = build_wire(prefix, &canonical);
191 Ok(Self {
192 prefix,
193 canonical_name: canonical,
194 wire,
195 })
196 }
197
198 pub fn invite(header: SipHeader) -> Self {
200 Self::new(SipHeaderPrefix::Invite, header)
201 }
202
203 pub fn invite_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
205 Self::new_raw(SipHeaderPrefix::Invite, name)
206 }
207
208 pub fn request(header: SipHeader) -> Self {
210 Self::new(SipHeaderPrefix::Request, header)
211 }
212
213 pub fn request_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
215 Self::new_raw(SipHeaderPrefix::Request, name)
216 }
217
218 pub fn response(header: SipHeader) -> Self {
220 Self::new(SipHeaderPrefix::Response, header)
221 }
222
223 pub fn response_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
225 Self::new_raw(SipHeaderPrefix::Response, name)
226 }
227
228 pub fn provisional(header: SipHeader) -> Self {
230 Self::new(SipHeaderPrefix::Provisional, header)
231 }
232
233 pub fn provisional_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
235 Self::new_raw(SipHeaderPrefix::Provisional, name)
236 }
237
238 pub fn bye(header: SipHeader) -> Self {
240 Self::new(SipHeaderPrefix::Bye, header)
241 }
242
243 pub fn bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
245 Self::new_raw(SipHeaderPrefix::Bye, name)
246 }
247
248 pub fn no_bye(header: SipHeader) -> Self {
250 Self::new(SipHeaderPrefix::NoBye, header)
251 }
252
253 pub fn no_bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
255 Self::new_raw(SipHeaderPrefix::NoBye, name)
256 }
257
258 pub fn prefix(&self) -> SipHeaderPrefix {
260 self.prefix
261 }
262
263 pub fn canonical_name(&self) -> &str {
265 &self.canonical_name
266 }
267
268 pub fn as_str(&self) -> &str {
270 &self.wire
271 }
272
273 pub fn extract_from(&self, message: &str) -> Vec<String> {
278 sip_header::extract_header(message, &self.canonical_name)
279 }
280
281 pub fn is_array_header(&self) -> bool {
287 if self.prefix != SipHeaderPrefix::Invite {
288 return false;
289 }
290 self.canonical_name
291 .parse::<SipHeader>()
292 .map(|h| h.is_multi_valued())
293 .unwrap_or(false)
294 }
295}
296
297impl super::VariableName for SipPassthroughHeader {
298 fn as_str(&self) -> &str {
299 &self.wire
300 }
301}
302
303impl std::fmt::Display for SipPassthroughHeader {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 f.write_str(&self.wire)
306 }
307}
308
309impl AsRef<str> for SipPassthroughHeader {
310 fn as_ref(&self) -> &str {
311 &self.wire
312 }
313}
314
315impl From<SipPassthroughHeader> for String {
316 fn from(h: SipPassthroughHeader) -> Self {
317 h.wire
318 }
319}
320
321impl std::str::FromStr for SipPassthroughHeader {
322 type Err = ParseSipPassthroughError;
323
324 fn from_str(s: &str) -> Result<Self, Self::Err> {
325 for &(prefix, pat) in PREFIX_PATTERNS {
326 if let Some(suffix) = s.strip_prefix(pat) {
327 if suffix.is_empty() {
328 return Err(ParseSipPassthroughError(s.to_string()));
329 }
330
331 let canonical = match prefix {
332 SipHeaderPrefix::Invite => {
333 let with_hyphens = suffix.replace('_', "-");
336 match with_hyphens.parse::<SipHeader>() {
337 Ok(h) => h
338 .as_str()
339 .to_string(),
340 Err(_) => with_hyphens,
341 }
342 }
343 _ => match suffix.parse::<SipHeader>() {
344 Ok(h) => h
345 .as_str()
346 .to_string(),
347 Err(_) => suffix.to_string(),
348 },
349 };
350
351 return Ok(Self {
352 prefix,
353 canonical_name: canonical,
354 wire: s.to_string(),
355 });
356 }
357 }
358
359 let lower = s.to_ascii_lowercase();
362 if lower != s {
363 for &(prefix, pat) in PREFIX_PATTERNS {
364 if let Some(suffix) = lower.strip_prefix(pat) {
365 if suffix.is_empty() {
366 return Err(ParseSipPassthroughError(s.to_string()));
367 }
368 let canonical = match prefix {
369 SipHeaderPrefix::Invite => {
370 let with_hyphens = suffix.replace('_', "-");
371 match with_hyphens.parse::<SipHeader>() {
372 Ok(h) => h
373 .as_str()
374 .to_string(),
375 Err(_) => with_hyphens,
376 }
377 }
378 _ => match suffix.parse::<SipHeader>() {
379 Ok(h) => h
380 .as_str()
381 .to_string(),
382 Err(_) => suffix.to_string(),
383 },
384 };
385 let wire = build_wire(prefix, &canonical);
386 return Ok(Self {
387 prefix,
388 canonical_name: canonical,
389 wire,
390 });
391 }
392 }
393 }
394
395 Err(ParseSipPassthroughError(s.to_string()))
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
406 fn invite_typed_wire_format() {
407 assert_eq!(
408 SipPassthroughHeader::invite(SipHeader::CallInfo).as_str(),
409 "sip_i_call_info"
410 );
411 assert_eq!(
412 SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).as_str(),
413 "sip_i_p_asserted_identity"
414 );
415 assert_eq!(
416 SipPassthroughHeader::invite(SipHeader::Via).as_str(),
417 "sip_i_via"
418 );
419 assert_eq!(
420 SipPassthroughHeader::invite(SipHeader::CallId).as_str(),
421 "sip_i_call_id"
422 );
423 }
424
425 #[test]
426 fn request_typed_wire_format() {
427 assert_eq!(
428 SipPassthroughHeader::request(SipHeader::CallInfo).as_str(),
429 "sip_h_Call-Info"
430 );
431 assert_eq!(
432 SipPassthroughHeader::request(SipHeader::PAssertedIdentity).as_str(),
433 "sip_h_P-Asserted-Identity"
434 );
435 }
436
437 #[test]
438 fn response_typed_wire_format() {
439 assert_eq!(
440 SipPassthroughHeader::response(SipHeader::CallInfo).as_str(),
441 "sip_rh_Call-Info"
442 );
443 }
444
445 #[test]
446 fn provisional_typed_wire_format() {
447 assert_eq!(
448 SipPassthroughHeader::provisional(SipHeader::AlertInfo).as_str(),
449 "sip_ph_Alert-Info"
450 );
451 }
452
453 #[test]
454 fn bye_typed_wire_format() {
455 assert_eq!(
456 SipPassthroughHeader::bye(SipHeader::Reason).as_str(),
457 "sip_bye_h_Reason"
458 );
459 }
460
461 #[test]
462 fn no_bye_typed_wire_format() {
463 assert_eq!(
464 SipPassthroughHeader::no_bye(SipHeader::Reason).as_str(),
465 "sip_nobye_h_Reason"
466 );
467 }
468
469 #[test]
470 fn raw_custom_header() {
471 assert_eq!(
472 SipPassthroughHeader::request_raw("X-Tenant")
473 .unwrap()
474 .as_str(),
475 "sip_h_X-Tenant"
476 );
477 assert_eq!(
478 SipPassthroughHeader::invite_raw("X-Custom")
479 .unwrap()
480 .as_str(),
481 "sip_i_x_custom"
482 );
483 }
484
485 #[test]
486 fn raw_rejects_newlines() {
487 assert!(SipPassthroughHeader::request_raw("X-Bad\nHeader").is_err());
488 assert!(SipPassthroughHeader::request_raw("X-Bad\rHeader").is_err());
489 assert!(SipPassthroughHeader::request_raw("").is_err());
490 }
491
492 #[test]
495 fn display_matches_as_str() {
496 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
497 assert_eq!(h.to_string(), h.as_str());
498 }
499
500 #[test]
503 fn from_str_request() {
504 let parsed: SipPassthroughHeader = "sip_h_Call-Info"
505 .parse()
506 .unwrap();
507 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
508 assert_eq!(parsed.canonical_name(), "Call-Info");
509 assert_eq!(parsed.as_str(), "sip_h_Call-Info");
510 }
511
512 #[test]
513 fn from_str_invite() {
514 let parsed: SipPassthroughHeader = "sip_i_call_info"
515 .parse()
516 .unwrap();
517 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
518 assert_eq!(parsed.canonical_name(), "Call-Info");
519 assert_eq!(parsed.as_str(), "sip_i_call_info");
520 }
521
522 #[test]
523 fn from_str_invite_p_asserted_identity() {
524 let parsed: SipPassthroughHeader = "sip_i_p_asserted_identity"
525 .parse()
526 .unwrap();
527 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
528 assert_eq!(parsed.canonical_name(), "P-Asserted-Identity");
529 }
530
531 #[test]
532 fn from_str_response() {
533 let parsed: SipPassthroughHeader = "sip_rh_Call-Info"
534 .parse()
535 .unwrap();
536 assert_eq!(parsed.prefix(), SipHeaderPrefix::Response);
537 assert_eq!(parsed.canonical_name(), "Call-Info");
538 }
539
540 #[test]
541 fn from_str_bye() {
542 let parsed: SipPassthroughHeader = "sip_bye_h_Reason"
543 .parse()
544 .unwrap();
545 assert_eq!(parsed.prefix(), SipHeaderPrefix::Bye);
546 assert_eq!(parsed.canonical_name(), "Reason");
547 }
548
549 #[test]
550 fn from_str_no_bye() {
551 let parsed: SipPassthroughHeader = "sip_nobye_h_Reason"
552 .parse()
553 .unwrap();
554 assert_eq!(parsed.prefix(), SipHeaderPrefix::NoBye);
555 assert_eq!(parsed.canonical_name(), "Reason");
556 }
557
558 #[test]
559 fn from_str_unknown_custom_header() {
560 let parsed: SipPassthroughHeader = "sip_h_X-Tenant"
561 .parse()
562 .unwrap();
563 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
564 assert_eq!(parsed.canonical_name(), "X-Tenant");
565 }
566
567 #[test]
568 fn from_str_invite_unknown_custom() {
569 let parsed: SipPassthroughHeader = "sip_i_x_custom"
570 .parse()
571 .unwrap();
572 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
573 assert_eq!(parsed.canonical_name(), "x-custom");
575 }
576
577 #[test]
578 fn from_str_case_insensitive_invite() {
579 let parsed: SipPassthroughHeader = "SIP_I_CALL_INFO"
580 .parse()
581 .unwrap();
582 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
583 assert_eq!(parsed.canonical_name(), "Call-Info");
584 }
585
586 #[test]
587 fn from_str_rejects_no_prefix() {
588 assert!("call_info"
589 .parse::<SipPassthroughHeader>()
590 .is_err());
591 assert!("sip_call_info"
592 .parse::<SipPassthroughHeader>()
593 .is_err());
594 }
595
596 #[test]
597 fn from_str_rejects_empty_suffix() {
598 assert!("sip_h_"
599 .parse::<SipPassthroughHeader>()
600 .is_err());
601 assert!("sip_i_"
602 .parse::<SipPassthroughHeader>()
603 .is_err());
604 }
605
606 #[test]
607 fn from_str_round_trip_all_prefixes() {
608 let headers = [
609 SipPassthroughHeader::invite(SipHeader::CallInfo),
610 SipPassthroughHeader::request(SipHeader::CallInfo),
611 SipPassthroughHeader::response(SipHeader::CallInfo),
612 SipPassthroughHeader::provisional(SipHeader::AlertInfo),
613 SipPassthroughHeader::bye(SipHeader::Reason),
614 SipPassthroughHeader::no_bye(SipHeader::Reason),
615 ];
616 for h in &headers {
617 let parsed: SipPassthroughHeader = h
618 .as_str()
619 .parse()
620 .unwrap();
621 assert_eq!(&parsed, h, "round-trip failed for {}", h.as_str());
622 }
623 }
624
625 #[test]
628 fn prefix_accessor() {
629 assert_eq!(
630 SipPassthroughHeader::invite(SipHeader::Via).prefix(),
631 SipHeaderPrefix::Invite
632 );
633 assert_eq!(
634 SipPassthroughHeader::request(SipHeader::Via).prefix(),
635 SipHeaderPrefix::Request
636 );
637 }
638
639 #[test]
640 fn canonical_name_accessor() {
641 assert_eq!(
642 SipPassthroughHeader::invite(SipHeader::CallInfo).canonical_name(),
643 "Call-Info"
644 );
645 assert_eq!(
646 SipPassthroughHeader::request_raw("X-Tenant")
647 .unwrap()
648 .canonical_name(),
649 "X-Tenant"
650 );
651 }
652
653 #[test]
656 fn is_array_header_invite_multi_valued() {
657 assert!(SipPassthroughHeader::invite(SipHeader::Via).is_array_header());
658 assert!(SipPassthroughHeader::invite(SipHeader::CallInfo).is_array_header());
659 assert!(SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).is_array_header());
660 assert!(SipPassthroughHeader::invite(SipHeader::RecordRoute).is_array_header());
661 }
662
663 #[test]
664 fn is_array_header_invite_single_valued() {
665 assert!(!SipPassthroughHeader::invite(SipHeader::From).is_array_header());
666 assert!(!SipPassthroughHeader::invite(SipHeader::CallId).is_array_header());
667 assert!(!SipPassthroughHeader::invite(SipHeader::ContentType).is_array_header());
668 }
669
670 #[test]
671 fn is_array_header_non_invite_always_false() {
672 assert!(!SipPassthroughHeader::request(SipHeader::Via).is_array_header());
673 assert!(!SipPassthroughHeader::response(SipHeader::CallInfo).is_array_header());
674 }
675
676 #[test]
677 fn is_array_header_raw_unknown() {
678 assert!(!SipPassthroughHeader::invite_raw("X-Custom")
679 .unwrap()
680 .is_array_header());
681 }
682
683 #[test]
686 fn extract_from_sip_message() {
687 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
688 Call-Info: <sip:example.com>;answer-after=0\r\n\
689 \r\n";
690 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
691 assert_eq!(
692 h.extract_from(msg),
693 vec!["<sip:example.com>;answer-after=0"]
694 );
695 }
696
697 #[test]
698 fn extract_from_missing() {
699 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
700 From: Alice <sip:alice@example.com>\r\n\
701 \r\n";
702 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
703 assert!(h
704 .extract_from(msg)
705 .is_empty());
706 }
707
708 #[test]
711 fn variable_name_trait() {
712 use crate::variables::VariableName;
713 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
714 let name: &str = VariableName::as_str(&h);
715 assert_eq!(name, "sip_h_Call-Info");
716 }
717}