From eec1c193d9043622bf27e162dfb8ffb248ae0caa Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Thu, 12 Jul 2018 14:07:56 -0700 Subject: (MODULE-7443) Safely deserialize stringified array This ports PUP-8974, and the related follow-up maintenance commits from the Puppet repo. The augeas provider used Kernel#eval to convert stringified arrays to Ruby arrays. For example, it extracted the array part of the "clause" below: onlyif => 'values HostKey == ["/etc/ssh/ssh_host_rsa_key"]' and called Kernel#eval with '["/etc/ssh/ssh_host_rsa_key"]'. Using eval is bad because it executes arbitrary code. This commit changes the provider to convert the comma delimited string to a Ruby array. This mostly maintains the functionality of the original Kernel#eval (minus running arbitrary code) except for no longer handling the \M-x, \M-\C-x, \M-\cx, \c\M-x, \c?, and \C-? escape sequences in double-quoted strings, and \u{nnnn ...} is more lenient about whitespace. --- lib/puppet/provider/augeas/augeas.rb | 180 ++++++++++++++++++++++++++++++- spec/unit/provider/augeas/augeas_spec.rb | 65 +++++++++++ 2 files changed, 243 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index 05183e5..b64b0b3 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -18,7 +18,6 @@ require 'strscan' require 'puppet/util' require 'puppet/util/diff' require 'puppet/util/package' -require 'json' Puppet::Type.type(:augeas).provide(:augeas) do include Puppet::Util @@ -574,7 +573,184 @@ Puppet::Type.type(:augeas).provide(:augeas) do # rubocop:enable Style/GuardClause def to_array(string) - JSON.parse(string.tr("'", '"')) + s = StringScanner.new(string) + match = array_open(s) + raise "Unable to parse array. Unexpected character at: #{s.rest}" if match.nil? + + array_content = array_values(s) + + match = array_close(s) + raise "Unable to parse array. Unexpected character at: #{s.rest}" if match.nil? + + array_content end private :to_array + + def array_open(scanner) + scanner.scan(%r{\s*\[\s*}) + end + private :array_open + + def array_close(scanner) + scanner.scan(%r{\s*\]\s*}) + end + private :array_close + + def array_separator(scanner) + scanner.scan(%r{\s*,\s*}) + end + private :array_separator + + def single_quote_unescaped_char(scanner) + scanner.scan(%r{[^'\\]}) + end + private :single_quote_unescaped_char + + def single_quote_escaped_char(scanner) + scanner.scan(%r{\\(['\\])}) && scanner[1] + end + private :single_quote_escaped_char + + def single_quote_char(scanner) + single_quote_escaped_char(scanner) || single_quote_unescaped_char(scanner) + end + private :single_quote_char + + def double_quote_unescaped_char(scanner) + scanner.scan(%r{[^"\\]}) + end + private :double_quote_unescaped_char + + # This handles the possible Ruby escape sequences in double-quoted strings, + # except for \M-x, \M-\C-x, \M-\cx, \c\M-x, \c?, and \C-?. The full list of + # escape sequences, and their meanings is taken from: + # https://github.com/ruby/ruby/blob/90fdfec11a4a42653722e2ce2a672d6e87a57b8e/doc/syntax/literals.rdoc#strings + def double_quote_escaped_char(scanner) + match = scanner.scan(%r{\\(["\\abtnvfres0-7xu])}) + return nil if match.nil? + + case scanner[1] + when '\\' then return '\\' + when '"' then return '"' + when 'a' then return "\a" + when 'b' then return "\b" + when 't' then return "\t" + when 'n' then return "\n" + when 'v' then return "\v" + when 'f' then return "\f" + when 'r' then return "\r" + when 'e' then return "\e" + when 's' then return "\s" + when %r{[0-7]} + octal_character = scanner[1] + other_digits = scanner.scan(%r{[0-7]{1,2}}) + octal_character << other_digits unless other_digits.nil? + + return octal_character.to_i(8).chr + when 'x' + hex_character = scanner.scan(%r{[0-9a-fA-F]{1,2}}) + return nil if hex_character.nil? + + hex_character.to_i(16).chr + when 'u' + return unicode_short_hex_character(scanner) || unicode_long_hex_characters(scanner) + else + # Not a valid escape sequence as far as we're concerned. + return nil + end + end + private :double_quote_escaped_char + + def unicode_short_hex_character(scanner) + unicode_character = scanner.scan(%r{[0-9a-fA-F]{4}}) + return nil if unicode_character.nil? + + [unicode_character.hex].pack 'U' + end + private :unicode_short_hex_character + + def unicode_long_hex_characters(scanner) + unicode_string = '' + return nil unless scanner.scan(%r|{|) + + loop do + char = scanner.scan(%r{[0-9a-fA-F]{1,6}}) + break if char.nil? + unicode_string << [char.hex].pack('U') + + separator = scanner.scan(%r{\s}) + break if separator.nil? + end + + return nil if scanner.scan(%r|}|).nil? || unicode_string.empty? + + unicode_string + end + private :unicode_long_hex_characters + + def single_quoted_string(scanner) + quoted_string = '' + + match = scanner.scan(%r{'}) + return nil if match.nil? + + loop do + match = single_quote_char(scanner) + break if match.nil? + + quoted_string << match + end + + match = scanner.scan(%r{'}) + return quoted_string if match + + nil + end + private :single_quoted_string + + def double_quote_char(scanner) + double_quote_escaped_char(scanner) || double_quote_unescaped_char(scanner) + end + private :double_quote_char + + def double_quoted_string(scanner) + quoted_string = '' + + match = scanner.scan(%r{"}) + return nil if match.nil? + + loop do + match = double_quote_char(scanner) + break if match.nil? + + quoted_string << match + end + + match = scanner.scan(%r{"}) + return quoted_string if match + + nil + end + private :double_quoted_string + + def quoted_string(scanner) + single_quoted_string(scanner) || double_quoted_string(scanner) + end + private :quoted_string + + def array_values(scanner) + values = [] + + loop do + match = quoted_string(scanner) + break if match.nil? + values << match + + match = array_separator(scanner) + break if match.nil? + end + + values + end + private :array_values end diff --git a/spec/unit/provider/augeas/augeas_spec.rb b/spec/unit/provider/augeas/augeas_spec.rb index 6166140..180f89c 100644 --- a/spec/unit/provider/augeas/augeas_spec.rb +++ b/spec/unit/provider/augeas/augeas_spec.rb @@ -262,6 +262,49 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do command = ['values', 'fake value', "== ['set', 'of', 'values']"] expect(provider.process_values(command)).to eq(true) end + it 'returns true for an array match with double quotes and spaces' do + command = ['values', 'fake value', '== [ "set" , "of" , "values" ] '] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with internally escaped single quotes' do + provider.aug.stubs(:match).returns(['set', "o'values", 'here']) + provider.aug.stubs(:get).returns('set').then.returns("o'values").then.returns('here') + command = ['values', 'fake value', "== [ 'set', 'o\\'values', 'here']"] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with octal character sequences' do + command = ['values', 'fake value', '== ["\\x73et", "of", "values"]'] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with hex character sequences' do + command = ['values', 'fake value', '== ["\\163et", "of", "values"]'] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with short unicode escape sequences' do + command = ['values', 'fake value', '== ["\\u0073et", "of", "values"]'] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with single character long unicode escape sequences' do + command = ['values', 'fake value', '== ["\\u{0073}et", "of", "values"]'] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with multi-character long unicode escape sequences' do + command = ['values', 'fake value', '== ["\\u{0073 0065 0074}", "of", "values"]'] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an array match with literal backslashes' do + provider.aug.stubs(:match).returns(['set', 'o\\values', 'here']) + provider.aug.stubs(:get).returns('set').then.returns('o\\values').then.returns('here') + command = ['values', 'fake value', '== [ "set", "o\\\\values", "here"]'] + expect(provider.process_values(command)).to eq(true) + end it 'returns false for an array non match' do command = ['values', 'fake value', "== ['this', 'should', 'not', 'match']"] @@ -277,6 +320,18 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do command = ['values', 'fake value', "!= ['this', 'should', 'not', 'match']"] expect(provider.process_values(command)).to eq(true) end + + it 'returns true for an array non match with double quotes and spaces' do + command = ['values', 'fake value', '!= [ "this" , "should" ,"not", "match" ] '] + expect(provider.process_values(command)).to eq(true) + end + + it 'returns true for an empty array match' do + provider.aug.stubs(:match).returns([]) + provider.aug.stubs(:get) + command = ['values', 'fake value', '== []'] + expect(provider.process_values(command)).to eq(true) + end end describe 'match filters' do @@ -322,6 +377,11 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do expect(provider.process_match(command)).to eq(true) end + it 'returns true for an array match with double quotes and spaces' do + command = ['match', 'fake value', '== [ "set" , "of" , "values" ] '] + expect(provider.process_match(command)).to eq(true) + end + it 'returns false for an array non match' do command = ['match', 'fake value', "== ['this', 'should', 'not', 'match']"] expect(provider.process_match(command)).to eq(false) @@ -336,6 +396,11 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do command = ['match', 'fake value', "!= ['this', 'should', 'not', 'match']"] expect(provider.process_match(command)).to eq(true) end + + it 'returns true for an array non match with double quotes and spaces' do + command = ['match', 'fake value', '!= [ "this" , "should" ,"not", "match" ] '] + expect(provider.process_match(command)).to eq(true) + end end describe 'need to run' do -- cgit v1.2.3 From be1d15019ddd7b51965fa204f6e837f83297e7c6 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 16 Jul 2018 09:09:30 -0700 Subject: (maint) Move array parser logic into a util module --- lib/puppet/provider/augeas/augeas.rb | 192 +---------------------- lib/puppet_x/augeas/util/parser.rb | 215 ++++++++++++++++++++++++++ spec/unit/puppet_x/augeas/util/parser_spec.rb | 110 +++++++++++++ 3 files changed, 331 insertions(+), 186 deletions(-) create mode 100644 lib/puppet_x/augeas/util/parser.rb create mode 100644 spec/unit/puppet_x/augeas/util/parser_spec.rb diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index b64b0b3..ee52ee6 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -18,11 +18,13 @@ require 'strscan' require 'puppet/util' require 'puppet/util/diff' require 'puppet/util/package' +require 'puppet_x/augeas/util/parser' Puppet::Type.type(:augeas).provide(:augeas) do include Puppet::Util include Puppet::Util::Diff include Puppet::Util::Package + include PuppetX::Augeas::Util::Parser confine feature: :augeas @@ -280,7 +282,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do when '==' begin arg = clause_array.shift - new_array = to_array(arg) + new_array = parse_to_array(arg) return_value = (values == new_array) rescue fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') }) @@ -288,7 +290,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do when '!=' begin arg = clause_array.shift - new_array = to_array(arg) + new_array = parse_to_array(arg) return_value = (values != new_array) rescue fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') }) @@ -336,7 +338,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do when '==' begin arg = clause_array.shift - new_array = to_array(arg) + new_array = parse_to_array(arg) return_value = (result == new_array) rescue fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') }) @@ -344,7 +346,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do when '!=' begin arg = clause_array.shift - new_array = to_array(arg) + new_array = parse_to_array(arg) return_value = (result != new_array) rescue fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') }) @@ -571,186 +573,4 @@ Puppet::Type.type(:augeas).provide(:augeas) do end end # rubocop:enable Style/GuardClause - - def to_array(string) - s = StringScanner.new(string) - match = array_open(s) - raise "Unable to parse array. Unexpected character at: #{s.rest}" if match.nil? - - array_content = array_values(s) - - match = array_close(s) - raise "Unable to parse array. Unexpected character at: #{s.rest}" if match.nil? - - array_content - end - private :to_array - - def array_open(scanner) - scanner.scan(%r{\s*\[\s*}) - end - private :array_open - - def array_close(scanner) - scanner.scan(%r{\s*\]\s*}) - end - private :array_close - - def array_separator(scanner) - scanner.scan(%r{\s*,\s*}) - end - private :array_separator - - def single_quote_unescaped_char(scanner) - scanner.scan(%r{[^'\\]}) - end - private :single_quote_unescaped_char - - def single_quote_escaped_char(scanner) - scanner.scan(%r{\\(['\\])}) && scanner[1] - end - private :single_quote_escaped_char - - def single_quote_char(scanner) - single_quote_escaped_char(scanner) || single_quote_unescaped_char(scanner) - end - private :single_quote_char - - def double_quote_unescaped_char(scanner) - scanner.scan(%r{[^"\\]}) - end - private :double_quote_unescaped_char - - # This handles the possible Ruby escape sequences in double-quoted strings, - # except for \M-x, \M-\C-x, \M-\cx, \c\M-x, \c?, and \C-?. The full list of - # escape sequences, and their meanings is taken from: - # https://github.com/ruby/ruby/blob/90fdfec11a4a42653722e2ce2a672d6e87a57b8e/doc/syntax/literals.rdoc#strings - def double_quote_escaped_char(scanner) - match = scanner.scan(%r{\\(["\\abtnvfres0-7xu])}) - return nil if match.nil? - - case scanner[1] - when '\\' then return '\\' - when '"' then return '"' - when 'a' then return "\a" - when 'b' then return "\b" - when 't' then return "\t" - when 'n' then return "\n" - when 'v' then return "\v" - when 'f' then return "\f" - when 'r' then return "\r" - when 'e' then return "\e" - when 's' then return "\s" - when %r{[0-7]} - octal_character = scanner[1] - other_digits = scanner.scan(%r{[0-7]{1,2}}) - octal_character << other_digits unless other_digits.nil? - - return octal_character.to_i(8).chr - when 'x' - hex_character = scanner.scan(%r{[0-9a-fA-F]{1,2}}) - return nil if hex_character.nil? - - hex_character.to_i(16).chr - when 'u' - return unicode_short_hex_character(scanner) || unicode_long_hex_characters(scanner) - else - # Not a valid escape sequence as far as we're concerned. - return nil - end - end - private :double_quote_escaped_char - - def unicode_short_hex_character(scanner) - unicode_character = scanner.scan(%r{[0-9a-fA-F]{4}}) - return nil if unicode_character.nil? - - [unicode_character.hex].pack 'U' - end - private :unicode_short_hex_character - - def unicode_long_hex_characters(scanner) - unicode_string = '' - return nil unless scanner.scan(%r|{|) - - loop do - char = scanner.scan(%r{[0-9a-fA-F]{1,6}}) - break if char.nil? - unicode_string << [char.hex].pack('U') - - separator = scanner.scan(%r{\s}) - break if separator.nil? - end - - return nil if scanner.scan(%r|}|).nil? || unicode_string.empty? - - unicode_string - end - private :unicode_long_hex_characters - - def single_quoted_string(scanner) - quoted_string = '' - - match = scanner.scan(%r{'}) - return nil if match.nil? - - loop do - match = single_quote_char(scanner) - break if match.nil? - - quoted_string << match - end - - match = scanner.scan(%r{'}) - return quoted_string if match - - nil - end - private :single_quoted_string - - def double_quote_char(scanner) - double_quote_escaped_char(scanner) || double_quote_unescaped_char(scanner) - end - private :double_quote_char - - def double_quoted_string(scanner) - quoted_string = '' - - match = scanner.scan(%r{"}) - return nil if match.nil? - - loop do - match = double_quote_char(scanner) - break if match.nil? - - quoted_string << match - end - - match = scanner.scan(%r{"}) - return quoted_string if match - - nil - end - private :double_quoted_string - - def quoted_string(scanner) - single_quoted_string(scanner) || double_quoted_string(scanner) - end - private :quoted_string - - def array_values(scanner) - values = [] - - loop do - match = quoted_string(scanner) - break if match.nil? - values << match - - match = array_separator(scanner) - break if match.nil? - end - - values - end - private :array_values end diff --git a/lib/puppet_x/augeas/util/parser.rb b/lib/puppet_x/augeas/util/parser.rb new file mode 100644 index 0000000..1abf42f --- /dev/null +++ b/lib/puppet_x/augeas/util/parser.rb @@ -0,0 +1,215 @@ +# rubocop:disable Style/Documentation +module PuppetX; end +module PuppetX::Augeas; end +module PuppetX::Augeas::Util; end +# rubocop:enable Style/Documentation + +# Container for helpers to parse user provided data contained in manifests. +module PuppetX::Augeas::Util::Parser + TOKEN_ARRAY_CLOSE = %r{\s*\]\s*} + TOKEN_ARRAY_OPEN = %r{\s*\[\s*} + TOKEN_ARRAY_SEPARATOR = %r{\s*,\s*} + TOKEN_CLOSE_CURLY = %r|}| + TOKEN_DOUBLE_QUOTE = %r{"} + TOKEN_DOUBLE_QUOTE_ESCAPED_CHAR = %r{\\(["\\abtnvfres0-7xu])} + TOKEN_DOUBLE_QUOTE_UNESCAPED_CHAR = %r{[^"\\]} + TOKEN_HEX_CHAR = %r{[0-9a-fA-F]{1,2}} + TOKEN_OCTAL_CHAR = %r{[0-7]{1,3}} + TOKEN_OPEN_CURLY = %r|{| + TOKEN_SINGLE_QUOTE = %r{'} + TOKEN_SINGLE_QUOTE_ESCAPED_CHAR = %r{\\(['\\])} + TOKEN_SINGLE_QUOTE_UNESCAPED_CHAR = %r{[^'\\]} + TOKEN_SPACE = %r{\s} + TOKEN_UNICODE_LONG_HEX_CHAR = %r{[0-9a-fA-F]{1,6}} + TOKEN_UNICODE_SHORT_HEX_CHAR = %r{[0-9a-fA-F]{4}} + + # Parse a string into the (nearly) equivalent Ruby array. This only handles + # arrays with string members (double-, or single-quoted), and does not + # support the full quite of escape sequences that Ruby allows in + # double-quoted strings. + # + # @param [String] The string to be parsed. + # @return [Array] The parsed array elements, including handling any + # escape sequences. + def parse_to_array(string) + s = StringScanner.new(string) + match = array_open(s) + raise "Unexpected character in array at: #{s.rest}" if match.nil? + + array_content = array_values(s) + + match = array_close(s) + raise "Unexpected character in array at: #{s.rest}" if match.nil? || !s.empty? + + array_content + end + + def array_open(scanner) + scanner.scan(TOKEN_ARRAY_OPEN) + end + private :array_open + + def array_close(scanner) + scanner.scan(TOKEN_ARRAY_CLOSE) + end + private :array_close + + def array_separator(scanner) + scanner.scan(TOKEN_ARRAY_SEPARATOR) + end + private :array_separator + + def single_quote_unescaped_char(scanner) + scanner.scan(TOKEN_SINGLE_QUOTE_UNESCAPED_CHAR) + end + private :single_quote_unescaped_char + + def single_quote_escaped_char(scanner) + scanner.scan(TOKEN_SINGLE_QUOTE_ESCAPED_CHAR) && scanner[1] + end + private :single_quote_escaped_char + + def single_quote_char(scanner) + single_quote_escaped_char(scanner) || single_quote_unescaped_char(scanner) + end + private :single_quote_char + + def double_quote_unescaped_char(scanner) + scanner.scan(TOKEN_DOUBLE_QUOTE_UNESCAPED_CHAR) + end + private :double_quote_unescaped_char + + # This handles the possible Ruby escape sequences in double-quoted strings, + # except for \M-x, \M-\C-x, \M-\cx, \c\M-x, \c?, and \C-?. The full list of + # escape sequences, and their meanings is taken from: + # https://github.com/ruby/ruby/blob/90fdfec11a4a42653722e2ce2a672d6e87a57b8e/doc/syntax/literals.rdoc#strings + def double_quote_escaped_char(scanner) + match = scanner.scan(TOKEN_DOUBLE_QUOTE_ESCAPED_CHAR) + return nil if match.nil? + + case scanner[1] + when '\\' then return '\\' + when '"' then return '"' + when 'a' then return "\a" + when 'b' then return "\b" + when 't' then return "\t" + when 'n' then return "\n" + when 'v' then return "\v" + when 'f' then return "\f" + when 'r' then return "\r" + when 'e' then return "\e" + when 's' then return "\s" + when %r{[0-7]} + # Back the scanner up by one byte so we can grab all of the potential + # octal digits at the same time. + scanner.pos = scanner.pos - 1 + octal_character = scanner.scan(TOKEN_OCTAL_CHAR) + + return octal_character.to_i(8).chr + when 'x' + hex_character = scanner.scan(TOKEN_HEX_CHAR) + return nil if hex_character.nil? + + hex_character.to_i(16).chr + when 'u' + return unicode_short_hex_character(scanner) || unicode_long_hex_characters(scanner) + else + # Not a valid escape sequence as far as we're concerned. + return nil + end + end + private :double_quote_escaped_char + + def unicode_short_hex_character(scanner) + unicode_character = scanner.scan(TOKEN_UNICODE_SHORT_HEX_CHAR) + return nil if unicode_character.nil? + + [unicode_character.hex].pack 'U' + end + private :unicode_short_hex_character + + def unicode_long_hex_characters(scanner) + unicode_string = '' + return nil unless scanner.scan(TOKEN_OPEN_CURLY) + + loop do + char = scanner.scan(TOKEN_UNICODE_LONG_HEX_CHAR) + break if char.nil? + unicode_string << [char.hex].pack('U') + + separator = scanner.scan(TOKEN_SPACE) + break if separator.nil? + end + + return nil if scanner.scan(TOKEN_CLOSE_CURLY).nil? || unicode_string.empty? + + unicode_string + end + private :unicode_long_hex_characters + + def single_quoted_string(scanner) + quoted_string = '' + + match = scanner.scan(TOKEN_SINGLE_QUOTE) + return nil if match.nil? + + loop do + match = single_quote_char(scanner) + break if match.nil? + + quoted_string << match + end + + match = scanner.scan(TOKEN_SINGLE_QUOTE) + return quoted_string if match + + nil + end + private :single_quoted_string + + def double_quote_char(scanner) + double_quote_escaped_char(scanner) || double_quote_unescaped_char(scanner) + end + private :double_quote_char + + def double_quoted_string(scanner) + quoted_string = '' + + match = scanner.scan(TOKEN_DOUBLE_QUOTE) + return nil if match.nil? + + loop do + match = double_quote_char(scanner) + break if match.nil? + + quoted_string << match + end + + match = scanner.scan(TOKEN_DOUBLE_QUOTE) + return quoted_string if match + + nil + end + private :double_quoted_string + + def quoted_string(scanner) + single_quoted_string(scanner) || double_quoted_string(scanner) + end + private :quoted_string + + def array_values(scanner) + values = [] + + loop do + match = quoted_string(scanner) + break if match.nil? + values << match + + match = array_separator(scanner) + break if match.nil? + end + + values + end + private :array_values +end diff --git a/spec/unit/puppet_x/augeas/util/parser_spec.rb b/spec/unit/puppet_x/augeas/util/parser_spec.rb new file mode 100644 index 0000000..f8b5b2b --- /dev/null +++ b/spec/unit/puppet_x/augeas/util/parser_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' +require 'puppet_x/augeas/util/parser' + +describe PuppetX::Augeas::Util::Parser do + include described_class + + it 'handles an empty array' do + expect(parse_to_array('[]')).to eq([]) + end + + it 'handles an array with a simple single-quoted entry' do + expect(parse_to_array("['entry']")).to eq(['entry']) + end + + it 'handles an array with a simple double-quoted entry' do + expect(parse_to_array('["entry"]')).to eq(['entry']) + end + + it 'handles an array with both single- and double-quoted entries' do + expect(parse_to_array(%q(['first', "second"]))).to eq(['first', 'second']) + end + + context 'inside single-quoted strings' do + it 'allows a literal backslash' do + expect(parse_to_array("['entry\\\\here']")).to eq(['entry\\here']) + end + + it 'allows an internal single-quote' do + expect(parse_to_array("['entry\\'here']")).to eq(['entry\'here']) + end + end + + context 'inside double-quoted strings' do + it 'allows a literal backslash' do + expect(parse_to_array('["entry\\\\here"]')).to eq(['entry\\here']) + end + + it 'allows an internal double-quote' do + expect(parse_to_array('["entry\\"here"]')).to eq(['entry"here']) + end + + it 'does not require escaping a single-quote' do + expect(parse_to_array('["entry\'here"]')).to eq(["entry'here"]) + end + + it 'allows a bell character escape' do + expect(parse_to_array('["entry\\ahere"]')).to eq(["entry\ahere"]) + end + + it 'allows a backspace character escape' do + expect(parse_to_array('["entry\\bhere"]')).to eq(["entry\bhere"]) + end + + it 'allows a horizontal tab character escape' do + expect(parse_to_array('["entry\\there"]')).to eq(["entry\there"]) + end + + it 'allows a line feed character escape' do + expect(parse_to_array('["entry\\nhere"]')).to eq(["entry\nhere"]) + end + + it 'allows a vertical tab character escape' do + expect(parse_to_array('["entry\\vhere"]')).to eq(["entry\vhere"]) + end + + it 'allows a form feed character escape' do + expect(parse_to_array('["entry\\fhere"]')).to eq(["entry\fhere"]) + end + + it 'allows a carriage return character escape' do + expect(parse_to_array('["entry\\rhere"]')).to eq(["entry\rhere"]) + end + + it 'allows an escape character escape' do + expect(parse_to_array('["entry\\ehere"]')).to eq(["entry\ehere"]) + end + + it 'allows a space character escape' do + expect(parse_to_array('["entry\\shere"]')).to eq(['entry here']) + end + + it 'allows octal character escapes' do + expect(parse_to_array('["\7", "\41", "\101", "\1411"]')).to eq(["\a", '!', 'A', 'a1']) + end + + it 'allows hexadecimal character escapes with \\x' do + expect(parse_to_array('["\x7", "\x21", "\x211"]')).to eq(["\a", '!', '!1']) + end + + it 'allows single-character unicode hexadecimal character escapes with \\u' do + expect(parse_to_array('["\u2015", "\u20222"]')).to eq(["\u2015", "\u2022" << '2']) + end + + it 'allows multi-character unicode hexadecimal character escapes with \\u{...}' do + expect(parse_to_array('["\u{7}", "\u{20}", "\u{100}", "\u{2026}", "\u{1F464}", "\u{100000}", "\u{53 74 72 69 6E 67}"]')).to eq(["\a", ' ', "\u{100}", "\u{2026}", "\u{1F464}", "\u{100000}", 'String']) + end + end + + it 'fails with garbage in front of the array' do + expect { parse_to_array("junk ['good', 'array', 'here']") }.to raise_error(RuntimeError, %r{^Unexpected character in array at: junk \['good}) + end + + it 'fails with garbage in the middle of the array' do + expect { parse_to_array("['got', 'some', junk 'here']") }.to raise_error(RuntimeError, %r{^Unexpected character in array at: junk 'here'}) + end + + it 'fails with garbage after the array' do + expect { parse_to_array("['good', 'array', 'here'] junk after") }.to raise_error(RuntimeError, %r{^Unexpected character in array at: junk after}) + end +end -- cgit v1.2.3 From 374f73ded7956eeecdf0b5101d0a4cd4f7384c5e Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Tue, 17 Jul 2018 10:43:26 -0700 Subject: (maint) Update beaker-puppet version to 0.16 --- .sync.yml | 2 +- Gemfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.sync.yml b/.sync.yml index 100cd76..9d88d65 100644 --- a/.sync.yml +++ b/.sync.yml @@ -45,6 +45,6 @@ Gemfile: from_env: BEAKER_RSPEC_VERSION - gem: beaker-puppet from_env: BEAKER_PUPPET_VERSION - version: '~> 0.14' + version: '~> 0.16' ':development': - gem: puppet-strings diff --git a/Gemfile b/Gemfile index 1c6557c..4c9526b 100644 --- a/Gemfile +++ b/Gemfile @@ -43,7 +43,7 @@ group :system_tests do gem "beaker-pe", require: false gem "beaker-hostgenerator" gem "beaker-rspec" - gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 0.14') + gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 0.16') end puppet_version = ENV['PUPPET_GEM_VERSION'] -- cgit v1.2.3 From 3a32fccd8242afa810d6ca827ac97cbc25c385de Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 17 Jul 2018 19:37:38 -0700 Subject: Include puppet 6 and remove default role * bump max version to 7 * install module on all hosts, not just those with the default role * point project URL to module's README --- metadata.json | 4 ++-- spec/spec_helper_acceptance.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/metadata.json b/metadata.json index 5d7853d..8599c0f 100644 --- a/metadata.json +++ b/metadata.json @@ -5,7 +5,7 @@ "summary": "Manage files using Augeas", "license": "Apache-2.0", "source": "https://github.com/puppetlabs/puppetlabs-augeas_core", - "project_page": "https://puppet.com/docs/puppet/latest/types/augeas.html", + "project_page": "https://github.com/puppetlabs/puppetlabs-augeas_core/blob/master/README.md", "issues_url": "https://tickets.puppetlabs.com/projects/MODULES", "dependencies": [ @@ -57,7 +57,7 @@ "requirements": [ { "name": "puppet", - "version_requirement": ">= 4.7.0 < 6.0.0" + "version_requirement": ">= 4.7.0 < 7.0.0" } ], "pdk-version": "1.6.0", diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 848f7d9..a6836f2 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -6,7 +6,7 @@ RSpec.configure do |c| c.before :suite do unless ENV['BEAKER_provision'] == 'no' run_puppet_install_helper - install_module_on(hosts_as('default')) + install_module_on(hosts) install_module_dependencies_on(hosts) end end -- cgit v1.2.3 From 392a49d4656f436e00c602e52a9ffde31d6f43d4 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Wed, 15 Aug 2018 14:18:18 -0700 Subject: (PUP-9052) Bump puppet req to at least puppet 6 --- metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata.json b/metadata.json index 8599c0f..dee00ad 100644 --- a/metadata.json +++ b/metadata.json @@ -57,7 +57,7 @@ "requirements": [ { "name": "puppet", - "version_requirement": ">= 4.7.0 < 7.0.0" + "version_requirement": ">= 6.0.0 < 7.0.0" } ], "pdk-version": "1.6.0", -- cgit v1.2.3 From 5fdf3e2567bab490e42bfe8d16b875c1af8359ee Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Wed, 15 Aug 2018 14:40:27 -0700 Subject: Prep for the 1.0.1 release --- CHANGELOG.md | 18 ++++++++++++++---- metadata.json | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c954cd..7b2d9c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,20 @@ All notable changes to this project will be documented in this file. -## Release 0.1.0 +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org). -**Features** +## [1.0.1] - 2018-08-15 +### Added +- (MODULE-7443) Safely deserialize stringified array +### Changed +- (maint) Move array parser logic into a util module +- (maint) Update beaker-puppet version to 0.16 +- Include puppet 6 and remove default role +- (PUP-9052) Bump puppet req to at least puppet 6 -**Bugfixes** +## [1.0.0] - 2018-06-07 +### Summary +This is the initial release of the extracted augeas module -**Known Issues** +[1.0.1]: https://github.com/puppetlabs/puppetlabs-augeas_core/compare/1.0.0...1.0.1 +[1.0.0]: https://github.com/puppetlabs/puppetlabs-augeas_core/releases/tag/1.0.0 diff --git a/metadata.json b/metadata.json index dee00ad..255205b 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "puppetlabs-augeas_core", - "version": "1.0.0", + "version": "1.0.1", "author": "Puppet Labs", "summary": "Manage files using Augeas", "license": "Apache-2.0", -- cgit v1.2.3