Module: RubyAsterisk::AGI::Protocol
- Defined in:
- lib/ruby-asterisk/agi/protocol.rb
Overview
Low-level formatter and parser for the FastAGI wire protocol.
Constant Summary collapse
- ENV_LINE =
/\A(agi_\w+):\s*(.*)/- RESULT_RE =
Matches a response carrying a result value: CODE result=VALUE [extra]
/\A(\d{3})\s+result=(\S+)(?:\s+(.*?))?\s*\z/- CODE_RE =
Matches any response line (fallback): CODEmessage
/\A(\d{3})(?:[-\s](.*?))?\s*\z/
Class Method Summary collapse
-
.collect_multiline_error(first_line, first_response, io) ⇒ Hash
Complete a (possibly multi-line) 520 error response.
-
.error?(response) ⇒ Boolean
True when the response code signals an error.
-
.escape_argument(arg) ⇒ String
Escape a single AGI command argument.
-
.format_command(command, *args) ⇒ String
Format an AGI command string ready for sending to Asterisk.
-
.parse_env_block(io) {|line| ... } ⇒ Hash{String=>String}
Read and parse the AGI environment block from an I/O source.
-
.parse_env_line(line) ⇒ Array(String, String)?
Parse a single line from the initial AGI environment block.
-
.parse_response(line) ⇒ Hash
Parse a single raw AGI response line into a frozen Hash.
-
.quote(arg) ⇒ String
Always wrap
argin double quotes, escaping any internal backslashes and double-quotes.
Class Method Details
.collect_multiline_error(first_line, first_response, io) ⇒ Hash
Complete a (possibly multi-line) 520 error response.
Returns first_response unchanged when first_line does not begin with
a continuation marker (+NNN-+). Otherwise reads continuation lines from
io until the NNN <text> closing line, then returns a new frozen
response hash with the accumulated body as :data.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 120 def self.collect_multiline_error(first_line, first_response, io) return first_response unless first_line.match?(/\A\d{3}-/) lines = [first_line.sub(/\A\d{3}-/, '')] while (l = io.gets) chopped = l.chomp if chopped.match?(/\A\d{3}\s/) lines << chopped.sub(/\A\d{3}\s*/, '') break else lines << chopped.sub(/\A\d{3}-/, '') end end body = lines.reject(&:empty?).join(' ') { code: first_response[:code], result: nil, data: body.freeze }.freeze end |
.error?(response) ⇒ Boolean
Returns true when the response code signals an error.
69 70 71 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 69 def self.error?(response) response[:code] >= 500 end |
.escape_argument(arg) ⇒ String
Escape a single AGI command argument.
Plain alphanumeric/dash/underscore/slash tokens are returned as-is. Empty strings and strings containing whitespace, quotes, or backslashes are double-quoted with internal special characters escaped.
34 35 36 37 38 39 40 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 34 def self.escape_argument(arg) return '""' if arg.empty? return arg unless arg.match?(/[\s"\\]/) escaped = arg.gsub('\\') { '\\\\' }.gsub('"') { '\\"' } "\"#{escaped}\"" end |
.format_command(command, *args) ⇒ String
Format an AGI command string ready for sending to Asterisk.
20 21 22 23 24 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 20 def self.format_command(command, *args) parts = [command.to_s] args.each { |a| parts << escape_argument(a.to_s) } "#{parts.join(' ')}\n" end |
.parse_env_block(io) {|line| ... } ⇒ Hash{String=>String}
Read and parse the AGI environment block from an I/O source.
Reads lines via io.gets until a blank line or EOF. Each recognised
agi_key: value line is stored in the returned Hash. Unrecognised lines
are yielded to the optional block (e.g. for debug logging).
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 93 def self.parse_env_block(io) env = {} while (raw = io.gets) chopped = raw.chomp break if chopped.empty? pair = parse_env_line(chopped) if pair env[pair[0]] = pair[1] elsif block_given? yield chopped end end env end |
.parse_env_line(line) ⇒ Array(String, String)?
Parse a single line from the initial AGI environment block.
77 78 79 80 81 82 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 77 def self.parse_env_line(line) m = ENV_LINE.match(line.chomp) return nil unless m [m[1], m[2].strip] end |
.parse_response(line) ⇒ Hash
Parse a single raw AGI response line into a frozen Hash.
57 58 59 60 61 62 63 64 65 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 57 def self.parse_response(line) return { code: 0, result: nil, data: 'Connection closed' }.freeze if line.nil? stripped = line.chomp return { code: 0, result: nil, data: stripped }.freeze if stripped.empty? parse_result_line(stripped) || parse_code_line(stripped) || { code: 0, result: nil, data: stripped }.freeze end |
.quote(arg) ⇒ String
Always wrap arg in double quotes, escaping any internal backslashes
and double-quotes. Use for AGI arguments that Asterisk requires to be
quoted regardless of content (filenames, variable values, messages).
48 49 50 51 |
# File 'lib/ruby-asterisk/agi/protocol.rb', line 48 def self.quote(arg) escaped = arg.to_s.gsub('\\') { '\\\\' }.gsub('"') { '\\"' } "\"#{escaped}\"" end |