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) -> Option<String> {
277 sip_header::extract_header(message, &self.canonical_name)
278 }
279
280 pub fn is_array_header(&self) -> bool {
286 if self.prefix != SipHeaderPrefix::Invite {
287 return false;
288 }
289 self.canonical_name
290 .parse::<SipHeader>()
291 .map(|h| h.is_multi_valued())
292 .unwrap_or(false)
293 }
294}
295
296impl super::VariableName for SipPassthroughHeader {
297 fn as_str(&self) -> &str {
298 &self.wire
299 }
300}
301
302impl std::fmt::Display for SipPassthroughHeader {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.write_str(&self.wire)
305 }
306}
307
308impl AsRef<str> for SipPassthroughHeader {
309 fn as_ref(&self) -> &str {
310 &self.wire
311 }
312}
313
314impl From<SipPassthroughHeader> for String {
315 fn from(h: SipPassthroughHeader) -> Self {
316 h.wire
317 }
318}
319
320impl std::str::FromStr for SipPassthroughHeader {
321 type Err = ParseSipPassthroughError;
322
323 fn from_str(s: &str) -> Result<Self, Self::Err> {
324 for &(prefix, pat) in PREFIX_PATTERNS {
325 if let Some(suffix) = s.strip_prefix(pat) {
326 if suffix.is_empty() {
327 return Err(ParseSipPassthroughError(s.to_string()));
328 }
329
330 let canonical = match prefix {
331 SipHeaderPrefix::Invite => {
332 let with_hyphens = suffix.replace('_', "-");
335 match with_hyphens.parse::<SipHeader>() {
336 Ok(h) => h
337 .as_str()
338 .to_string(),
339 Err(_) => with_hyphens,
340 }
341 }
342 _ => match suffix.parse::<SipHeader>() {
343 Ok(h) => h
344 .as_str()
345 .to_string(),
346 Err(_) => suffix.to_string(),
347 },
348 };
349
350 return Ok(Self {
351 prefix,
352 canonical_name: canonical,
353 wire: s.to_string(),
354 });
355 }
356 }
357
358 let lower = s.to_ascii_lowercase();
361 if lower != s {
362 for &(prefix, pat) in PREFIX_PATTERNS {
363 if let Some(suffix) = lower.strip_prefix(pat) {
364 if suffix.is_empty() {
365 return Err(ParseSipPassthroughError(s.to_string()));
366 }
367 let canonical = match prefix {
368 SipHeaderPrefix::Invite => {
369 let with_hyphens = suffix.replace('_', "-");
370 match with_hyphens.parse::<SipHeader>() {
371 Ok(h) => h
372 .as_str()
373 .to_string(),
374 Err(_) => with_hyphens,
375 }
376 }
377 _ => match suffix.parse::<SipHeader>() {
378 Ok(h) => h
379 .as_str()
380 .to_string(),
381 Err(_) => suffix.to_string(),
382 },
383 };
384 let wire = build_wire(prefix, &canonical);
385 return Ok(Self {
386 prefix,
387 canonical_name: canonical,
388 wire,
389 });
390 }
391 }
392 }
393
394 Err(ParseSipPassthroughError(s.to_string()))
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
405 fn invite_typed_wire_format() {
406 assert_eq!(
407 SipPassthroughHeader::invite(SipHeader::CallInfo).as_str(),
408 "sip_i_call_info"
409 );
410 assert_eq!(
411 SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).as_str(),
412 "sip_i_p_asserted_identity"
413 );
414 assert_eq!(
415 SipPassthroughHeader::invite(SipHeader::Via).as_str(),
416 "sip_i_via"
417 );
418 assert_eq!(
419 SipPassthroughHeader::invite(SipHeader::CallId).as_str(),
420 "sip_i_call_id"
421 );
422 }
423
424 #[test]
425 fn request_typed_wire_format() {
426 assert_eq!(
427 SipPassthroughHeader::request(SipHeader::CallInfo).as_str(),
428 "sip_h_Call-Info"
429 );
430 assert_eq!(
431 SipPassthroughHeader::request(SipHeader::PAssertedIdentity).as_str(),
432 "sip_h_P-Asserted-Identity"
433 );
434 }
435
436 #[test]
437 fn response_typed_wire_format() {
438 assert_eq!(
439 SipPassthroughHeader::response(SipHeader::CallInfo).as_str(),
440 "sip_rh_Call-Info"
441 );
442 }
443
444 #[test]
445 fn provisional_typed_wire_format() {
446 assert_eq!(
447 SipPassthroughHeader::provisional(SipHeader::AlertInfo).as_str(),
448 "sip_ph_Alert-Info"
449 );
450 }
451
452 #[test]
453 fn bye_typed_wire_format() {
454 assert_eq!(
455 SipPassthroughHeader::bye(SipHeader::Reason).as_str(),
456 "sip_bye_h_Reason"
457 );
458 }
459
460 #[test]
461 fn no_bye_typed_wire_format() {
462 assert_eq!(
463 SipPassthroughHeader::no_bye(SipHeader::Reason).as_str(),
464 "sip_nobye_h_Reason"
465 );
466 }
467
468 #[test]
469 fn raw_custom_header() {
470 assert_eq!(
471 SipPassthroughHeader::request_raw("X-Tenant")
472 .unwrap()
473 .as_str(),
474 "sip_h_X-Tenant"
475 );
476 assert_eq!(
477 SipPassthroughHeader::invite_raw("X-Custom")
478 .unwrap()
479 .as_str(),
480 "sip_i_x_custom"
481 );
482 }
483
484 #[test]
485 fn raw_rejects_newlines() {
486 assert!(SipPassthroughHeader::request_raw("X-Bad\nHeader").is_err());
487 assert!(SipPassthroughHeader::request_raw("X-Bad\rHeader").is_err());
488 assert!(SipPassthroughHeader::request_raw("").is_err());
489 }
490
491 #[test]
494 fn display_matches_as_str() {
495 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
496 assert_eq!(h.to_string(), h.as_str());
497 }
498
499 #[test]
502 fn from_str_request() {
503 let parsed: SipPassthroughHeader = "sip_h_Call-Info"
504 .parse()
505 .unwrap();
506 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
507 assert_eq!(parsed.canonical_name(), "Call-Info");
508 assert_eq!(parsed.as_str(), "sip_h_Call-Info");
509 }
510
511 #[test]
512 fn from_str_invite() {
513 let parsed: SipPassthroughHeader = "sip_i_call_info"
514 .parse()
515 .unwrap();
516 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
517 assert_eq!(parsed.canonical_name(), "Call-Info");
518 assert_eq!(parsed.as_str(), "sip_i_call_info");
519 }
520
521 #[test]
522 fn from_str_invite_p_asserted_identity() {
523 let parsed: SipPassthroughHeader = "sip_i_p_asserted_identity"
524 .parse()
525 .unwrap();
526 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
527 assert_eq!(parsed.canonical_name(), "P-Asserted-Identity");
528 }
529
530 #[test]
531 fn from_str_response() {
532 let parsed: SipPassthroughHeader = "sip_rh_Call-Info"
533 .parse()
534 .unwrap();
535 assert_eq!(parsed.prefix(), SipHeaderPrefix::Response);
536 assert_eq!(parsed.canonical_name(), "Call-Info");
537 }
538
539 #[test]
540 fn from_str_bye() {
541 let parsed: SipPassthroughHeader = "sip_bye_h_Reason"
542 .parse()
543 .unwrap();
544 assert_eq!(parsed.prefix(), SipHeaderPrefix::Bye);
545 assert_eq!(parsed.canonical_name(), "Reason");
546 }
547
548 #[test]
549 fn from_str_no_bye() {
550 let parsed: SipPassthroughHeader = "sip_nobye_h_Reason"
551 .parse()
552 .unwrap();
553 assert_eq!(parsed.prefix(), SipHeaderPrefix::NoBye);
554 assert_eq!(parsed.canonical_name(), "Reason");
555 }
556
557 #[test]
558 fn from_str_unknown_custom_header() {
559 let parsed: SipPassthroughHeader = "sip_h_X-Tenant"
560 .parse()
561 .unwrap();
562 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
563 assert_eq!(parsed.canonical_name(), "X-Tenant");
564 }
565
566 #[test]
567 fn from_str_invite_unknown_custom() {
568 let parsed: SipPassthroughHeader = "sip_i_x_custom"
569 .parse()
570 .unwrap();
571 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
572 assert_eq!(parsed.canonical_name(), "x-custom");
574 }
575
576 #[test]
577 fn from_str_case_insensitive_invite() {
578 let parsed: SipPassthroughHeader = "SIP_I_CALL_INFO"
579 .parse()
580 .unwrap();
581 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
582 assert_eq!(parsed.canonical_name(), "Call-Info");
583 }
584
585 #[test]
586 fn from_str_rejects_no_prefix() {
587 assert!("call_info"
588 .parse::<SipPassthroughHeader>()
589 .is_err());
590 assert!("sip_call_info"
591 .parse::<SipPassthroughHeader>()
592 .is_err());
593 }
594
595 #[test]
596 fn from_str_rejects_empty_suffix() {
597 assert!("sip_h_"
598 .parse::<SipPassthroughHeader>()
599 .is_err());
600 assert!("sip_i_"
601 .parse::<SipPassthroughHeader>()
602 .is_err());
603 }
604
605 #[test]
606 fn from_str_round_trip_all_prefixes() {
607 let headers = [
608 SipPassthroughHeader::invite(SipHeader::CallInfo),
609 SipPassthroughHeader::request(SipHeader::CallInfo),
610 SipPassthroughHeader::response(SipHeader::CallInfo),
611 SipPassthroughHeader::provisional(SipHeader::AlertInfo),
612 SipPassthroughHeader::bye(SipHeader::Reason),
613 SipPassthroughHeader::no_bye(SipHeader::Reason),
614 ];
615 for h in &headers {
616 let parsed: SipPassthroughHeader = h
617 .as_str()
618 .parse()
619 .unwrap();
620 assert_eq!(&parsed, h, "round-trip failed for {}", h.as_str());
621 }
622 }
623
624 #[test]
627 fn prefix_accessor() {
628 assert_eq!(
629 SipPassthroughHeader::invite(SipHeader::Via).prefix(),
630 SipHeaderPrefix::Invite
631 );
632 assert_eq!(
633 SipPassthroughHeader::request(SipHeader::Via).prefix(),
634 SipHeaderPrefix::Request
635 );
636 }
637
638 #[test]
639 fn canonical_name_accessor() {
640 assert_eq!(
641 SipPassthroughHeader::invite(SipHeader::CallInfo).canonical_name(),
642 "Call-Info"
643 );
644 assert_eq!(
645 SipPassthroughHeader::request_raw("X-Tenant")
646 .unwrap()
647 .canonical_name(),
648 "X-Tenant"
649 );
650 }
651
652 #[test]
655 fn is_array_header_invite_multi_valued() {
656 assert!(SipPassthroughHeader::invite(SipHeader::Via).is_array_header());
657 assert!(SipPassthroughHeader::invite(SipHeader::CallInfo).is_array_header());
658 assert!(SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).is_array_header());
659 assert!(SipPassthroughHeader::invite(SipHeader::RecordRoute).is_array_header());
660 }
661
662 #[test]
663 fn is_array_header_invite_single_valued() {
664 assert!(!SipPassthroughHeader::invite(SipHeader::From).is_array_header());
665 assert!(!SipPassthroughHeader::invite(SipHeader::CallId).is_array_header());
666 assert!(!SipPassthroughHeader::invite(SipHeader::ContentType).is_array_header());
667 }
668
669 #[test]
670 fn is_array_header_non_invite_always_false() {
671 assert!(!SipPassthroughHeader::request(SipHeader::Via).is_array_header());
672 assert!(!SipPassthroughHeader::response(SipHeader::CallInfo).is_array_header());
673 }
674
675 #[test]
676 fn is_array_header_raw_unknown() {
677 assert!(!SipPassthroughHeader::invite_raw("X-Custom")
678 .unwrap()
679 .is_array_header());
680 }
681
682 #[test]
685 fn extract_from_sip_message() {
686 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
687 Call-Info: <sip:example.com>;answer-after=0\r\n\
688 \r\n";
689 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
690 assert_eq!(
691 h.extract_from(msg),
692 Some("<sip:example.com>;answer-after=0".into())
693 );
694 }
695
696 #[test]
697 fn extract_from_missing() {
698 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
699 From: Alice <sip:alice@example.com>\r\n\
700 \r\n";
701 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
702 assert_eq!(h.extract_from(msg), None);
703 }
704
705 #[test]
708 fn variable_name_trait() {
709 use crate::variables::VariableName;
710 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
711 let name: &str = VariableName::as_str(&h);
712 assert_eq!(name, "sip_h_Call-Info");
713 }
714}