Skip to main content

freeswitch_types/commands/variables/
mod.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;
8use crate::switch_passes::brackets::{check_separator, same_header, unbalanced, Block, PairEffect};
9use crate::switch_passes::escape::{escape_text, escape_value, EscapedField};
10use crate::switch_passes::originate_legs::{splits_into_threads, ENTERPRISE_DELIM};
11use crate::switch_passes::{pipeline, PipelineError};
12
13mod target;
14#[cfg(test)]
15mod tests;
16
17pub use target::{
18    BlockParse, DialStringCarrier, DialStringTarget, InvalidArgvSeparator, ParseBlockParseError,
19    UnvouchedVersion,
20};
21
22/// Scope for channel variables in an originate command.
23///
24/// - `Enterprise` (`<>`) -- applies across all threads (`:_:` separated)
25/// - `Default` (`{}`) -- applies to all channels in this originate
26/// - `Channel` (`[]`) -- applies only to one specific channel
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
30#[non_exhaustive]
31pub enum VariablesType {
32    /// `<>` scope -- applies across all `:_:` separated threads.
33    Enterprise,
34    /// `{}` scope -- applies to all channels in this originate.
35    Default,
36    /// `[]` scope -- applies to one specific channel.
37    Channel,
38}
39
40impl VariablesType {
41    pub(crate) fn delimiters(self) -> (char, char) {
42        match self {
43            Self::Enterprise => ('<', '>'),
44            Self::Default => ('{', '}'),
45            Self::Channel => ('[', ']'),
46        }
47    }
48
49    /// The scope of the block the switch parsed.
50    pub(crate) fn of_block(block: &Block) -> Self {
51        match block.open {
52            '<' => Self::Enterprise,
53            '{' => Self::Default,
54            _ => Self::Channel,
55        }
56    }
57}
58
59/// Ordered set of channel variables with FreeSWITCH escaping.
60///
61/// A comma is escaped with `\,`, a backslash and a single quote with as many
62/// backslashes as the [`DialStringTarget`]'s passes consume, a space at either
63/// edge of a value as a `\s` escaped for the same passes, and a value with other
64/// spaces is wrapped in single quotes. This form round-trips through [`FromStr`];
65/// what the switch itself decodes depends on which command carries the block
66/// and which parser revision reads it, documented in `docs/dial-string-format.md`.
67///
68/// A key meets the same passes and is escaped the same way, with `\=` for the `=` split and an
69/// empty `''` ahead of one opening `^^`.
70///
71/// A value naming a variable (`${…}`) is left to the switch, which expands it or
72/// drops it at install unless `origination_nested_vars` is true.
73///
74/// # Serde format
75///
76/// [`Default`](VariablesType::Default) scope with the comma separator
77/// serializes as a flat JSON map: `{"key": "value", ...}`. Anything else
78/// serializes as `{"scope": "enterprise", "vars": {"key": "value"}}`, carrying
79/// a `"separator"` field only when [`with_separator`](Variables::with_separator)
80/// chose one. Deserialization accepts both formats; a flat map implies
81/// `Default` scope and the comma. A `separator` that cannot delimit the block,
82/// or that a key or value already contains, is refused at load.
83// qual:allow(srp, god_struct) reason: "public builder; accessors read disjoint fields"
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct Variables {
86    vars_type: VariablesType,
87    inner: IndexMap<String, String>,
88    /// Set by [`with_separator`](Variables::with_separator). Parsing a `^^`
89    /// block does not populate it: reading one back and writing it out
90    /// canonicalises to a comma, and the other form is asked for explicitly.
91    separator: Option<char>,
92}
93
94/// Reject a key no escaping carries: empty, `:_:`, `[`, a quote in channel scope or an unbalanced
95/// bracket. The key is the offending text, so no error quotes it.
96fn check_key(key: &str, vars_type: VariablesType) -> Result<(), OriginateError> {
97    let fault = if key.is_empty() {
98        Some("is empty, which the switch installs under no name".to_owned())
99    } else if key.contains('[') {
100        Some(
101            "carries '[', which the switch reads as an array index, installing the value \
102             under the text before it"
103                .to_owned(),
104        )
105    } else if splits_into_threads(key) {
106        Some(format!(
107            "carries the enterprise separator {ENTERPRISE_DELIM}, on which the switch splits \
108             the dial string into threads whatever quoting or escaping surrounds it"
109        ))
110    } else if vars_type == VariablesType::Channel && key.contains('\'') {
111        Some(
112            "carries a single quote in channel scope, which the switch pairs with the next \
113             quote in the dial string before it parses the block"
114                .to_owned(),
115        )
116    } else {
117        unbalanced(key, vars_type.delimiters())
118    };
119    fault.map_or(Ok(()), |fault| {
120        Err(OriginateError::ParseError(format!(
121            "a variable name {fault}"
122        )))
123    })
124}
125
126/// Reject a pair no escaping carries: a key [`check_key`] refuses, or a value carrying `:_:`, a
127/// quote in channel scope, nothing or an unbalanced bracket. Each error names why the switch loses it.
128fn check_representable(
129    key: &str,
130    value: &str,
131    vars_type: VariablesType,
132) -> Result<(), OriginateError> {
133    check_key(key, vars_type)?;
134    if splits_into_threads(value) {
135        return Err(OriginateError::ParseError(format!(
136            "variable {key} carries the enterprise separator {ENTERPRISE_DELIM}, on which \
137             the switch splits the dial string into threads whatever quoting or escaping \
138             surrounds it"
139        )));
140    }
141    if vars_type == VariablesType::Channel && value.contains('\'') {
142        return Err(OriginateError::ParseError(format!(
143            "variable {key} carries a single quote in channel scope: the switch \
144             pairs it with the next quote in the dial string before it parses \
145             the block, whatever escaping precedes either. Use default scope, \
146             or keep the quote out of the value"
147        )));
148    }
149    if value.is_empty() {
150        return Err(OriginateError::ParseError(format!(
151            "variable {key} has an empty value: the switch discards such a pair \
152             without logging it, so it cannot be told from an absent variable on \
153             the wire. Give it a value or remove it -- and if its presence was \
154             itself the signal, that signal needs a home outside the dial string"
155        )));
156    }
157
158    unbalanced(value, vars_type.delimiters()).map_or(Ok(()), |fault| {
159        Err(OriginateError::ParseError(format!(
160            "variable {key} {fault}"
161        )))
162    })
163}
164
165/// Reject a key naming, but for ASCII case, one of `earlier`: the block's event replaces a header
166/// by `strcasecmp`, so the two install as one variable. Position `at` names it, never its text.
167fn check_case_collision<'a>(
168    earlier: impl IntoIterator<Item = &'a String>,
169    key: &str,
170    at: usize,
171) -> Result<(), OriginateError> {
172    let collides = earlier
173        .into_iter()
174        .any(|name| name != key && same_header(name, key));
175    match collides {
176        true => Err(OriginateError::ParseError(format!(
177            "variable {at} differs only in case from an earlier name, which the switch \
178             installs as the same variable"
179        ))),
180        false => Ok(()),
181    }
182}
183
184impl Variables {
185    /// Create an empty variable set with the given scope.
186    pub fn new(vars_type: VariablesType) -> Self {
187        Self {
188            vars_type,
189            inner: IndexMap::new(),
190            separator: None,
191        }
192    }
193
194    /// Create from an existing set of key-value pairs.
195    pub fn with_vars(
196        vars_type: VariablesType,
197        vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
198    ) -> Self {
199        Self {
200            vars_type,
201            inner: vars
202                .into_iter()
203                .map(|(k, v)| (k.into(), v.into()))
204                .collect(),
205            separator: None,
206        }
207    }
208
209    /// Separate the pairs with `sep` instead of a comma, emitting the block in
210    /// FreeSWITCH's `^^<sep>` form.
211    ///
212    /// A value expanded from `${...}` in a dialplan is substituted before the
213    /// block is parsed, so no escaping can be inserted into it; choosing a
214    /// separator none of the values contain is the only way such a value can
215    /// carry a comma. The separator is given rather than derived, so a block
216    /// renders the same way whatever its values happen to be that call.
217    ///
218    /// Fails if `sep` cannot delimit this block, or if a key or value already
219    /// present contains it. A pair inserted afterwards is not checked: one carrying
220    /// `sep` splits into a pair nobody wrote, silently, until
221    /// [`insert`](Self::insert) becomes fallible (`docs/next-major.md`).
222    ///
223    /// Refused: space, controls, non-ASCII, `\`, `'` and lowercase `n r t s`, which break the
224    /// switch's split or its escapes; either of the block's brackets, `=` and `^`; `$` and `{`,
225    /// which dialplan expansion reads as a reference across a pair boundary; `|` in a `[]` block.
226    pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
227        check_separator(sep, self.vars_type)?;
228        if self
229            .inner
230            .keys()
231            .any(|k| k.contains(sep))
232        {
233            return Err(OriginateError::ParseError(format!(
234                "a variable name contains the chosen '{sep}' separator"
235            )));
236        }
237        if let Some((key, _)) = self
238            .inner
239            .iter()
240            .find(|(_, v)| v.contains(sep))
241        {
242            return Err(OriginateError::ParseError(format!(
243                "variable {key} contains the chosen '{sep}' separator"
244            )));
245        }
246        self.separator = Some(sep);
247        Ok(self)
248    }
249
250    /// The `^^` separator this block renders with, if one was chosen.
251    pub fn separator(&self) -> Option<char> {
252        self.separator
253    }
254
255    /// Insert or overwrite a variable.
256    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
257        self.inner
258            .insert(key.into(), value.into());
259    }
260
261    /// Remove a variable by name, returning its value if it existed.
262    pub fn remove(&mut self, key: &str) -> Option<String> {
263        self.inner
264            .shift_remove(key)
265    }
266
267    /// Look up a variable by name.
268    pub fn get(&self, key: &str) -> Option<&str> {
269        self.inner
270            .get(key)
271            .map(|s| s.as_str())
272    }
273
274    /// Whether the set contains no variables.
275    pub fn is_empty(&self) -> bool {
276        self.inner
277            .is_empty()
278    }
279
280    /// Number of variables.
281    pub fn len(&self) -> usize {
282        self.inner
283            .len()
284    }
285
286    /// Variable scope (Enterprise, Default, or Channel).
287    pub fn scope(&self) -> VariablesType {
288        self.vars_type
289    }
290
291    /// Change the variable scope.
292    pub fn set_scope(&mut self, scope: VariablesType) {
293        self.vars_type = scope;
294    }
295
296    /// Iterate over key-value pairs in insertion order.
297    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
298        self.inner
299            .iter()
300            .map(|(k, v)| (k.as_str(), v.as_str()))
301    }
302
303    /// Mutable iterator over key-value pairs in insertion order.
304    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
305        self.inner
306            .iter_mut()
307            .map(|(k, v)| (k.as_str(), v))
308    }
309
310    /// Mutable iterator over values in insertion order.
311    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
312        self.inner
313            .values_mut()
314    }
315}
316
317#[cfg(feature = "serde")]
318impl serde::Serialize for Variables {
319    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
320        if self.vars_type == VariablesType::Default
321            && self
322                .separator
323                .is_none()
324        {
325            self.inner
326                .serialize(serializer)
327        } else {
328            use serde::ser::SerializeStruct;
329            let fields = 2 + usize::from(
330                self.separator
331                    .is_some(),
332            );
333            let mut s = serializer.serialize_struct("Variables", fields)?;
334            s.serialize_field("scope", &self.vars_type)?;
335            s.serialize_field("vars", &self.inner)?;
336            if let Some(sep) = self.separator {
337                s.serialize_field("separator", &sep)?;
338            }
339            s.end()
340        }
341    }
342}
343
344#[cfg(feature = "serde")]
345impl<'de> serde::Deserialize<'de> for Variables {
346    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
347        #[derive(serde::Deserialize)]
348        #[serde(untagged)]
349        enum VariablesRepr {
350            Scoped {
351                scope: VariablesType,
352                vars: IndexMap<String, String>,
353                #[serde(default)]
354                separator: Option<char>,
355            },
356            Flat(IndexMap<String, String>),
357        }
358
359        let (vars_type, inner, separator) = match VariablesRepr::deserialize(deserializer)? {
360            VariablesRepr::Scoped {
361                scope,
362                vars,
363                separator,
364            } => (scope, vars, separator),
365            VariablesRepr::Flat(map) => (VariablesType::Default, map, None),
366        };
367        // A config naming a value the wire cannot carry fails at load rather
368        // than on the call it was loaded for.
369        for (at, (key, value)) in inner
370            .iter()
371            .enumerate()
372        {
373            check_representable(key, value, vars_type)
374                .and_then(|()| {
375                    check_case_collision(
376                        inner
377                            .keys()
378                            .take(at),
379                        key,
380                        at,
381                    )
382                })
383                .map_err(serde::de::Error::custom)?;
384        }
385        let vars = Self {
386            vars_type,
387            inner,
388            separator: None,
389        };
390        match separator {
391            Some(sep) => vars
392                .with_separator(sep)
393                .map_err(serde::de::Error::custom),
394            None => Ok(vars),
395        }
396    }
397}
398
399/// Renders a [`Variables`] for one target. Returned by
400/// [`Variables::display_for`].
401#[derive(Debug, Clone, Copy)]
402pub struct VariablesDisplay<'a> {
403    vars: &'a Variables,
404    target: DialStringTarget,
405}
406
407impl fmt::Display for VariablesDisplay<'_> {
408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409        self.target
410            .write_argument(f, |f, target| {
411                self.vars
412                    .write_for(f, target)
413            })
414    }
415}
416
417impl Variables {
418    /// Render for a named carrier or [`DialStringTarget`], rather than the
419    /// [`DialStringCarrier::EslApi`] default that [`Display`](fmt::Display) uses.
420    pub fn display_for(&self, target: impl Into<DialStringTarget>) -> VariablesDisplay<'_> {
421        VariablesDisplay {
422            vars: self,
423            target: target.into(),
424        }
425    }
426
427    pub(super) fn write_for(
428        &self,
429        f: &mut fmt::Formatter<'_>,
430        target: DialStringTarget,
431    ) -> fmt::Result {
432        let (open, close) = self
433            .vars_type
434            .delimiters();
435        f.write_fmt(format_args!("{}", open))?;
436        if let Some(sep) = self.separator {
437            write!(f, "^^{sep}")?;
438        }
439        // A chosen separator carries the values that a comma would have needed
440        // escaping for, so only a block splitting on commas escapes them.
441        let commas_separate = self
442            .separator
443            .is_none_or(|sep| sep == ',');
444        let sep = self
445            .separator
446            .unwrap_or(',');
447        for (i, (key, value)) in self
448            .inner
449            .iter()
450            .enumerate()
451        {
452            if i > 0 {
453                write!(f, "{sep}")?;
454            }
455            let key = escape_text(
456                key,
457                target,
458                EscapedField::Key {
459                    scope: self.vars_type,
460                    commas_separate,
461                },
462            );
463            let value = escape_value(value, target, commas_separate, self.vars_type);
464            write!(f, "{}={}", key, value)?;
465        }
466        f.write_fmt(format_args!("{}", close))
467    }
468}
469
470impl fmt::Display for Variables {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        self.write_for(f, DialStringCarrier::EslApi.into())
473    }
474}
475
476impl FromStr for Variables {
477    type Err = OriginateError;
478
479    fn from_str(s: &str) -> Result<Self, Self::Err> {
480        Self::parse_for(s, DialStringCarrier::EslApi)
481    }
482}
483
484/// The endpoint text a lone block is read ahead of, so the switch's passes see a whole leg.
485const LONE_BLOCK_ENDPOINT: &str = "null/x";
486
487impl Variables {
488    /// Parse a block written for a named carrier or [`DialStringTarget`], as the switch installs
489    /// it there: the pairs are read through a port of every pass that target applies, so a block
490    /// [`display_for`](Self::display_for) wrote at the same target reads back as built.
491    /// [`FromStr`] uses the [`DialStringCarrier::EslApi`] default of [`Display`](fmt::Display).
492    pub fn parse_for(s: &str, target: impl Into<DialStringTarget>) -> Result<Self, OriginateError> {
493        let (argument, target) = target
494            .into()
495            .read_argument(s)?;
496        let s = argument.trim_matches(' ');
497        if s.len() < 2 {
498            return Err(OriginateError::ParseError(
499                "variable block too short".into(),
500            ));
501        }
502        match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
503            (b'{', b'}') | (b'<', b'>') | (b'[', b']') => {}
504            (open, close) => {
505                return Err(OriginateError::ParseError(format!(
506                    "unknown variable delimiters: {:?}..{:?}",
507                    open as char, close as char
508                )));
509            }
510        }
511        match read_leg(&format!("{s}{LONE_BLOCK_ENDPOINT}"), target)? {
512            (Some(block), endpoint) if endpoint == LONE_BLOCK_ENDPOINT => Self::from_block(&block),
513            _ => Err(OriginateError::ParseError(
514                "the switch ends the block before its last bracket".into(),
515            )),
516        }
517    }
518
519    /// The variables `block` installs, refusing what no render of this crate delivers.
520    pub(crate) fn from_block(block: &Block) -> Result<Self, OriginateError> {
521        let vars_type = VariablesType::of_block(block);
522        if block.separator != ',' {
523            check_separator(block.separator, vars_type)?;
524        }
525        if block.rewrites_following_text {
526            return Err(OriginateError::ParseError(
527                "the block's parse rewrites the text after it".into(),
528            ));
529        }
530        let mut inner = IndexMap::new();
531        for (i, pair) in block
532            .pairs
533            .iter()
534            .enumerate()
535        {
536            let value = match &pair.effect {
537                PairEffect::Set(value) => value.as_str(),
538                PairEffect::Cleared | PairEffect::Valueless => "",
539                PairEffect::Ignored => {
540                    return Err(OriginateError::ParseError(format!(
541                        "missing = in variable {i}"
542                    )))
543                }
544                PairEffect::Unreadable => {
545                    return Err(OriginateError::ParseError(format!(
546                        "variable {i} opens a non-ASCII ^^ separator the switch splits by byte"
547                    )))
548                }
549            };
550            let key = pair
551                .key
552                .as_str();
553            check_representable(key, value, vars_type)?;
554            check_case_collision(inner.keys(), key, i)?;
555            if block.separator != ','
556                && (key.contains(block.separator) || value.contains(block.separator))
557            {
558                return Err(OriginateError::ParseError(format!(
559                    "variable {i} contains the block's ^^ separator"
560                )));
561            }
562            inner.insert(key.to_owned(), value.to_owned());
563        }
564        Ok(Self {
565            vars_type,
566            inner,
567            separator: None,
568        })
569    }
570}
571
572/// The one leg the switch reads of `text` at `target`: its one block, if any, and the endpoint
573/// text after it.
574pub(crate) fn read_leg(
575    text: &str,
576    target: DialStringTarget,
577) -> Result<(Option<Block>, String), OriginateError> {
578    let list = pipeline::read(text, target).map_err(read_error)?;
579    let one_leg = || OriginateError::ParseError("the switch reads more than one leg".into());
580    let [thread] = &list.threads[..] else {
581        return Err(one_leg());
582    };
583    let [group] = &thread.groups[..] else {
584        return Err(one_leg());
585    };
586    let [leg] = &group[..] else {
587        return Err(one_leg());
588    };
589    let mut blocks = list
590        .blocks
591        .iter()
592        .chain(&thread.blocks)
593        .chain(&leg.blocks);
594    match (blocks.next(), blocks.next()) {
595        (Some(_), Some(_)) => Err(OriginateError::ParseError(
596            "an endpoint carries one variable block".into(),
597        )),
598        (block, _) => Ok((
599            block.cloned(),
600            leg.endpoint
601                .clone(),
602        )),
603    }
604}
605
606/// The variables `block` installs, or `None` for no block or one installing nothing.
607pub(crate) fn installed_variables(
608    block: Option<&Block>,
609) -> Result<Option<Variables>, OriginateError> {
610    Ok(block
611        .map(Variables::from_block)
612        .transpose()?
613        .filter(|vars| !vars.is_empty()))
614}
615
616/// Why the switch reads no dial string from the text.
617pub(crate) fn read_error(error: PipelineError) -> OriginateError {
618    OriginateError::ParseError(
619        match error {
620            PipelineError::Empty => "no endpoint to dial",
621            PipelineError::ArgvSplit => "originate's argument split cuts the dial string",
622            PipelineError::UnclosedBlock { .. } => "a variable block never closes",
623            PipelineError::SplitSeparatorUnreadable => {
624                "a split on a non-ASCII ^^ separator's first byte reaches past its text"
625            }
626        }
627        .into(),
628    )
629}