Skip to main content

freeswitch_types/commands/variables/
target.rs

1//! Where a dial string is headed: the command carrying it, the block-parser revision reading it,
2//! and the split cutting that command's arguments.
3//!
4//! Line numbers in this module index FreeSWITCH `v1.11.1`
5//! (`c2c59645f6911a76589e5008c4d73349ded44b65`).
6
7use std::borrow::Cow;
8use std::fmt;
9use std::str::FromStr;
10
11use crate::commands::originate::OriginateError;
12use crate::switch_passes::api_argument::{
13    escape_argument, sole_argument, usable_argv_separator, write_escaped, ArgumentPass, ArgvCut,
14};
15use crate::switch_passes::separate::Token;
16use crate::switch_passes::{trace, untrace, Traced};
17use crate::version::FreeswitchVersion;
18
19/// Which command carries a dial string, and therefore how deeply its variable
20/// values must be escaped.
21///
22/// FreeSWITCH escape-processes a bracket block a different number of times
23/// depending on the command it arrived on, and the correct escaping of a value
24/// containing a single quote differs between them with no form that satisfies
25/// both. Naming the carrier is the only way a renderer can be right.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[non_exhaustive]
29pub enum DialStringCarrier {
30    /// `api originate` / `bgapi originate`, whose argument list is split before
31    /// the block itself is parsed. The default for [`Display`](fmt::Display).
32    EslApi,
33    /// A dialplan application such as `bridge`, including via `sendmsg execute`,
34    /// which receives its argument whole.
35    Dialplan,
36}
37
38impl DialStringCarrier {
39    /// Dialplan variable expansion deletes an escaped quote outright, where every
40    /// other pass unescapes it, and drops the first `$` of a `$$`.
41    pub(crate) fn expands(self) -> bool {
42        matches!(self, Self::Dialplan)
43    }
44}
45
46/// Which revision of the switch's bracket-block parser a dial string is rendered
47/// for.
48///
49/// How many escape-consuming passes a block meets is the switch's to change, and
50/// every escaped quote and backslash depends on it. It covers `{}`, `<>` and `[]`
51/// escaping only: the inline action list and the quote pre-scan ahead of a `[]`
52/// block are other parsers. Which revisions a FreeSWITCH version is known to run
53/// is documented in `docs/dial-string-format.md`.
54#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
57#[non_exhaustive]
58pub enum BlockParse {
59    /// Both of the block's own splits, on the separator and on `=`, run the
60    /// escape-consuming cleanup.
61    #[default]
62    PairSplitCleans,
63}
64
65impl BlockParse {
66    const ALL: &'static [Self] = &[Self::PairSplitCleans];
67
68    fn as_str(self) -> &'static str {
69        match self {
70            Self::PairSplitCleans => "pair_split_cleans",
71        }
72    }
73
74    pub(crate) fn cleanup_passes(self) -> u32 {
75        match self {
76            Self::PairSplitCleans => 2,
77        }
78    }
79
80    /// The revision a FreeSWITCH release is vouched to run, or why it cannot be.
81    ///
82    /// A release is vouched for only inside a range whose block parse
83    /// (`switch_event.c:1737`, `switch_utils.c:2804`) was checked at every tag and
84    /// whose line passed the live escaping suite. Anything else, a development
85    /// build included, is refused; name a revision explicitly for it instead.
86    pub fn for_version(version: &FreeswitchVersion) -> Result<Self, UnvouchedVersion> {
87        let version = *version;
88        let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
89        if version.is_dev() {
90            Err(UnvouchedVersion::Dev { version })
91        } else if version < first {
92            Err(UnvouchedVersion::OlderThanVouched { version })
93        } else if version > last {
94            Err(UnvouchedVersion::NewerThanVouched { version })
95        } else {
96            Ok(Self::PairSplitCleans)
97        }
98    }
99}
100
101const VOUCHED_PAIR_SPLIT_CLEANS: (FreeswitchVersion, FreeswitchVersion) = (
102    FreeswitchVersion::new(1, 10, 0),
103    FreeswitchVersion::new(1, 11, 3),
104);
105
106/// A FreeSWITCH version [`BlockParse::for_version`] cannot vouch for.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum UnvouchedVersion {
110    /// A development build, whose version stays the same across commits.
111    Dev {
112        /// The version refused.
113        version: FreeswitchVersion,
114    },
115    /// A release newer than the vouched range.
116    NewerThanVouched {
117        /// The version refused.
118        version: FreeswitchVersion,
119    },
120    /// A release older than the vouched range.
121    OlderThanVouched {
122        /// The version refused.
123        version: FreeswitchVersion,
124    },
125}
126
127impl UnvouchedVersion {
128    /// The version refused.
129    pub fn version(&self) -> FreeswitchVersion {
130        match *self {
131            Self::Dev { version }
132            | Self::NewerThanVouched { version }
133            | Self::OlderThanVouched { version } => version,
134        }
135    }
136}
137
138impl fmt::Display for UnvouchedVersion {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
141        let why = match self {
142            Self::Dev { .. } => "is a development build",
143            Self::NewerThanVouched { .. } => "is newer than any vouched release",
144            Self::OlderThanVouched { .. } => "is older than any vouched release",
145        };
146        write!(
147            f,
148            "FreeSWITCH {} {why}; releases {first} to {last} are vouched for, name a BlockParse explicitly",
149            self.version()
150        )
151    }
152}
153
154impl std::error::Error for UnvouchedVersion {}
155
156impl fmt::Display for BlockParse {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.write_str(self.as_str())
159    }
160}
161
162impl FromStr for BlockParse {
163    type Err = ParseBlockParseError;
164
165    fn from_str(s: &str) -> Result<Self, Self::Err> {
166        Self::ALL
167            .iter()
168            .copied()
169            .find(|revision| {
170                revision
171                    .as_str()
172                    .eq_ignore_ascii_case(s)
173            })
174            .ok_or_else(|| ParseBlockParseError(s.to_string()))
175    }
176}
177
178/// A [`BlockParse`] name this crate does not know. The rejected name is the field.
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct ParseBlockParseError(pub String);
181
182impl fmt::Display for ParseBlockParseError {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        f.write_str("unknown block-parse revision, expected one of:")?;
185        for revision in BlockParse::ALL {
186            write!(f, " {revision}")?;
187        }
188        Ok(())
189    }
190}
191
192impl std::error::Error for ParseBlockParseError {}
193
194/// Where a dial string is headed: the command carrying it, the block-parser
195/// revision of the switch reading it, and the split cutting that command's arguments.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct DialStringTarget {
198    carrier: DialStringCarrier,
199    block_parse: BlockParse,
200    argument: ArgumentPass,
201}
202
203/// Why a [`DialStringTarget`] takes no argument separator.
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205#[non_exhaustive]
206pub enum InvalidArgvSeparator {
207    /// The carrier hands the dial string over whole, so no argument split reads it.
208    WrongCarrier(DialStringCarrier),
209    /// A separator [`DialStringTarget::with_argv_separator`] refuses.
210    Unusable(char),
211}
212
213impl fmt::Display for InvalidArgvSeparator {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            Self::WrongCarrier(carrier) => {
217                write!(f, "the {carrier:?} carrier takes no argument separator")
218            }
219            Self::Unusable(sep) => write!(f, "{sep:?} cannot separate originate's arguments"),
220        }
221    }
222}
223
224impl std::error::Error for InvalidArgvSeparator {}
225
226impl DialStringTarget {
227    /// Target `carrier` at the default [`BlockParse`].
228    pub fn new(carrier: DialStringCarrier) -> Self {
229        Self {
230            carrier,
231            block_parse: BlockParse::default(),
232            argument: ArgumentPass::Blank,
233        }
234    }
235
236    /// Split `originate`'s arguments on `sep`, as a line opening with `^^<sep>` asks the switch.
237    ///
238    /// The dial string is one argument of that split: rendered, it is escaped once for the
239    /// split's cleanup; parsed, that cleanup runs first and a second argument is refused.
240    ///
241    /// Refused: separators the switch splits or unescapes wrongly (space, `\`, `'`, lowercase
242    /// `n r t s`, controls, non-ASCII) and, as policy, `^ " , | [ ] { } < > = :`, which read as
243    /// dial-string grammar or quoting, and every other letter or digit, which reads as part of a word.
244    /// A `'` separator pairs with the next one as quotes.
245    pub fn with_argv_separator(mut self, sep: char) -> Result<Self, InvalidArgvSeparator> {
246        match self.carrier {
247            DialStringCarrier::EslApi => {}
248            DialStringCarrier::Dialplan => {
249                return Err(InvalidArgvSeparator::WrongCarrier(self.carrier))
250            }
251        }
252        if !usable_argv_separator(sep) {
253            return Err(InvalidArgvSeparator::Unusable(sep));
254        }
255        self.argument = ArgumentPass::Char(sep);
256        Ok(self)
257    }
258
259    /// The separator cutting `originate`'s arguments in place of the blank split.
260    pub fn argv_separator(&self) -> Option<char> {
261        match self.argument {
262            ArgumentPass::Char(sep) => Some(sep),
263            ArgumentPass::Blank | ArgumentPass::Consumed => None,
264        }
265    }
266
267    /// `text` escaped as one argument of `originate`'s split, on blanks or on the
268    /// [`argv_separator`](Self::argv_separator), or `None` at the dialplan carrier, which splits none.
269    ///
270    /// An empty text is written `''`, which the split keeps as an argument. On blanks a text
271    /// opening `^^` is written after `''`, since a line opening `^^` names its own separator.
272    pub fn escape_argument<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
273        self.split_delimiter()
274            .and_then(|sep| escape_argument(text, sep))
275    }
276
277    /// `render` at this target, escaped once at its edge for an argv separator's split.
278    pub(crate) fn write_argument(
279        self,
280        f: &mut fmt::Formatter<'_>,
281        render: impl Fn(&mut fmt::Formatter<'_>, Self) -> fmt::Result,
282    ) -> fmt::Result {
283        match self.argv_separator() {
284            Some(sep) => write_escaped(
285                f,
286                sep,
287                RenderedAt {
288                    target: self.inner(),
289                    render: &render,
290                },
291            ),
292            None => render(f, self),
293        }
294    }
295
296    /// A separator [`with_argv_separator`](Self::with_argv_separator) refuses as policy, for
297    /// reading captures the switch accepts under it.
298    #[cfg(test)]
299    pub(crate) fn with_unchecked_argv_separator(mut self, sep: char) -> Self {
300        self.argument = ArgumentPass::Char(sep);
301        self
302    }
303
304    /// The delimiter of `originate`'s argument split still ahead of this target, if any.
305    fn split_delimiter(self) -> Option<char> {
306        match (self.carrier, self.argument) {
307            (DialStringCarrier::EslApi, ArgumentPass::Blank) => Some(' '),
308            (_, ArgumentPass::Char(sep)) => Some(sep),
309            (DialStringCarrier::Dialplan, ArgumentPass::Blank) | (_, ArgumentPass::Consumed) => {
310                None
311            }
312        }
313    }
314
315    /// The one argument `originate`'s split leaves of `text`, or `None` where no split reads it.
316    pub(crate) fn split_argument(self, text: &[Traced]) -> Option<Result<Option<Token>, ArgvCut>> {
317        self.split_delimiter()
318            .map(|delim| sole_argument(text, delim))
319    }
320
321    /// This target inside an argument its split already read.
322    pub(crate) fn inner(mut self) -> Self {
323        if self
324            .split_delimiter()
325            .is_some()
326        {
327            self.argument = ArgumentPass::Consumed;
328        }
329        self
330    }
331
332    /// What this target's argument split leaves of `s`, and the target reading that.
333    pub(crate) fn read_argument(self, s: &str) -> Result<(Cow<'_, str>, Self), OriginateError> {
334        let Some(token) = self.split_argument(&trace(s)) else {
335            return Ok((Cow::Borrowed(s), self));
336        };
337        let token = token.map_err(|ArgvCut| {
338            OriginateError::ParseError("originate's argument split cuts the dial string".into())
339        })?;
340        let argument = token.map_or_else(String::new, |token| untrace(&token.text));
341        Ok((Cow::Owned(argument), self.inner()))
342    }
343
344    /// Target a switch running `block_parse`.
345    pub fn with_block_parse(mut self, block_parse: BlockParse) -> Self {
346        self.block_parse = block_parse;
347        self
348    }
349
350    /// The command carrying the dial string.
351    pub fn carrier(&self) -> DialStringCarrier {
352        self.carrier
353    }
354
355    /// The block-parser revision rendered for.
356    pub fn block_parse(&self) -> BlockParse {
357        self.block_parse
358    }
359
360    /// The argument splits still ahead of this target: one, or none once consumed.
361    pub(crate) fn argument_passes(self) -> u32 {
362        match self.argument {
363            ArgumentPass::Blank | ArgumentPass::Char(_) => 1,
364            ArgumentPass::Consumed => 0,
365        }
366    }
367}
368
369struct RenderedAt<'r, R> {
370    target: DialStringTarget,
371    render: &'r R,
372}
373
374impl<R> fmt::Display for RenderedAt<'_, R>
375where
376    R: Fn(&mut fmt::Formatter<'_>, DialStringTarget) -> fmt::Result,
377{
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        (self.render)(f, self.target)
380    }
381}
382
383impl From<DialStringCarrier> for DialStringTarget {
384    fn from(carrier: DialStringCarrier) -> Self {
385        Self::new(carrier)
386    }
387}