Skip to main content

freeswitch_types/commands/
variables.rs

1//! Channel variable scope and ordered key-value storage for originate commands.
2
3use indexmap::IndexMap;
4use std::fmt;
5use std::str::FromStr;
6
7use super::originate::OriginateError;
8
9/// Scope for channel variables in an originate command.
10///
11/// - `Enterprise` (`<>`) -- applies across all threads (`:_:` separated)
12/// - `Default` (`{}`) -- applies to all channels in this originate
13/// - `Channel` (`[]`) -- applies only to one specific channel
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
17#[non_exhaustive]
18pub enum VariablesType {
19    /// `<>` scope -- applies across all `:_:` separated threads.
20    Enterprise,
21    /// `{}` scope -- applies to all channels in this originate.
22    Default,
23    /// `[]` scope -- applies to one specific channel.
24    Channel,
25}
26
27impl VariablesType {
28    pub(super) fn delimiters(self) -> (char, char) {
29        match self {
30            Self::Enterprise => ('<', '>'),
31            Self::Default => ('{', '}'),
32            Self::Channel => ('[', ']'),
33        }
34    }
35}
36
37/// Ordered set of channel variables with FreeSWITCH escaping.
38///
39/// Backslashes are doubled, commas are escaped with `\,`, single quotes with
40/// `\'`, and values with spaces are wrapped in single quotes. This form
41/// round-trips through [`FromStr`]; what the switch itself decodes depends on
42/// which command carries the block, and is documented in
43/// `docs/dial-string-format.md`.
44///
45/// # Serde format
46///
47/// [`Default`](VariablesType::Default) scope serializes as a flat JSON map:
48/// `{"key": "value", ...}`. Non-default scopes serialize as
49/// `{"scope": "Enterprise", "vars": {"key": "value"}}`.
50/// Deserialization accepts both formats; a flat map implies `Default` scope.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct Variables {
53    vars_type: VariablesType,
54    inner: IndexMap<String, String>,
55}
56
57pub(super) fn escape_value(value: &str) -> String {
58    // The backslash goes first, or the ones introduced below get doubled.
59    let escaped = value
60        .replace('\\', "\\\\")
61        .replace('\'', "\\'")
62        .replace(',', "\\,");
63    if escaped.contains(' ') {
64        format!("'{}'", escaped)
65    } else {
66        escaped
67    }
68}
69
70fn unescape_value(value: &str) -> String {
71    let s = value
72        .strip_prefix('\'')
73        .and_then(|s| s.strip_suffix('\''))
74        .unwrap_or(value);
75
76    let mut out = String::with_capacity(s.len());
77    let mut chars = s.chars();
78    while let Some(c) = chars.next() {
79        if c != '\\' {
80            out.push(c);
81            continue;
82        }
83        match chars.next() {
84            Some(next) => out.push(next),
85            // Hand-written input can end in a backslash that escapes nothing.
86            None => out.push('\\'),
87        }
88    }
89    out
90}
91
92impl Variables {
93    /// Create an empty variable set with the given scope.
94    pub fn new(vars_type: VariablesType) -> Self {
95        Self {
96            vars_type,
97            inner: IndexMap::new(),
98        }
99    }
100
101    /// Create from an existing set of key-value pairs.
102    pub fn with_vars(
103        vars_type: VariablesType,
104        vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
105    ) -> Self {
106        Self {
107            vars_type,
108            inner: vars
109                .into_iter()
110                .map(|(k, v)| (k.into(), v.into()))
111                .collect(),
112        }
113    }
114
115    /// Insert or overwrite a variable.
116    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
117        self.inner
118            .insert(key.into(), value.into());
119    }
120
121    /// Remove a variable by name, returning its value if it existed.
122    pub fn remove(&mut self, key: &str) -> Option<String> {
123        self.inner
124            .shift_remove(key)
125    }
126
127    /// Look up a variable by name.
128    pub fn get(&self, key: &str) -> Option<&str> {
129        self.inner
130            .get(key)
131            .map(|s| s.as_str())
132    }
133
134    /// Whether the set contains no variables.
135    pub fn is_empty(&self) -> bool {
136        self.inner
137            .is_empty()
138    }
139
140    /// Number of variables.
141    pub fn len(&self) -> usize {
142        self.inner
143            .len()
144    }
145
146    /// Variable scope (Enterprise, Default, or Channel).
147    pub fn scope(&self) -> VariablesType {
148        self.vars_type
149    }
150
151    /// Change the variable scope.
152    pub fn set_scope(&mut self, scope: VariablesType) {
153        self.vars_type = scope;
154    }
155
156    /// Iterate over key-value pairs in insertion order.
157    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
158        self.inner
159            .iter()
160            .map(|(k, v)| (k.as_str(), v.as_str()))
161    }
162
163    /// Mutable iterator over key-value pairs in insertion order.
164    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
165        self.inner
166            .iter_mut()
167            .map(|(k, v)| (k.as_str(), v))
168    }
169
170    /// Mutable iterator over values in insertion order.
171    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
172        self.inner
173            .values_mut()
174    }
175}
176
177#[cfg(feature = "serde")]
178impl serde::Serialize for Variables {
179    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
180        if self.vars_type == VariablesType::Default {
181            self.inner
182                .serialize(serializer)
183        } else {
184            use serde::ser::SerializeStruct;
185            let mut s = serializer.serialize_struct("Variables", 2)?;
186            s.serialize_field("scope", &self.vars_type)?;
187            s.serialize_field("vars", &self.inner)?;
188            s.end()
189        }
190    }
191}
192
193#[cfg(feature = "serde")]
194impl<'de> serde::Deserialize<'de> for Variables {
195    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
196        #[derive(serde::Deserialize)]
197        #[serde(untagged)]
198        enum VariablesRepr {
199            Scoped {
200                scope: VariablesType,
201                vars: IndexMap<String, String>,
202            },
203            Flat(IndexMap<String, String>),
204        }
205
206        match VariablesRepr::deserialize(deserializer)? {
207            VariablesRepr::Scoped { scope, vars } => Ok(Self {
208                vars_type: scope,
209                inner: vars,
210            }),
211            VariablesRepr::Flat(map) => Ok(Self {
212                vars_type: VariablesType::Default,
213                inner: map,
214            }),
215        }
216    }
217}
218
219impl fmt::Display for Variables {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        let (open, close) = self
222            .vars_type
223            .delimiters();
224        f.write_fmt(format_args!("{}", open))?;
225        for (i, (key, value)) in self
226            .inner
227            .iter()
228            .enumerate()
229        {
230            if i > 0 {
231                f.write_str(",")?;
232            }
233            write!(f, "{}={}", key, escape_value(value))?;
234        }
235        f.write_fmt(format_args!("{}", close))
236    }
237}
238
239impl FromStr for Variables {
240    type Err = OriginateError;
241
242    fn from_str(s: &str) -> Result<Self, Self::Err> {
243        let s = s.trim();
244        if s.len() < 2 {
245            return Err(OriginateError::ParseError(
246                "variable block too short".into(),
247            ));
248        }
249
250        let (vars_type, inner_str) = match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
251            (b'{', b'}') => (VariablesType::Default, &s[1..s.len() - 1]),
252            (b'<', b'>') => (VariablesType::Enterprise, &s[1..s.len() - 1]),
253            (b'[', b']') => (VariablesType::Channel, &s[1..s.len() - 1]),
254            (open, close) => {
255                return Err(OriginateError::ParseError(format!(
256                    "unknown variable delimiters: {:?}..{:?}",
257                    open as char, close as char
258                )));
259            }
260        };
261
262        let mut inner = IndexMap::new();
263        if !inner_str.is_empty() {
264            if let Some(rest) = inner_str.strip_prefix("^^") {
265                let sep = rest
266                    .chars()
267                    .next()
268                    .ok_or_else(|| {
269                        OriginateError::ParseError("^^ without separator character".into())
270                    })?;
271                let (_, close) = vars_type.delimiters();
272                if sep == close || sep == '=' {
273                    return Err(OriginateError::ParseError(format!(
274                        "invalid ^^ separator: '{sep}'"
275                    )));
276                }
277                let var_str = &rest[sep.len_utf8()..];
278                if !var_str.is_empty() {
279                    for (i, part) in var_str
280                        .split(sep)
281                        .enumerate()
282                    {
283                        let (key, value) = part
284                            .split_once('=')
285                            .ok_or_else(|| {
286                                OriginateError::ParseError(format!("missing = in variable {i}"))
287                            })?;
288                        inner.insert(key.to_string(), value.to_string());
289                    }
290                }
291            } else {
292                for (i, part) in split_unescaped_commas(inner_str)
293                    .into_iter()
294                    .enumerate()
295                {
296                    let (key, value) = part
297                        .split_once('=')
298                        .ok_or_else(|| {
299                            OriginateError::ParseError(format!("missing = in variable {i}"))
300                        })?;
301                    inner.insert(key.to_string(), unescape_value(value));
302                }
303            }
304        }
305
306        Ok(Self { vars_type, inner })
307    }
308}
309
310/// Split on commas that are not escaped by a backslash.
311///
312/// A comma preceded by an odd number of backslashes is escaped (e.g. `\,`).
313/// A comma preceded by an even number of backslashes is a real split point
314/// (e.g. `\\,` means escaped backslash followed by comma delimiter).
315pub(super) fn split_unescaped_commas(s: &str) -> Vec<&str> {
316    let mut parts = Vec::new();
317    let mut start = 0;
318    let bytes = s.as_bytes();
319
320    for i in 0..bytes.len() {
321        if bytes[i] == b',' {
322            let mut backslashes = 0;
323            let mut j = i;
324            while j > 0 && bytes[j - 1] == b'\\' {
325                backslashes += 1;
326                j -= 1;
327            }
328            if backslashes % 2 == 0 {
329                parts.push(&s[start..i]);
330                start = i + 1;
331            }
332        }
333    }
334    parts.push(&s[start..]);
335    parts
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    /// `split_unescaped_commas` reads a comma behind an even number of
343    /// backslashes as a real separator, so a value ending in a backslash has to
344    /// be written with its own backslash escaped or the writer contradicts the
345    /// reader and the block no longer parses.
346    #[test]
347    fn value_with_backslash_round_trips() {
348        for value in [r"a\,b", r"C:\path", r"trailing\", r"\\", r"a\nb"] {
349            let mut vars = Variables::new(VariablesType::Default);
350            vars.insert("k", value);
351            vars.insert("after", "sentinel");
352            let rendered = vars.to_string();
353
354            let back: Variables = rendered
355                .parse()
356                .unwrap_or_else(|e| {
357                    panic!("{value:?} rendered {rendered} and failed to parse: {e}")
358                });
359            assert_eq!(back.get("k"), Some(value), "rendered {rendered}");
360            assert_eq!(
361                back.get("after"),
362                Some("sentinel"),
363                "value {value:?} ate the next variable: {rendered}"
364            );
365        }
366    }
367
368    #[test]
369    fn variables_standard_chars() {
370        let mut vars = Variables::new(VariablesType::Default);
371        vars.insert("test_key", "this_value");
372        let result = vars.to_string();
373        assert!(result.contains("test_key"));
374        assert!(result.contains("this_value"));
375    }
376
377    #[test]
378    fn variables_comma_escaped() {
379        let mut vars = Variables::new(VariablesType::Default);
380        vars.insert("test_key", "this,is,a,value");
381        let result = vars.to_string();
382        assert!(result.contains("\\,"));
383    }
384
385    #[test]
386    fn variables_spaces_quoted() {
387        let mut vars = Variables::new(VariablesType::Default);
388        vars.insert("test_key", "this is a value");
389        let result = vars.to_string();
390        assert_eq!(
391            result
392                .matches('\'')
393                .count(),
394            2
395        );
396    }
397
398    #[test]
399    fn variables_single_quote_escaped() {
400        let mut vars = Variables::new(VariablesType::Default);
401        vars.insert("test_key", "let's_this_be_a_value");
402        let result = vars.to_string();
403        assert!(result.contains("\\'"));
404    }
405
406    #[test]
407    fn variables_enterprise_delimiters() {
408        let mut vars = Variables::new(VariablesType::Enterprise);
409        vars.insert("k", "v");
410        let result = vars.to_string();
411        assert!(result.starts_with('<'));
412        assert!(result.ends_with('>'));
413    }
414
415    #[test]
416    fn variables_channel_delimiters() {
417        let mut vars = Variables::new(VariablesType::Channel);
418        vars.insert("k", "v");
419        let result = vars.to_string();
420        assert!(result.starts_with('['));
421        assert!(result.ends_with(']'));
422    }
423
424    #[test]
425    fn variables_default_delimiters() {
426        let mut vars = Variables::new(VariablesType::Default);
427        vars.insert("k", "v");
428        let result = vars.to_string();
429        assert!(result.starts_with('{'));
430        assert!(result.ends_with('}'));
431    }
432
433    #[test]
434    fn variables_parse_round_trip() {
435        let mut vars = Variables::new(VariablesType::Default);
436        vars.insert("origination_caller_id_number", "9005551212");
437        vars.insert("sip_h_Call-Info", "<url>;meta=123,<uri>");
438        let s = vars.to_string();
439        let parsed: Variables = s
440            .parse()
441            .unwrap();
442        assert_eq!(
443            parsed.get("origination_caller_id_number"),
444            Some("9005551212")
445        );
446        assert_eq!(parsed.get("sip_h_Call-Info"), Some("<url>;meta=123,<uri>"));
447    }
448
449    #[test]
450    fn split_unescaped_commas_basic() {
451        assert_eq!(split_unescaped_commas("a,b,c"), vec!["a", "b", "c"]);
452    }
453
454    #[test]
455    fn split_unescaped_commas_escaped() {
456        assert_eq!(split_unescaped_commas(r"a\,b,c"), vec![r"a\,b", "c"]);
457    }
458
459    #[test]
460    fn split_unescaped_commas_double_backslash() {
461        // \\, = escaped backslash + comma delimiter
462        assert_eq!(split_unescaped_commas(r"a\\,b"), vec![r"a\\", "b"]);
463    }
464
465    #[test]
466    fn split_unescaped_commas_triple_backslash() {
467        // \\\, = escaped backslash + escaped comma (no split)
468        assert_eq!(split_unescaped_commas(r"a\\\,b"), vec![r"a\\\,b"]);
469    }
470
471    #[test]
472    fn variables_caret_caret_separator() {
473        let vars: Variables =
474            "[^^:sip_invite_domain=pbx.example.com:presence_id=1211@pbx.example.com]"
475                .parse()
476                .unwrap();
477        assert_eq!(vars.scope(), VariablesType::Channel);
478        assert_eq!(vars.get("sip_invite_domain"), Some("pbx.example.com"));
479        assert_eq!(vars.get("presence_id"), Some("1211@pbx.example.com"));
480    }
481
482    #[test]
483    fn variables_caret_caret_display_uses_canonical_comma() {
484        let vars: Variables = "[^^:a=1:b=2]"
485            .parse()
486            .unwrap();
487        assert_eq!(vars.to_string(), "[a=1,b=2]");
488    }
489
490    #[test]
491    fn variables_caret_caret_default_scope() {
492        let vars: Variables = "{^^|x=1|y=2}"
493            .parse()
494            .unwrap();
495        assert_eq!(vars.scope(), VariablesType::Default);
496        assert_eq!(vars.get("x"), Some("1"));
497        assert_eq!(vars.get("y"), Some("2"));
498    }
499
500    #[test]
501    fn variables_caret_caret_enterprise_scope() {
502        let vars: Variables = "<^^;a=1;b=2>"
503            .parse()
504            .unwrap();
505        assert_eq!(vars.scope(), VariablesType::Enterprise);
506        assert_eq!(vars.get("a"), Some("1"));
507    }
508
509    #[test]
510    fn variables_caret_caret_no_unescape() {
511        let vars: Variables = r"[^^:key=val\,ue:other=x]"
512            .parse()
513            .unwrap();
514        assert_eq!(vars.get("key"), Some(r"val\,ue"));
515    }
516
517    #[test]
518    fn variables_caret_caret_values_with_commas() {
519        let vars: Variables = "[^^|sip_h_X-Call-Info=<urn:foo>;purpose=bar,<urn:baz>|other=val]"
520            .parse()
521            .unwrap();
522        assert_eq!(
523            vars.get("sip_h_X-Call-Info"),
524            Some("<urn:foo>;purpose=bar,<urn:baz>")
525        );
526        assert_eq!(vars.get("other"), Some("val"));
527    }
528
529    #[test]
530    fn variables_caret_caret_empty_vars() {
531        let vars: Variables = "[^^:]"
532            .parse()
533            .unwrap();
534        assert!(vars.is_empty());
535        assert_eq!(vars.scope(), VariablesType::Channel);
536    }
537
538    #[test]
539    fn variables_caret_caret_missing_separator() {
540        assert!("[^^]"
541            .parse::<Variables>()
542            .is_err());
543    }
544
545    #[test]
546    fn variables_caret_caret_closing_bracket_as_sep() {
547        assert!("[^^]]"
548            .parse::<Variables>()
549            .is_err());
550    }
551
552    #[test]
553    fn variables_caret_caret_equals_as_sep() {
554        assert!("[^^=a=1]"
555            .parse::<Variables>()
556            .is_err());
557    }
558
559    #[test]
560    fn variables_from_str_empty_block() {
561        let result = "{}".parse::<Variables>();
562        assert!(
563            result.is_ok(),
564            "empty variable block should parse successfully"
565        );
566        let vars = result.unwrap();
567        assert!(
568            vars.is_empty(),
569            "parsed empty block should have no variables"
570        );
571    }
572
573    #[test]
574    fn variables_from_str_empty_channel_block() {
575        let result = "[]".parse::<Variables>();
576        assert!(result.is_ok());
577        let vars = result.unwrap();
578        assert!(vars.is_empty());
579        assert_eq!(vars.scope(), VariablesType::Channel);
580    }
581
582    #[test]
583    fn variables_from_str_empty_enterprise_block() {
584        let result = "<>".parse::<Variables>();
585        assert!(result.is_ok());
586        let vars = result.unwrap();
587        assert!(vars.is_empty());
588        assert_eq!(vars.scope(), VariablesType::Enterprise);
589    }
590
591    #[test]
592    fn serde_variables_type() {
593        let json = serde_json::to_string(&VariablesType::Enterprise).unwrap();
594        assert_eq!(json, "\"enterprise\"");
595        let parsed: VariablesType = serde_json::from_str(&json).unwrap();
596        assert_eq!(parsed, VariablesType::Enterprise);
597    }
598
599    #[test]
600    fn serde_variables_flat_default() {
601        let mut vars = Variables::new(VariablesType::Default);
602        vars.insert("key1", "val1");
603        vars.insert("key2", "val2");
604        let json = serde_json::to_string(&vars).unwrap();
605        let parsed: Variables = serde_json::from_str(&json).unwrap();
606        assert_eq!(parsed.scope(), VariablesType::Default);
607        assert_eq!(parsed.get("key1"), Some("val1"));
608        assert_eq!(parsed.get("key2"), Some("val2"));
609    }
610
611    #[test]
612    fn serde_variables_scoped_enterprise() {
613        let mut vars = Variables::new(VariablesType::Enterprise);
614        vars.insert("key1", "val1");
615        let json = serde_json::to_string(&vars).unwrap();
616        assert!(json.contains("\"enterprise\""));
617        let parsed: Variables = serde_json::from_str(&json).unwrap();
618        assert_eq!(parsed.scope(), VariablesType::Enterprise);
619        assert_eq!(parsed.get("key1"), Some("val1"));
620    }
621
622    #[test]
623    fn serde_variables_flat_map_deserializes_as_default() {
624        let json = r#"{"key1":"val1","key2":"val2"}"#;
625        let vars: Variables = serde_json::from_str(json).unwrap();
626        assert_eq!(vars.scope(), VariablesType::Default);
627        assert_eq!(vars.get("key1"), Some("val1"));
628        assert_eq!(vars.get("key2"), Some("val2"));
629    }
630
631    #[test]
632    fn serde_variables_scoped_deserializes() {
633        let json = r#"{"scope":"channel","vars":{"k":"v"}}"#;
634        let vars: Variables = serde_json::from_str(json).unwrap();
635        assert_eq!(vars.scope(), VariablesType::Channel);
636        assert_eq!(vars.get("k"), Some("v"));
637    }
638}