Module: Switest::Escaper

Defined in:
lib/switest/escaper.rb

Overview

Escapes values for use in FreeSWITCH channel variable strings.

FreeSWITCH originate syntax: var1=value1,var2=value2endpoint

Escaping rules (per FreeSWITCH documentation):

  • Spaces: wrap value in single quotes
  • Commas in regular vars: use ^^ syntax (e.g., ^^:val1:val2)
  • Commas in SIP headers (sip_h_*): escape with backslash (,)
  • Single quotes in quoted values: escape with backslash (')

Constant Summary collapse

QUOTE_CHARS =

Characters that require the value to be quoted

/['\s<>]/
COMMA =

Characters that are problematic in channel variable values

","

Class Method Summary collapse

Class Method Details

.build_var_string(vars = {}, sip_header_vars = {}) ⇒ String

Build a channel variable string for originate command.

Examples:

build_var_string(
  { origination_uuid: "abc-123", origination_caller_id_name: "John Doe" },
  { "X-Custom" => "value,with,commas" }
)
# => "{origination_uuid=abc-123,origination_caller_id_name='John Doe',sip_h_X-Custom=value\\,with\\,commas}"

Parameters:

  • vars (Hash) (defaults to: {})

    Variable name => value pairs

  • sip_header_vars (Hash) (defaults to: {})

    SIP header name => value pairs (will be prefixed with sip_h_)

Returns:

  • (String)

    The formatted variable string, e.g., "var1=val1,var2=val2"



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/switest/escaper.rb', line 108

def build_var_string(vars = {}, sip_header_vars = {})
  vars ||= {}
  sip_header_vars ||= {}
  parts = []

  vars.each do |key, value|
    next if value.nil?
    parts << "#{key}=#{escape_value(value)}"
  end

  sip_header_vars.each do |key, value|
    next if value.nil?
    parts << "sip_h_#{key}=#{escape_header_value(value)}"
  end

  return "" if parts.empty?

  "{#{parts.join(",")}}"
end

.escape_header_value(value) ⇒ String?

Escape a value for use in a SIP header variable (sip_h_, sip_rh_, sip_ph_*).

SIP headers use backslash escaping for commas instead of the ^^ syntax.

Examples:

Value with commas

escape_header_value("one,two,three")
# => "one\\,two\\,three"

Value with spaces and commas

escape_header_value("Hello, World")
# => "'Hello\\, World'"

Parameters:

  • value (String, nil)

    The value to escape

Returns:

  • (String, nil)

    The escaped value



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/switest/escaper.rb', line 78

def escape_header_value(value)
  return value if value.nil?

  str = value.to_s
  return str if str.empty?

  # First, escape commas with backslash (SIP header specific)
  escaped = str.gsub(COMMA, "\\,")

  # Then check if we need quotes for other special chars
  if escaped.match?(QUOTE_CHARS)
    "'" + escaped.gsub("'", "\\\\'") + "'"
  else
    escaped
  end
end

.escape_value(value) ⇒ String?

Escape a value for use in a regular channel variable.

Examples:

Simple value (no escaping needed)

escape_value("+4512345678")
# => "+4512345678"

Value with spaces (quoted)

escape_value("John Doe")
# => "'John Doe'"

Value with commas (uses ^^ delimiter)

escape_value("one,two,three")
# => "^^:one:two:three"

Parameters:

  • value (String, nil)

    The value to escape

Returns:

  • (String, nil)

    The escaped value



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/switest/escaper.rb', line 41

def escape_value(value)
  return value if value.nil?

  str = value.to_s
  return str if str.empty?

  has_comma = str.include?(COMMA)
  needs_quotes = str.match?(QUOTE_CHARS)

  if has_comma
    # Use ^^<delimiter> syntax for values containing commas
    # Pick a delimiter that's not in the string
    delimiter = find_delimiter(str)
    "^^#{delimiter}#{str.gsub(COMMA, delimiter)}"
  elsif needs_quotes
    # Wrap in single quotes and escape any single quotes in the value
    "'" + str.gsub("'", "\\\\'") + "'"
  else
    str
  end
end

.find_delimiter(str) ⇒ String

Find a delimiter character that's not present in the string. Used for the ^^ syntax.

Parameters:

  • str (String)

    The string to check

Returns:

  • (String)

    A single character delimiter



133
134
135
136
137
138
139
140
# File 'lib/switest/escaper.rb', line 133

def find_delimiter(str)
  # Try common delimiters in order of preference
  %w[: | # @ ! ~ ^ ; /].each do |delim|
    return delim unless str.include?(delim)
  end
  # Fallback - this should rarely happen
  ":"
end