123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- # Library which provides functions for reading and writing Routing Policy Specification Language (RPSL, RFC2622).
- module RPSL
- # Generic error when processing RPSL
- class RPSLError < StandardError; end
- # Error while loading RPSL
- class ParserError < RPSLError; end
- # Error while generating RPSL
- class DumpError < RPSLError; end
- # Parses a string as RPSL.
- # @param rpsl [String] The RPSL data as an object which can be iterated
- # with each_line.
- # @return [Hash] Hash containing the RPSL data.
- # @example
- # rpsl = <<EOF
- # route6: fd42:4242:2601:ffff::/64
- # descr: This is an ROA test object
- # max-length: 112
- # remarks:
- # This object is designed to test ROA scripts
- # in a number of ways.
- # +
- # The expected output for this route should be:
- # roa fd42:4242:2601:ffff::/64 max 64 as 0;
- # +
- # This is not a blank line
- # + ----- +
- # +
- # The first test is to include a number of syntax
- # corner cases, designed to trip up parsers
- # +
- # The first max-length line should be clamped
- # to the maximums defined in filter6.txt
- # +
- # The second max-length is part of the remark
- # and should be ignored.
- # +
- # max-length: 48
- # +
- # origin: AS0
- # mnt-by: BURBLE-MNT
- # source: DN42
- # EOF
- #
- # RPSL.load(rpsl) # => {"route6"=>"fd42:4242:2601:ffff::/64\n",
- # # "descr"=>"This is an ROA test object\n",
- # # "max-length"=>"112\n",
- # # "remarks"=>
- # # "\n" +
- # # "This object is designed to test ROA scripts\n" +
- # # "in a number of ways.\n" +
- # # "\n" +
- # # "The expected output for this route should be:\n" +
- # # "roa fd42:4242:2601:ffff::/64 max 64 as 0;\n" +
- # # "\n" +
- # # "This is not a blank line\n" +
- # # "+ ----- +\n" +
- # # "\n" +
- # # "The first test is to include a number of syntax\n" +
- # # "corner cases, designed to trip up parsers\n" +
- # # "\n" +
- # # "The first max-length line should be clamped\n" +
- # # "to the maximums defined in filter6.txt\n" +
- # # "\n" +
- # # "The second max-length is part of the remark\n" +
- # # "and should be ignored.\n" +
- # # "\n" +
- # # "max-length: 48\n" +
- # # "\n",
- # # "origin"=>"AS0\n",
- # # "mnt-by"=>"BURBLE-MNT\n",
- # # "source"=>"DN42\n"}
- def self.load(rpsl)
- obj = {}
- current_section = nil
- rpsl.to_s.each_line.with_index do |line, lineno|
- line.chomp!
- case line
- when /^([\d\w\-_]+): *(.*)$/
- current_section = $1
- obj[current_section] = "#{obj[current_section]}#{$2}\n"
- when /^[+ \s] *(.+)$/
- raise ParserError, "Value without key in line #{lineno + 1}" unless current_section
- obj[current_section] = "#{obj[current_section]}#{$1}\n"
- when /^\+ *$/
- raise ParserError, "Empty line without key in line #{lineno + 1}" unless current_section
- obj[current_section] = "#{obj[current_section]}\n"
- else
- raise ParserError, "Failed to parse line #{lineno + 1}."
- end
- end
- return obj
- end
- # Converts a hash into the RPSL format.
- # @param obj [Hash] Hash in which the data to be converted is stored.
- # @param value_column [Integer, NilClass] `nil` or the column where the
- # value should start.
- # @example
- # obj = {"route6"=>"fd42:4242:2601:ffff::/64",
- # "descr"=>"This is an ROA test object",
- # "max-length"=>"112",
- # "remarks"=>
- # "\n" +
- # "This object is designed to test ROA scripts\n" +
- # "in a number of ways.\n" +
- # "\n" +
- # "The expected output for this route should be:\n" +
- # "roa fd42:4242:2601:ffff::/64 max 64 as 0;\n" +
- # "\n" +
- # "This is not a blank line\n" +
- # "+ ----- +\n" +
- # "\n" +
- # "The first test is to include a number of syntax\n" +
- # "corner cases, designed to trip up parsers\n" +
- # "\n" +
- # "The first max-length line should be clamped\n" +
- # "to the maximums defined in filter6.txt\n" +
- # "\n" +
- # "The second max-length is part of the remark\n" +
- # "and should be ignored.\n" +
- # "\n" +
- # "max-length: 48",
- # "origin"=>"AS0",
- # "mnt-by"=>"BURBLE-MNT",
- # "source"=>"DN42"}
- #
- # RPSL.dump(obj, 20) # => "route6: fd42:4242:2601:ffff::/64\n" +
- # # "descr: This is an ROA test object\n" +
- # # "max-length: 112\n" +
- # # "remarks: \n" +
- # # " This object is designed to test ROA scripts\n" +
- # # " in a number of ways.\n" +
- # # "+\n" +
- # # " The expected output for this route should be:\n" +
- # # " roa fd42:4242:2601:ffff::/64 max 64 as 0;\n" +
- # # "+\n" +
- # # " This is not a blank line\n" +
- # # " + ----- +\n" +
- # # "+\n" +
- # # " The first test is to include a number of syntax\n" +
- # # " corner cases, designed to trip up parsers\n" +
- # # "+\n" +
- # # " The first max-length line should be clamped\n" +
- # # " to the maximums defined in filter6.txt\n" +
- # # "+\n" +
- # # " The second max-length is part of the remark\n" +
- # # " and should be ignored.\n" +
- # # "+\n" +
- # # " max-length: 48\n" +
- # # "origin: AS0\n" +
- # # "mnt-by: BURBLE-MNT\n" +
- # # "source: DN42\n"
- def self.dump(obj, value_column = nil)
- rpsl = ''
- obj.each do |key, value|
- lines = value.to_s.lines
- lines.map!(&:chomp)
- first_value = lines.shift
- key = key.to_s
- if value_column
- padding_length = value_column.to_i - 1 - key.length
- raise DumpError, 'Key too long.' if padding_length.negative?
- padding = ' ' * padding_length
- end
- rpsl += "#{key}:#{padding}#{first_value}\n"
- lines.each do |line|
- rpsl += if line.empty?
- "+\n"
- elsif value_column
- "#{' ' * value_column}#{line}\n"
- else
- " #{line}\n"
- end
- end
- end
- return rpsl
- end
- end
|