1use 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};
12use crate::variables::names_outgoing_header;
13use crate::wire_safety::contains_header_control;
14
15mod target;
16#[cfg(test)]
17mod tests;
18
19pub use target::{
20 BlockParse, DialStringCarrier, DialStringTarget, InvalidArgvSeparator, ParseBlockParseError,
21 UnvouchedVersion,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
32#[non_exhaustive]
33pub enum VariablesType {
34 Enterprise,
36 Default,
38 Channel,
40}
41
42impl VariablesType {
43 pub(crate) fn delimiters(self) -> (char, char) {
44 match self {
45 Self::Enterprise => ('<', '>'),
46 Self::Default => ('{', '}'),
47 Self::Channel => ('[', ']'),
48 }
49 }
50
51 pub(crate) fn of_block(block: &Block) -> Self {
53 match block.open {
54 '<' => Self::Enterprise,
55 '{' => Self::Default,
56 _ => Self::Channel,
57 }
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Variables {
88 vars_type: VariablesType,
89 inner: IndexMap<String, String>,
90 separator: Option<char>,
94}
95
96fn check_key(key: &str, vars_type: VariablesType) -> Result<(), OriginateError> {
99 let fault = if key.is_empty() {
100 Some("is empty, which the switch installs under no name".to_owned())
101 } else if key.contains('[') {
102 Some(
103 "carries '[', which the switch reads as an array index, installing the value \
104 under the text before it"
105 .to_owned(),
106 )
107 } else if splits_into_threads(key) {
108 Some(format!(
109 "carries the enterprise separator {ENTERPRISE_DELIM}, on which the switch splits \
110 the dial string into threads whatever quoting or escaping surrounds it"
111 ))
112 } else if vars_type == VariablesType::Channel && key.contains('\'') {
113 Some(
114 "carries a single quote in channel scope, which the switch pairs with the next \
115 quote in the dial string before it parses the block"
116 .to_owned(),
117 )
118 } else {
119 unbalanced(key, vars_type.delimiters())
120 };
121 fault.map_or(Ok(()), |fault| {
122 Err(OriginateError::ParseError(format!(
123 "a variable name {fault}"
124 )))
125 })
126}
127
128fn check_representable(
131 key: &str,
132 value: &str,
133 vars_type: VariablesType,
134) -> Result<(), OriginateError> {
135 check_key(key, vars_type)?;
136 if splits_into_threads(value) {
137 return Err(OriginateError::ParseError(format!(
138 "variable {key} carries the enterprise separator {ENTERPRISE_DELIM}, on which \
139 the switch splits the dial string into threads whatever quoting or escaping \
140 surrounds it"
141 )));
142 }
143 if vars_type == VariablesType::Channel && value.contains('\'') {
144 return Err(OriginateError::ParseError(format!(
145 "variable {key} carries a single quote in channel scope: the switch \
146 pairs it with the next quote in the dial string before it parses \
147 the block, whatever escaping precedes either. Use default scope, \
148 or keep the quote out of the value"
149 )));
150 }
151 if names_outgoing_header(key) && contains_header_control(value) {
152 return Err(OriginateError::ParseError(format!(
153 "variable {key} carries a control character: the switch copies it verbatim \
154 into an outgoing SIP header, whose field value cannot hold one"
155 )));
156 }
157 if value.is_empty() {
158 return Err(OriginateError::ParseError(format!(
159 "variable {key} has an empty value: the switch discards such a pair \
160 without logging it, so it cannot be told from an absent variable on \
161 the wire. Give it a value or remove it -- and if its presence was \
162 itself the signal, that signal needs a home outside the dial string"
163 )));
164 }
165
166 unbalanced(value, vars_type.delimiters()).map_or(Ok(()), |fault| {
167 Err(OriginateError::ParseError(format!(
168 "variable {key} {fault}"
169 )))
170 })
171}
172
173fn check_case_collision<'a>(
176 earlier: impl IntoIterator<Item = &'a String>,
177 key: &str,
178 at: usize,
179) -> Result<(), OriginateError> {
180 let collides = earlier
181 .into_iter()
182 .any(|name| name != key && same_header(name, key));
183 match collides {
184 true => Err(OriginateError::ParseError(format!(
185 "variable {at} differs only in case from an earlier name, which the switch \
186 installs as the same variable"
187 ))),
188 false => Ok(()),
189 }
190}
191
192impl Variables {
193 pub fn new(vars_type: VariablesType) -> Self {
195 Self {
196 vars_type,
197 inner: IndexMap::new(),
198 separator: None,
199 }
200 }
201
202 pub fn with_vars(
204 vars_type: VariablesType,
205 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
206 ) -> Self {
207 Self {
208 vars_type,
209 inner: vars
210 .into_iter()
211 .map(|(k, v)| (k.into(), v.into()))
212 .collect(),
213 separator: None,
214 }
215 }
216
217 pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
235 check_separator(sep, self.vars_type)?;
236 if self
237 .inner
238 .keys()
239 .any(|k| k.contains(sep))
240 {
241 return Err(OriginateError::ParseError(format!(
242 "a variable name contains the chosen '{sep}' separator"
243 )));
244 }
245 if let Some((key, _)) = self
246 .inner
247 .iter()
248 .find(|(_, v)| v.contains(sep))
249 {
250 return Err(OriginateError::ParseError(format!(
251 "variable {key} contains the chosen '{sep}' separator"
252 )));
253 }
254 self.separator = Some(sep);
255 Ok(self)
256 }
257
258 pub fn separator(&self) -> Option<char> {
260 self.separator
261 }
262
263 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
265 self.inner
266 .insert(key.into(), value.into());
267 }
268
269 pub fn remove(&mut self, key: &str) -> Option<String> {
271 self.inner
272 .shift_remove(key)
273 }
274
275 pub fn get(&self, key: &str) -> Option<&str> {
277 self.inner
278 .get(key)
279 .map(|s| s.as_str())
280 }
281
282 pub fn is_empty(&self) -> bool {
284 self.inner
285 .is_empty()
286 }
287
288 pub fn len(&self) -> usize {
290 self.inner
291 .len()
292 }
293
294 pub fn scope(&self) -> VariablesType {
296 self.vars_type
297 }
298
299 pub fn set_scope(&mut self, scope: VariablesType) {
301 self.vars_type = scope;
302 }
303
304 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
306 self.inner
307 .iter()
308 .map(|(k, v)| (k.as_str(), v.as_str()))
309 }
310
311 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
313 self.inner
314 .iter_mut()
315 .map(|(k, v)| (k.as_str(), v))
316 }
317
318 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
320 self.inner
321 .values_mut()
322 }
323}
324
325#[cfg(feature = "serde")]
326impl serde::Serialize for Variables {
327 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
328 if self.vars_type == VariablesType::Default
329 && self
330 .separator
331 .is_none()
332 {
333 self.inner
334 .serialize(serializer)
335 } else {
336 use serde::ser::SerializeStruct;
337 let fields = 2 + usize::from(
338 self.separator
339 .is_some(),
340 );
341 let mut s = serializer.serialize_struct("Variables", fields)?;
342 s.serialize_field("scope", &self.vars_type)?;
343 s.serialize_field("vars", &self.inner)?;
344 if let Some(sep) = self.separator {
345 s.serialize_field("separator", &sep)?;
346 }
347 s.end()
348 }
349 }
350}
351
352#[cfg(feature = "serde")]
353impl<'de> serde::Deserialize<'de> for Variables {
354 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
355 #[derive(serde::Deserialize)]
356 #[serde(untagged)]
357 enum VariablesRepr {
358 Scoped {
359 scope: VariablesType,
360 vars: IndexMap<String, String>,
361 #[serde(default)]
362 separator: Option<char>,
363 },
364 Flat(IndexMap<String, String>),
365 }
366
367 let (vars_type, inner, separator) = match VariablesRepr::deserialize(deserializer)? {
368 VariablesRepr::Scoped {
369 scope,
370 vars,
371 separator,
372 } => (scope, vars, separator),
373 VariablesRepr::Flat(map) => (VariablesType::Default, map, None),
374 };
375 for (at, (key, value)) in inner
378 .iter()
379 .enumerate()
380 {
381 check_representable(key, value, vars_type)
382 .and_then(|()| {
383 check_case_collision(
384 inner
385 .keys()
386 .take(at),
387 key,
388 at,
389 )
390 })
391 .map_err(serde::de::Error::custom)?;
392 }
393 let vars = Self {
394 vars_type,
395 inner,
396 separator: None,
397 };
398 match separator {
399 Some(sep) => vars
400 .with_separator(sep)
401 .map_err(serde::de::Error::custom),
402 None => Ok(vars),
403 }
404 }
405}
406
407#[derive(Debug, Clone, Copy)]
410pub struct VariablesDisplay<'a> {
411 vars: &'a Variables,
412 target: DialStringTarget,
413}
414
415impl fmt::Display for VariablesDisplay<'_> {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 self.target
418 .write_argument(f, |f, target| {
419 self.vars
420 .write_for(f, target)
421 })
422 }
423}
424
425impl Variables {
426 pub fn display_for(&self, target: impl Into<DialStringTarget>) -> VariablesDisplay<'_> {
429 VariablesDisplay {
430 vars: self,
431 target: target.into(),
432 }
433 }
434
435 pub(super) fn write_for(
436 &self,
437 f: &mut fmt::Formatter<'_>,
438 target: DialStringTarget,
439 ) -> fmt::Result {
440 let (open, close) = self
441 .vars_type
442 .delimiters();
443 f.write_fmt(format_args!("{}", open))?;
444 if let Some(sep) = self.separator {
445 write!(f, "^^{sep}")?;
446 }
447 let commas_separate = self
450 .separator
451 .is_none_or(|sep| sep == ',');
452 let sep = self
453 .separator
454 .unwrap_or(',');
455 for (i, (key, value)) in self
456 .inner
457 .iter()
458 .enumerate()
459 {
460 if i > 0 {
461 write!(f, "{sep}")?;
462 }
463 let key = escape_text(
464 key,
465 target,
466 EscapedField::Key {
467 scope: self.vars_type,
468 commas_separate,
469 },
470 );
471 let value = escape_value(value, target, commas_separate, self.vars_type);
472 write!(f, "{}={}", key, value)?;
473 }
474 f.write_fmt(format_args!("{}", close))
475 }
476}
477
478impl fmt::Display for Variables {
479 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480 self.write_for(f, DialStringCarrier::EslApi.into())
481 }
482}
483
484impl FromStr for Variables {
485 type Err = OriginateError;
486
487 fn from_str(s: &str) -> Result<Self, Self::Err> {
488 Self::parse_for(s, DialStringCarrier::EslApi)
489 }
490}
491
492const LONE_BLOCK_ENDPOINT: &str = "null/x";
494
495impl Variables {
496 pub fn parse_for(s: &str, target: impl Into<DialStringTarget>) -> Result<Self, OriginateError> {
501 let (argument, target) = target
502 .into()
503 .read_argument(s)?;
504 let s = argument.trim_matches(' ');
505 if s.len() < 2 {
506 return Err(OriginateError::ParseError(
507 "variable block too short".into(),
508 ));
509 }
510 match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
511 (b'{', b'}') | (b'<', b'>') | (b'[', b']') => {}
512 (open, close) => {
513 return Err(OriginateError::ParseError(format!(
514 "unknown variable delimiters: {:?}..{:?}",
515 open as char, close as char
516 )));
517 }
518 }
519 match read_leg(&format!("{s}{LONE_BLOCK_ENDPOINT}"), target)? {
520 (Some(block), endpoint) if endpoint == LONE_BLOCK_ENDPOINT => Self::from_block(&block),
521 _ => Err(OriginateError::ParseError(
522 "the switch ends the block before its last bracket".into(),
523 )),
524 }
525 }
526
527 pub(crate) fn from_block(block: &Block) -> Result<Self, OriginateError> {
529 let vars_type = VariablesType::of_block(block);
530 if block.separator != ',' {
531 check_separator(block.separator, vars_type)?;
532 }
533 if block.rewrites_following_text {
534 return Err(OriginateError::ParseError(
535 "the block's parse rewrites the text after it".into(),
536 ));
537 }
538 let mut inner = IndexMap::new();
539 for (i, pair) in block
540 .pairs
541 .iter()
542 .enumerate()
543 {
544 let value = match &pair.effect {
545 PairEffect::Set(value) => value.as_str(),
546 PairEffect::Cleared | PairEffect::Valueless => "",
547 PairEffect::Ignored => {
548 return Err(OriginateError::ParseError(format!(
549 "missing = in variable {i}"
550 )))
551 }
552 PairEffect::Unreadable => {
553 return Err(OriginateError::ParseError(format!(
554 "variable {i} opens a non-ASCII ^^ separator the switch splits by byte"
555 )))
556 }
557 };
558 let key = pair
559 .key
560 .as_str();
561 check_representable(key, value, vars_type)?;
562 check_case_collision(inner.keys(), key, i)?;
563 if block.separator != ','
564 && (key.contains(block.separator) || value.contains(block.separator))
565 {
566 return Err(OriginateError::ParseError(format!(
567 "variable {i} contains the block's ^^ separator"
568 )));
569 }
570 inner.insert(key.to_owned(), value.to_owned());
571 }
572 Ok(Self {
573 vars_type,
574 inner,
575 separator: None,
576 })
577 }
578}
579
580pub(crate) fn read_leg(
583 text: &str,
584 target: DialStringTarget,
585) -> Result<(Option<Block>, String), OriginateError> {
586 let list = pipeline::read(text, target).map_err(read_error)?;
587 let one_leg = || OriginateError::ParseError("the switch reads more than one leg".into());
588 let [thread] = &list.threads[..] else {
589 return Err(one_leg());
590 };
591 let [group] = &thread.groups[..] else {
592 return Err(one_leg());
593 };
594 let [leg] = &group[..] else {
595 return Err(one_leg());
596 };
597 let mut blocks = list
598 .blocks
599 .iter()
600 .chain(&thread.blocks)
601 .chain(&leg.blocks);
602 match (blocks.next(), blocks.next()) {
603 (Some(_), Some(_)) => Err(OriginateError::ParseError(
604 "an endpoint carries one variable block".into(),
605 )),
606 (block, _) => Ok((
607 block.cloned(),
608 leg.endpoint
609 .clone(),
610 )),
611 }
612}
613
614pub(crate) fn installed_variables(
616 block: Option<&Block>,
617) -> Result<Option<Variables>, OriginateError> {
618 Ok(block
619 .map(Variables::from_block)
620 .transpose()?
621 .filter(|vars| !vars.is_empty()))
622}
623
624pub(crate) fn read_error(error: PipelineError) -> OriginateError {
626 OriginateError::ParseError(
627 match error {
628 PipelineError::Empty => "no endpoint to dial",
629 PipelineError::ArgvSplit => "originate's argument split cuts the dial string",
630 PipelineError::UnclosedBlock { .. } => "a variable block never closes",
631 PipelineError::SplitSeparatorUnreadable => {
632 "a split on a non-ASCII ^^ separator's first byte reaches past its text"
633 }
634 }
635 .into(),
636 )
637}