dns-zone 0.0.0.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/Guardfile +14 -0
- data/HISTORY.md +3 -0
- data/README.md +100 -0
- data/Rakefile +15 -0
- data/dns-zone.gemspec +41 -0
- data/lib/dns/zone.rb +105 -0
- data/lib/dns/zone/rr.rb +51 -0
- data/lib/dns/zone/rr/a.rb +21 -0
- data/lib/dns/zone/rr/aaaa.rb +5 -0
- data/lib/dns/zone/rr/cname.rb +14 -0
- data/lib/dns/zone/rr/mx.rb +16 -0
- data/lib/dns/zone/rr/ns.rb +21 -0
- data/lib/dns/zone/rr/record.rb +81 -0
- data/lib/dns/zone/rr/soa.rb +52 -0
- data/lib/dns/zone/rr/srv.rb +17 -0
- data/lib/dns/zone/rr/txt.rb +22 -0
- data/lib/dns/zone/test_case.rb +28 -0
- data/lib/dns/zone/version.rb +6 -0
- data/test/rr/a_test.rb +36 -0
- data/test/rr/aaaa_test.rb +13 -0
- data/test/rr/cname_test.rb +12 -0
- data/test/rr/mx_test.rb +18 -0
- data/test/rr/ns_test.rb +11 -0
- data/test/rr/record_test.rb +30 -0
- data/test/rr/soa_test.rb +34 -0
- data/test/rr/srv_test.rb +14 -0
- data/test/rr/txt_test.rb +44 -0
- data/test/rr_test.rb +42 -0
- data/test/version_test.rb +9 -0
- data/test/zone_test.rb +111 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 82c71fa4e618b0bff6f946e6097511699e49aec0
|
4
|
+
data.tar.gz: b4021e5692ea47f2ca8a3d129aed828593155af7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f162ed2a6e7804120dd8b6fadac2c5b4c6d2a51b9044e66dc8e48fc2c86e1dbad34f89b185934febb26cc41d6f032dfff742bf7254b5fd553cbf77363d82653c
|
7
|
+
data.tar.gz: 4242b9bbf4fe966a790e948ff5e31affc9488ac0fe5c6f6dd1ae2e92cd4b4e2f68f04bfe2f0bb685e7f8eac15e2420855568da9419a1849534c43e089022acd3
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Run minitest suite
|
2
|
+
guard :minitest do
|
3
|
+
watch(%r{^dns-zone\.gemspec}) { 'all' }
|
4
|
+
watch(%r{^lib/dns/(.*)\.rb}) { |m| "test/#{m[1]}_test.rb" }
|
5
|
+
watch(%r{^lib/dns/zone/test_case\.rb}) { 'all' }
|
6
|
+
watch(%r{^lib/dns/zone/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
|
7
|
+
watch(%r{^test/(.*/)*(.*)_test\.rb})
|
8
|
+
end
|
9
|
+
|
10
|
+
# Run `bundle update|install` when gem files altered.
|
11
|
+
guard :bundler do
|
12
|
+
watch('Gemfile')
|
13
|
+
watch(/^.+\.gemspec/)
|
14
|
+
end
|
data/HISTORY.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
dns-zone
|
2
|
+
========
|
3
|
+
|
4
|
+
A Ruby library for building and parsing DNS zone files.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your Gemfile:
|
9
|
+
|
10
|
+
gem 'dns-zone'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
bundle install
|
15
|
+
|
16
|
+
Require the gem in your code:
|
17
|
+
|
18
|
+
require 'dns/zone'
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
DNS::Zone.new
|
23
|
+
DNS::Zone.load(zone_as_string)
|
24
|
+
DNS::Zone::RR.load(rr_as_string)
|
25
|
+
DNS::Zone::RR::A.load(a_rr_as_string)
|
26
|
+
|
27
|
+
zone = DNS::Zone.new
|
28
|
+
zone.origin = 'example.com.'
|
29
|
+
# FIXME: not sure what RFC (if any) defines the time formatting
|
30
|
+
zone.ttl = '1d'
|
31
|
+
# FIXME: keep DNS style representation? for <domain-name>s and email addresses
|
32
|
+
zone.soa.nameserver = 'ns0.lividpenguin.com.'
|
33
|
+
zone.soa.email = 'hostmaster.lividpenguin.com.'
|
34
|
+
|
35
|
+
# output as dns zone file
|
36
|
+
zone.to_zone_file
|
37
|
+
|
38
|
+
# Development
|
39
|
+
|
40
|
+
## Development Commands
|
41
|
+
|
42
|
+
# install external gem dependencies first
|
43
|
+
bundle install
|
44
|
+
|
45
|
+
# run all tests and build code coverage
|
46
|
+
bundle exec rake test
|
47
|
+
|
48
|
+
# hints where to improve docs
|
49
|
+
bundle exec inch
|
50
|
+
|
51
|
+
# watch for changes and run development commands (tests, documentation, etc)
|
52
|
+
bundle exec guard
|
53
|
+
|
54
|
+
# TODO
|
55
|
+
|
56
|
+
## Must have
|
57
|
+
|
58
|
+
[ ] Ability to load a whole zone
|
59
|
+
[x] Add support for RR Type: SOA
|
60
|
+
[ ] Add support for RR Type: PTR
|
61
|
+
[ ] Add support for RR Type: SPF
|
62
|
+
[ ] Add support for RR Type: LOC
|
63
|
+
[ ] Add support for RR Type: HINFO
|
64
|
+
|
65
|
+
## Would be nice
|
66
|
+
|
67
|
+
[ ] Handle parsing a zone file that uses more then one $ORIGIN directive.
|
68
|
+
[ ] Validation, error checking...
|
69
|
+
[ ] Only one SOA per zone.
|
70
|
+
[ ] CNAMEs can't use a label of `@`.
|
71
|
+
|
72
|
+
[ ] Ability to 'include' defaults/records into a zone.
|
73
|
+
This may or may not want to mean supporting the `$INCLUDE` directive.
|
74
|
+
|
75
|
+
## At some point; low priority
|
76
|
+
|
77
|
+
[ ] Configuration options:
|
78
|
+
[ ] spaces/tabs used between RR params in zone file output
|
79
|
+
[ ] time format to use (seconds or bind time format (e.g. 1d))
|
80
|
+
[ ] add comments to explain TTL's that are in seconds
|
81
|
+
[ ] Ability to add comment to RR (n.b. currently we strip comments when parsing)
|
82
|
+
[ ] Add support for RR Type: DNAME
|
83
|
+
[ ] Add support for RR Type: DNSKEY
|
84
|
+
[ ] Add support for RR Type: DS
|
85
|
+
[ ] Add support for RR Type: KEY
|
86
|
+
[ ] Add support for RR Type: NSEC
|
87
|
+
[ ] Add support for RR Type: RRSIG
|
88
|
+
[ ] Add support for RR Type: NAPTR
|
89
|
+
[ ] Add support for RR Type: RP
|
90
|
+
[ ] Add support for RR Type: RT
|
91
|
+
|
92
|
+
# Notes
|
93
|
+
|
94
|
+
- RR Format: `[<TTL>] [<class>] <type> <RDATA>`
|
95
|
+
- A DNS zone is built from RR's and a couple of other special directives.
|
96
|
+
- If zone file does not include $ORIGIN, it will be inferred by the `zone "<zone-name>" {}` clause from bind.conf
|
97
|
+
In general we should always explicitly define an $ORIGIN directive unless there is a very good reason not to.
|
98
|
+
- [RFC 1035 - Domain Names - Implementation and Specification](http://www.ietf.org/rfc/rfc1035.txt)
|
99
|
+
- [RFC 2308 - Negative Caching of DNS Queries (DNS NCACHE)](http://www.ietf.org/rfc/rfc2308.txt)
|
100
|
+
- [RFC 3596 - DNS Extensions to Support IP Version 6](http://www.ietf.org/rfc/rfc3596.txt)
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems' unless defined?(Gem)
|
2
|
+
require 'bundler'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
# by default run unit tests.
|
7
|
+
task :default => 'test'
|
8
|
+
|
9
|
+
desc 'Run full test suite and generate code coverage -- COVERAGE=false to disable code coverage'
|
10
|
+
Rake::TestTask.new(:test) do |task|
|
11
|
+
ENV['COVERAGE'] ||= 'yes'
|
12
|
+
task.libs << 'test'
|
13
|
+
task.pattern = 'test/**/*_test.rb'
|
14
|
+
task.verbose = true
|
15
|
+
end
|
data/dns-zone.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require './lib/dns/zone/version'
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
# gem information/details
|
5
|
+
s.name = 'dns-zone'
|
6
|
+
s.version = DNS::Zone::Version
|
7
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
8
|
+
s.summary = 'A Ruby library for building and parsing DNS zone files.'
|
9
|
+
s.license = 'MIT'
|
10
|
+
s.homepage = 'https://github.com/lantins/dns-zone'
|
11
|
+
s.authors = ['Luke Antins']
|
12
|
+
s.email = ['luke@lividpenguin.com']
|
13
|
+
|
14
|
+
# gem settings for what files to include.
|
15
|
+
s.files = %w(Rakefile README.md HISTORY.md Gemfile Guardfile dns-zone.gemspec)
|
16
|
+
s.files += Dir.glob('{test/**/*,lib/**/*}')
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
#s.executables = ['dns-zone']
|
19
|
+
#s.default_executable = 'dns-zone'
|
20
|
+
|
21
|
+
# min ruby version
|
22
|
+
s.required_ruby_version = ::Gem::Requirement.new("~> 1.9")
|
23
|
+
|
24
|
+
# cross platform gem dependencies
|
25
|
+
#s.add_dependency('gli')
|
26
|
+
#s.add_dependency('paint')
|
27
|
+
s.add_development_dependency('bundler', '~> 0')
|
28
|
+
s.add_development_dependency('rake', '~> 0')
|
29
|
+
s.add_development_dependency('minitest', '~> 0')
|
30
|
+
s.add_development_dependency('simplecov', '~> 0.7')
|
31
|
+
s.add_development_dependency('yard', '~> 0')
|
32
|
+
s.add_development_dependency('inch', '~> 0')
|
33
|
+
s.add_development_dependency('guard-minitest', '~> 0')
|
34
|
+
s.add_development_dependency('guard-bundler', '~> 0')
|
35
|
+
|
36
|
+
# long description.
|
37
|
+
s.description = <<-EOL
|
38
|
+
A Ruby library for building and parsing DNS zone files for use with
|
39
|
+
Bind and PowerDNS (with Bind backend) DNS servers.
|
40
|
+
EOL
|
41
|
+
end
|
data/lib/dns/zone.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'dns/zone/rr'
|
2
|
+
require 'dns/zone/version'
|
3
|
+
|
4
|
+
# :nodoc:
|
5
|
+
module DNS
|
6
|
+
|
7
|
+
# Represents a 'whole' zone of many resource records (RRs).
|
8
|
+
#
|
9
|
+
# This is also the primary namespace for the `dns-zone` gem.
|
10
|
+
class Zone
|
11
|
+
|
12
|
+
attr_accessor :ttl, :origin, :records
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@records = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# FROM RFC:
|
19
|
+
# The format of these files is a sequence of entries. Entries are
|
20
|
+
# predominantly line-oriented, though parentheses can be used to continue
|
21
|
+
# a list of items across a line boundary, and text literals can contain
|
22
|
+
# CRLF within the text. Any combination of tabs and spaces act as a
|
23
|
+
# delimiter between the separate items that make up an entry. The end of
|
24
|
+
# any line in the master file can end with a comment. The comment starts
|
25
|
+
# with a ";" (semicolon).
|
26
|
+
|
27
|
+
def self.load(string)
|
28
|
+
# get entries
|
29
|
+
entries = self.extract_entries(string)
|
30
|
+
|
31
|
+
instance = self.new
|
32
|
+
|
33
|
+
entries.each do |entry|
|
34
|
+
if entry =~ /\$(ORIGIN|TTL)\s+(.+)/
|
35
|
+
instance.ttl = $2 if $1 == 'TTL'
|
36
|
+
instance.origin = $2 if $1 == 'ORIGIN'
|
37
|
+
next
|
38
|
+
end
|
39
|
+
|
40
|
+
if entry =~ DNS::Zone::RR::RX_RR
|
41
|
+
rec = DNS::Zone::RR.load(entry)
|
42
|
+
instance.records << rec if rec
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# read in special statments like $TTL and $ORIGIN
|
48
|
+
# parse each RR and create a Ruby object for it
|
49
|
+
return instance
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.extract_entries(string)
|
53
|
+
entries = []
|
54
|
+
mode = :line
|
55
|
+
entry = ''
|
56
|
+
|
57
|
+
parentheses_ref_count = 0
|
58
|
+
|
59
|
+
string.lines.each do |line|
|
60
|
+
# strip comments unless escaped
|
61
|
+
line = line.gsub(/(?<!\\);.*/o, '').chomp
|
62
|
+
|
63
|
+
next if line.gsub(/\s+/, '').empty?
|
64
|
+
|
65
|
+
# append to entry line
|
66
|
+
entry << line
|
67
|
+
|
68
|
+
quotes = entry.count('"')
|
69
|
+
has_quotes = quotes > 0
|
70
|
+
|
71
|
+
parentheses = entry.count('()')
|
72
|
+
has_parentheses = parentheses > 0
|
73
|
+
|
74
|
+
if has_quotes
|
75
|
+
character_strings = entry.scan(/("(?:[^"\\]+|\\.)*")/).join(' ')
|
76
|
+
without = entry.gsub(/"((?:[^"\\]+|\\.)*)"/, '')
|
77
|
+
parentheses_ref_count = without.count('(') - without.count(')')
|
78
|
+
else
|
79
|
+
parentheses_ref_count = entry.count('(') - entry.count(')')
|
80
|
+
end
|
81
|
+
|
82
|
+
# are parentheses balanced?
|
83
|
+
if parentheses_ref_count == 0
|
84
|
+
if has_quotes
|
85
|
+
without.gsub!(/[()]/, '')
|
86
|
+
without.gsub!(/[ ]{2,}/, ' ')
|
87
|
+
#entries << (without + character_strings)
|
88
|
+
entry = (without + character_strings)
|
89
|
+
else
|
90
|
+
entry.gsub!(/[()]/, '')
|
91
|
+
entry.gsub!(/[ ]{2,}/, ' ')
|
92
|
+
entry.gsub!(/[ ]+$/, '')
|
93
|
+
#entries << entry
|
94
|
+
end
|
95
|
+
entries << entry
|
96
|
+
entry = ''
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
return entries
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/lib/dns/zone/rr.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module DNS
|
2
|
+
class Zone
|
3
|
+
|
4
|
+
# The module containes resource record types supported by this gem.
|
5
|
+
# The #{load} method will convert RR string data into a Ruby class.
|
6
|
+
module RR
|
7
|
+
|
8
|
+
RX_TTL = /\d+[wdmhs]?/i
|
9
|
+
RX_KLASS = /(?<klass>IN)?/i
|
10
|
+
RX_TYPE = /(?<type>A|AAAA|CNAME|MX|NS|SOA|SRV|TXT)\s{1}/i
|
11
|
+
RX_RR = /^(?<label>\S+|\s{1})\s*(?<ttl>#{RX_TTL})?\s*#{RX_KLASS}\s*#{RX_TYPE}\s*(?<rdata>[\s\S]*)$/i
|
12
|
+
RX_DOMAINNAME = /\S+\./i
|
13
|
+
|
14
|
+
# Load RR string data and return an instance representing the RR.
|
15
|
+
#
|
16
|
+
# @param string [String] RR ASCII string data
|
17
|
+
# @param options [Hash] additional data required to correctly parse a 'whole' zone
|
18
|
+
# @option options [String] :last_label The last label used by the previous RR
|
19
|
+
# @return [Object]
|
20
|
+
def self.load(string, options = {})
|
21
|
+
# strip comments, unless its escaped.
|
22
|
+
string.gsub!(/(?<!\\);.*/o, "");
|
23
|
+
|
24
|
+
captures = string.match(RX_RR)
|
25
|
+
return nil unless captures
|
26
|
+
|
27
|
+
case captures[:type]
|
28
|
+
when 'A' then A.new.load(string, options)
|
29
|
+
when 'AAAA' then AAAA.new.load(string, options)
|
30
|
+
when 'TXT' then TXT.new.load(string, options)
|
31
|
+
when 'SOA' then SOA.new.load(string, options)
|
32
|
+
when 'NS' then NS.new.load(string, options)
|
33
|
+
else
|
34
|
+
raise 'Unknown RR Type'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
autoload :Record, 'dns/zone/rr/record'
|
39
|
+
|
40
|
+
autoload :A, 'dns/zone/rr/a'
|
41
|
+
autoload :AAAA, 'dns/zone/rr/aaaa'
|
42
|
+
autoload :CNAME, 'dns/zone/rr/cname'
|
43
|
+
autoload :MX, 'dns/zone/rr/mx'
|
44
|
+
autoload :NS, 'dns/zone/rr/ns'
|
45
|
+
autoload :SOA, 'dns/zone/rr/soa'
|
46
|
+
autoload :SRV, 'dns/zone/rr/srv'
|
47
|
+
autoload :TXT, 'dns/zone/rr/txt'
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# `A` resource record.
|
2
|
+
#
|
3
|
+
# RFC 1035
|
4
|
+
class DNS::Zone::RR::A < DNS::Zone::RR::Record
|
5
|
+
|
6
|
+
attr_accessor :address
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
parts = general_prefix
|
10
|
+
parts << address
|
11
|
+
parts.join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(string, options = {})
|
15
|
+
rdata = load_general_and_get_rdata(string, options)
|
16
|
+
return nil unless rdata
|
17
|
+
@address = rdata
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# `MX` resource record.
|
2
|
+
#
|
3
|
+
# RFC 1035
|
4
|
+
class DNS::Zone::RR::MX < DNS::Zone::RR::Record
|
5
|
+
|
6
|
+
attr_accessor :preference
|
7
|
+
attr_accessor :exchange
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
parts = general_prefix
|
11
|
+
parts << preference
|
12
|
+
parts << exchange
|
13
|
+
parts.join(' ')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# `NS` resource record.
|
2
|
+
#
|
3
|
+
# RFC 1035
|
4
|
+
class DNS::Zone::RR::NS < DNS::Zone::RR::Record
|
5
|
+
|
6
|
+
attr_accessor :nameserver
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
parts = general_prefix
|
10
|
+
parts << nameserver
|
11
|
+
parts.join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(string, options = {})
|
15
|
+
rdata = load_general_and_get_rdata(string, options)
|
16
|
+
return nil unless rdata
|
17
|
+
@nameserver = rdata
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Parent class of all RR types, common resource record code lives here.
|
2
|
+
# Is responsible for building a Ruby object given a RR string.
|
3
|
+
#
|
4
|
+
# @abstract Each RR TYPE should subclass and override: {#load} and #{to_s}
|
5
|
+
class DNS::Zone::RR::Record
|
6
|
+
|
7
|
+
attr_accessor :label, :ttl
|
8
|
+
attr_reader :klass
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@label = '@'
|
12
|
+
@klass = 'IN'
|
13
|
+
end
|
14
|
+
|
15
|
+
# FIXME: should we just: `def type; 'SOA'; end` rather then do the class name convension?
|
16
|
+
#
|
17
|
+
# Figures out TYPE of RR using class name.
|
18
|
+
# This means the class name _must_ match the RR ASCII TYPE.
|
19
|
+
#
|
20
|
+
# When called directly on the parent class (that you should never do), it will
|
21
|
+
# return the string as `<type>`, for use with internal tests.
|
22
|
+
#
|
23
|
+
# @return [String] the RR type
|
24
|
+
def type
|
25
|
+
name = self.class.name.split('::').last
|
26
|
+
return '<type>' if name == 'Record'
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns 'general' prefix (in parts) that come before the RDATA.
|
31
|
+
# Used by all RR types, generates: `[<label>] [<ttl>] [<class>] <type>`
|
32
|
+
#
|
33
|
+
# @return [Array<String>] rr prefix parts
|
34
|
+
def general_prefix
|
35
|
+
parts = []
|
36
|
+
parts << label
|
37
|
+
parts << ttl if ttl
|
38
|
+
parts << 'IN'
|
39
|
+
parts << type
|
40
|
+
parts
|
41
|
+
end
|
42
|
+
|
43
|
+
# Build RR zone file output.
|
44
|
+
#
|
45
|
+
# @return [String] RR zone file output
|
46
|
+
def to_s
|
47
|
+
general_prefix.join(' ')
|
48
|
+
end
|
49
|
+
|
50
|
+
# @abstract Override to update instance with RR type spesific data.
|
51
|
+
# @param string [String] RR ASCII string data
|
52
|
+
# @param options [Hash] additional data required to correctly parse a 'whole' zone
|
53
|
+
# @option options [String] :last_label The last label used by the previous RR
|
54
|
+
# @return [Object]
|
55
|
+
def load(string, options = {})
|
56
|
+
raise 'must be implemented by subclass'
|
57
|
+
end
|
58
|
+
|
59
|
+
# Load 'general' RR data/params and return the remaining RDATA for further parsing.
|
60
|
+
#
|
61
|
+
# @param string [String] RR ASCII string data
|
62
|
+
# @param options [Hash] additional data required to correctly parse a 'whole' zone
|
63
|
+
# @return [String] remaining RDATA
|
64
|
+
def load_general_and_get_rdata(string, options = {})
|
65
|
+
# strip comments, unless its escaped.
|
66
|
+
string.gsub!(/(?<!\\);.*/o, "");
|
67
|
+
|
68
|
+
captures = string.match(DNS::Zone::RR::RX_RR)
|
69
|
+
return nil unless captures
|
70
|
+
|
71
|
+
if captures[:label] == ' '
|
72
|
+
@label = options[:last_label]
|
73
|
+
else
|
74
|
+
@label = captures[:label]
|
75
|
+
end
|
76
|
+
|
77
|
+
@ttl = captures[:ttl]
|
78
|
+
captures[:rdata]
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# `SRV` resource record.
|
2
|
+
#
|
3
|
+
# RFC xxxx
|
4
|
+
class DNS::Zone::RR::SOA < DNS::Zone::RR::Record
|
5
|
+
|
6
|
+
RX_SOA_RDATA = %r{
|
7
|
+
(?<nameserver>#{DNS::Zone::RR::RX_DOMAINNAME})\s* # get nameserver domainname
|
8
|
+
(?<email>#{DNS::Zone::RR::RX_DOMAINNAME})\s* # get mailbox domainname
|
9
|
+
(?<serial>\d+)\s*
|
10
|
+
(?<refresh_ttl>#{DNS::Zone::RR::RX_TTL})\s*
|
11
|
+
(?<retry_ttl>#{DNS::Zone::RR::RX_TTL})\s*
|
12
|
+
(?<expiry_ttl>#{DNS::Zone::RR::RX_TTL})\s*
|
13
|
+
(?<minimum_ttl>#{DNS::Zone::RR::RX_TTL})\s*
|
14
|
+
}mx
|
15
|
+
|
16
|
+
attr_accessor :nameserver, :email, :serial, :refresh_ttl, :retry_ttl, :expiry_ttl, :minimum_ttl
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
parts = general_prefix
|
20
|
+
parts << nameserver
|
21
|
+
parts << email
|
22
|
+
|
23
|
+
parts << '('
|
24
|
+
parts << serial
|
25
|
+
parts << refresh_ttl
|
26
|
+
parts << retry_ttl
|
27
|
+
parts << expiry_ttl
|
28
|
+
parts << minimum_ttl
|
29
|
+
parts << ')'
|
30
|
+
parts.join(' ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def load(string, options = {})
|
34
|
+
rdata = load_general_and_get_rdata(string, options)
|
35
|
+
return nil unless rdata
|
36
|
+
|
37
|
+
captures = rdata.match(RX_SOA_RDATA)
|
38
|
+
return nil unless captures
|
39
|
+
|
40
|
+
@nameserver = captures[:nameserver]
|
41
|
+
@email = captures[:email]
|
42
|
+
@serial = captures[:serial].to_i
|
43
|
+
@refresh_ttl = captures[:refresh_ttl]
|
44
|
+
@retry_ttl = captures[:retry_ttl]
|
45
|
+
@expiry_ttl = captures[:expiry_ttl]
|
46
|
+
@minimum_ttl = captures[:minimum_ttl]
|
47
|
+
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# `SRV` resource record.
|
2
|
+
#
|
3
|
+
# RFC xxxx
|
4
|
+
class DNS::Zone::RR::SRV < DNS::Zone::RR::Record
|
5
|
+
|
6
|
+
attr_accessor :priority, :weight, :port, :target
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
parts = general_prefix
|
10
|
+
parts << priority
|
11
|
+
parts << weight
|
12
|
+
parts << port
|
13
|
+
parts << target
|
14
|
+
parts.join(' ')
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# `A` resource record.
|
2
|
+
#
|
3
|
+
# RFC 1035
|
4
|
+
class DNS::Zone::RR::TXT < DNS::Zone::RR::Record
|
5
|
+
|
6
|
+
attr_accessor :text
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
parts = general_prefix
|
10
|
+
parts << %Q{"#{text}"}
|
11
|
+
parts.join(' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def load(string, options = {})
|
15
|
+
rdata = load_general_and_get_rdata(string, options)
|
16
|
+
return nil unless rdata
|
17
|
+
# extract text from within quotes; allow multiple quoted strings; ignore escaped quotes
|
18
|
+
@text = rdata.scan(/"((?:[^"\\]+|\\.)*)"/).join
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# test_case.rb - A file used to setup the testing enviroment for the library.
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
# --- code coverage on MRI 1.9 ruby only, but disabled by default --------------
|
8
|
+
if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'ruby' && ENV['COVERAGE']
|
9
|
+
require 'simplecov'
|
10
|
+
#SimpleCov.command_name 'test:unit'
|
11
|
+
SimpleCov.start do
|
12
|
+
# code coverage groups.
|
13
|
+
add_filter 'test/'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# --- load our dependencies using bundler --------------------------------------
|
18
|
+
require 'bundler/setup'
|
19
|
+
Bundler.setup
|
20
|
+
require 'minitest/autorun'
|
21
|
+
require 'minitest/pride'
|
22
|
+
|
23
|
+
# --- Load lib to test ---------------------------------------------------------
|
24
|
+
require 'dns/zone'
|
25
|
+
|
26
|
+
# --- Extend DNS::Zone::TestCase -------------------------------------------------
|
27
|
+
class DNS::Zone::TestCase < Minitest::Test
|
28
|
+
end
|
data/test/rr/a_test.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_A_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_build_rr__a
|
6
|
+
rr = DNS::Zone::RR::A.new
|
7
|
+
|
8
|
+
# ensure we can set address parameter
|
9
|
+
rr.address = '10.0.1.1'
|
10
|
+
assert_equal '@ IN A 10.0.1.1', rr.to_s
|
11
|
+
rr.address = '10.0.2.2'
|
12
|
+
assert_equal '@ IN A 10.0.2.2', rr.to_s
|
13
|
+
|
14
|
+
# with a label set
|
15
|
+
rr.label = 'labelname'
|
16
|
+
assert_equal 'labelname IN A 10.0.2.2', rr.to_s
|
17
|
+
|
18
|
+
# with a ttl
|
19
|
+
rr.ttl = '4w'
|
20
|
+
assert_equal 'labelname 4w IN A 10.0.2.2', rr.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_load
|
24
|
+
rr = DNS::Zone::RR::A.new.load('@ IN A 127.0.0.1')
|
25
|
+
assert_equal '@', rr.label
|
26
|
+
assert_equal 'A', rr.type
|
27
|
+
assert_equal '127.0.0.1', rr.address
|
28
|
+
|
29
|
+
rr = DNS::Zone::RR::A.new.load('www IN A 127.0.0.1')
|
30
|
+
assert_equal 'www', rr.label
|
31
|
+
assert_equal 'A', rr.type
|
32
|
+
assert_equal 'IN', rr.klass
|
33
|
+
assert_equal '127.0.0.1', rr.address
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_AAAA_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_build_rr__aaaa
|
6
|
+
rr = DNS::Zone::RR::AAAA.new
|
7
|
+
|
8
|
+
# ensure we can set address parameter
|
9
|
+
rr.address = '2001:db8::3'
|
10
|
+
assert_equal '@ IN AAAA 2001:db8::3', rr.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_CNAME_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_build_rr__cname
|
6
|
+
rr = DNS::Zone::RR::CNAME.new
|
7
|
+
rr.label = 'google9d97d7f266ee521d'
|
8
|
+
rr.domainname = 'google.com.'
|
9
|
+
assert_equal 'google9d97d7f266ee521d IN CNAME google.com.', rr.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/test/rr/mx_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_MX_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_build_rr__mx
|
6
|
+
rr = DNS::Zone::RR::MX.new
|
7
|
+
|
8
|
+
# ensure we can set preference and exchange parameter
|
9
|
+
rr.preference = '10'
|
10
|
+
rr.exchange = 'mx0.lividpenguin.com.'
|
11
|
+
assert_equal '@ IN MX 10 mx0.lividpenguin.com.', rr.to_s
|
12
|
+
|
13
|
+
rr.preference = '20'
|
14
|
+
rr.exchange = 'mx1.lividpenguin.com.'
|
15
|
+
assert_equal '@ IN MX 20 mx1.lividpenguin.com.', rr.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/rr/ns_test.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_Record_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_rr_record_defaults
|
6
|
+
rr = DNS::Zone::RR::Record.new
|
7
|
+
assert_equal '@', rr.label, 'label is @, by default'
|
8
|
+
assert_equal nil, rr.ttl, 'ttl is nil, by default'
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_rr_record_with_label
|
12
|
+
rr = DNS::Zone::RR::Record.new
|
13
|
+
rr.label = 'labelname'
|
14
|
+
assert_equal 'labelname IN <type>', rr.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_rr_record_with_label_and_ttl
|
18
|
+
rr = DNS::Zone::RR::Record.new
|
19
|
+
rr.label = 'labelname'
|
20
|
+
rr.ttl = '2d'
|
21
|
+
assert_equal 'labelname 2d IN <type>', rr.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_rr_record_with_ttl
|
25
|
+
rr = DNS::Zone::RR::Record.new
|
26
|
+
rr.ttl = '2d'
|
27
|
+
assert_equal '@ 2d IN <type>', rr.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/test/rr/soa_test.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_SRV_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
EXAMPLE_SOA_IN = '@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. 2014021601 3h 15m 4w 30m'
|
6
|
+
EXAMPLE_SOA_OUT = '@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. ( 2014021601 3h 15m 4w 30m )'
|
7
|
+
|
8
|
+
def test_build
|
9
|
+
rr = DNS::Zone::RR::SOA.new
|
10
|
+
rr.nameserver = 'ns0.lividpenguin.com.'
|
11
|
+
rr.email = 'luke.lividpenguin.com.'
|
12
|
+
rr.serial = 2014_02_16_01
|
13
|
+
rr.refresh_ttl = '3h'
|
14
|
+
rr.retry_ttl = '15m'
|
15
|
+
rr.expiry_ttl = '4w'
|
16
|
+
rr.minimum_ttl = '30m'
|
17
|
+
assert_equal EXAMPLE_SOA_OUT, rr.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_load
|
21
|
+
rr = DNS::Zone::RR::SOA.new.load(EXAMPLE_SOA_IN)
|
22
|
+
assert_equal '@', rr.label
|
23
|
+
assert_equal 'SOA', rr.type
|
24
|
+
|
25
|
+
assert_equal 'ns0.lividpenguin.com.', rr.nameserver
|
26
|
+
assert_equal 'luke.lividpenguin.com.', rr.email
|
27
|
+
assert_equal 2014_02_16_01, rr.serial
|
28
|
+
assert_equal '3h', rr.refresh_ttl
|
29
|
+
assert_equal '15m', rr.retry_ttl
|
30
|
+
assert_equal '4w', rr.expiry_ttl
|
31
|
+
assert_equal '30m', rr.minimum_ttl
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/test/rr/srv_test.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_SRV_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_build_rr__srv
|
6
|
+
rr = DNS::Zone::RR::SRV.new
|
7
|
+
rr.priority = 5
|
8
|
+
rr.weight = 0
|
9
|
+
rr.port = 5269
|
10
|
+
rr.target = 'xmpp-server.l.google.com.'
|
11
|
+
assert_equal '@ IN SRV 5 0 5269 xmpp-server.l.google.com.', rr.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/test/rr/txt_test.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RR_TXT_Test < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_build_rr__txt
|
6
|
+
rr = DNS::Zone::RR::TXT.new
|
7
|
+
|
8
|
+
# ensure we can set text parameter
|
9
|
+
rr.text = 'test text'
|
10
|
+
assert_equal '@ IN TXT "test text"', rr.to_s
|
11
|
+
|
12
|
+
# with a label set
|
13
|
+
rr.label = 'labelname'
|
14
|
+
assert_equal 'labelname IN TXT "test text"', rr.to_s
|
15
|
+
|
16
|
+
# with a ttl
|
17
|
+
rr.ttl = '2w'
|
18
|
+
assert_equal 'labelname 2w IN TXT "test text"', rr.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_load
|
22
|
+
rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "test text"')
|
23
|
+
assert_equal 'txtrecord', rr.label
|
24
|
+
assert_equal 'IN', rr.klass
|
25
|
+
assert_equal 'TXT', rr.type
|
26
|
+
assert_equal 'test text', rr.text
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_load_multiple_quoted_strings
|
30
|
+
rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "part1 yo" " part2 yo"')
|
31
|
+
assert_equal 'part1 yo part2 yo', rr.text
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_load_string_with_quotes
|
35
|
+
rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "we have \"double\" quotes"')
|
36
|
+
assert_equal %q{we have \"double\" quotes}, rr.text
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_load_multiple_strings_with_quotes
|
40
|
+
rr = DNS::Zone::RR::TXT.new.load('txtrecord IN TXT "part1 " "we have \"double\" quotes" " part3"')
|
41
|
+
assert_equal %q{part1 we have \"double\" quotes part3}, rr.text
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/test/rr_test.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class RRTest < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
def test_load_ignores_comments
|
6
|
+
rr = DNS::Zone::RR.load('; comment lines are ignored')
|
7
|
+
assert_nil rr, 'should be nil when its a comment line'
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_load_ignores_unparsable_input
|
11
|
+
rr = DNS::Zone::RR.load('invalid input should not be parsed')
|
12
|
+
assert_nil rr, 'should be nil when input cant be parsed at all'
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_load_a_rr
|
16
|
+
rr = DNS::Zone::RR.load('www IN A 10.2.3.1')
|
17
|
+
assert_instance_of DNS::Zone::RR::A, rr, 'should be instance of A RR'
|
18
|
+
assert_equal 'www', rr.label
|
19
|
+
assert_equal 'A', rr.type
|
20
|
+
assert_equal '10.2.3.1', rr.address
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_load_txt_rr
|
24
|
+
rr = DNS::Zone::RR.load('mytxt IN TXT "test text"')
|
25
|
+
assert_instance_of DNS::Zone::RR::TXT, rr, 'should be instance of TXT RR'
|
26
|
+
assert_equal 'mytxt', rr.label
|
27
|
+
assert_equal 'TXT', rr.type
|
28
|
+
assert_equal 'test text', rr.text
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_load_a_rr_with_options_hash
|
32
|
+
rr = DNS::Zone::RR.load(' IN A 10.2.3.1', { last_label: 'www' })
|
33
|
+
assert_equal 'www', rr.label
|
34
|
+
|
35
|
+
rr = DNS::Zone::RR.load('blog IN A 10.2.3.1', { last_label: 'www' })
|
36
|
+
assert_equal 'blog', rr.label
|
37
|
+
|
38
|
+
rr = DNS::Zone::RR.load('@ IN A 10.2.3.1', { last_label: 'mail' })
|
39
|
+
assert_equal '@', rr.label
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/test/zone_test.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'dns/zone/test_case'
|
2
|
+
|
3
|
+
class ZoneTest < DNS::Zone::TestCase
|
4
|
+
|
5
|
+
ZONE_EXAMPLE_SIMPLE =<<-EOL
|
6
|
+
$ORIGIN lividpenguin.com.
|
7
|
+
$TTL 3d
|
8
|
+
@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. (
|
9
|
+
2013101406 ; zone serial number
|
10
|
+
12h ; refresh ttl
|
11
|
+
15m ; retry ttl
|
12
|
+
3w ; expiry ttl
|
13
|
+
3h ; minimum ttl
|
14
|
+
)
|
15
|
+
|
16
|
+
; a more difficult ; comment ( its trying to break things!
|
17
|
+
|
18
|
+
@ IN NS ns0.lividpenguin.com.
|
19
|
+
@ IN NS ns1.lividpenguin.com.
|
20
|
+
@ IN NS ns2.lividpenguin.com.
|
21
|
+
|
22
|
+
@ IN A 77.75.105.197
|
23
|
+
@ IN AAAA 2a01:348::6:4d4b:69c5:0:1
|
24
|
+
|
25
|
+
foo IN TXT "part1""part2"
|
26
|
+
bar IN TXT ("part1 "
|
27
|
+
"part2 "
|
28
|
+
"part3")
|
29
|
+
EOL
|
30
|
+
|
31
|
+
def test_create_new_instance
|
32
|
+
assert DNS::Zone.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_load_simple_zone
|
36
|
+
zone = DNS::Zone.load(ZONE_EXAMPLE_SIMPLE)
|
37
|
+
assert_equal '3d', zone.ttl, 'check ttl matches example input'
|
38
|
+
assert_equal 'lividpenguin.com.', zone.origin, 'check origin matches example input'
|
39
|
+
assert_equal 8, zone.records.length, 'we should have 8 records (including SOA)'
|
40
|
+
|
41
|
+
p ''
|
42
|
+
zone.records.each do |rec|
|
43
|
+
p rec
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_extract_entry_from_one_line
|
48
|
+
entries = DNS::Zone.extract_entries(%Q{maiow IN TXT "purr"})
|
49
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
50
|
+
assert_equal 'maiow IN TXT "purr"', entries[0], 'entry should match expected'
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_extract_entry_should_ignore_comments
|
54
|
+
entries = DNS::Zone.extract_entries(%Q{maiow IN TXT "purr"; this is a comment})
|
55
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
56
|
+
assert_equal 'maiow IN TXT "purr"', entries[0], 'entry should match expected'
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_extract_entry_should_ignore_empty_lines
|
60
|
+
entries = DNS::Zone.extract_entries(%Q{\n\nmaiow IN TXT "purr";\n\n})
|
61
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
62
|
+
assert_equal 'maiow IN TXT "purr"', entries[0], 'entry should match expected'
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_extract_entry_using_parentheses_but_not_crossing_line_boundary
|
66
|
+
entries = DNS::Zone.extract_entries(%Q{maiow IN TXT ("part1" "part2")})
|
67
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
68
|
+
assert_equal 'maiow IN TXT "part1" "part2"', entries[0], 'entry should match expected'
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_extract_entry_crossing_line_boundary
|
72
|
+
entries = DNS::Zone.extract_entries(%Q{maiow1 IN TXT ("part1"\n "part2" )})
|
73
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
74
|
+
assert_equal 'maiow1 IN TXT "part1" "part2"', entries[0], 'entry should match expected'
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_extract_entry_soa_crossing_line_boundary
|
78
|
+
entries = DNS::Zone.extract_entries(%Q{
|
79
|
+
@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. (
|
80
|
+
2013101406 ; zone serial number
|
81
|
+
12h ; refresh ttl
|
82
|
+
15m ; retry ttl
|
83
|
+
3w ; expiry ttl
|
84
|
+
3h ; minimum ttl
|
85
|
+
)})
|
86
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
87
|
+
|
88
|
+
expected_soa = '@ IN SOA ns0.lividpenguin.com. luke.lividpenguin.com. 2013101406 12h 15m 3w 3h'
|
89
|
+
assert_equal expected_soa, entries[0], 'entry should match expected'
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_extract_entries_with_parentheses_crossing_multiple_line_boundaries
|
93
|
+
entries = DNS::Zone.extract_entries(%Q{maiow1 IN TXT (\n"part1"\n "part2"\n)})
|
94
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
95
|
+
assert_equal 'maiow1 IN TXT "part1" "part2"', entries[0], 'entry should match expected'
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_extract_entries_with_legal_but_crazy_parentheses_used
|
99
|
+
entries = DNS::Zone.extract_entries(%Q{maiow IN TXT (\n(\n("part1")\n \n("part2" \n("part3"\n)\n)\n)\n)})
|
100
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
101
|
+
assert_equal 'maiow IN TXT "part1" "part2" "part3"', entries[0], 'entry should match expected'
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_extract_entry_with_parentheses_in_character_string
|
105
|
+
entries = DNS::Zone.extract_entries(%Q{maiow IN TXT ("purr((maiow)")})
|
106
|
+
assert_equal 1, entries.length, 'we should have 1 entry'
|
107
|
+
assert_equal 'maiow IN TXT "purr((maiow)"', entries[0], 'entry should match expected'
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dns-zone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0.alpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Antins
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: inch
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: guard-bundler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: |
|
126
|
+
A Ruby library for building and parsing DNS zone files for use with
|
127
|
+
Bind and PowerDNS (with Bind backend) DNS servers.
|
128
|
+
email:
|
129
|
+
- luke@lividpenguin.com
|
130
|
+
executables: []
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- Gemfile
|
135
|
+
- Guardfile
|
136
|
+
- HISTORY.md
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- dns-zone.gemspec
|
140
|
+
- lib/dns/zone.rb
|
141
|
+
- lib/dns/zone/rr.rb
|
142
|
+
- lib/dns/zone/rr/a.rb
|
143
|
+
- lib/dns/zone/rr/aaaa.rb
|
144
|
+
- lib/dns/zone/rr/cname.rb
|
145
|
+
- lib/dns/zone/rr/mx.rb
|
146
|
+
- lib/dns/zone/rr/ns.rb
|
147
|
+
- lib/dns/zone/rr/record.rb
|
148
|
+
- lib/dns/zone/rr/soa.rb
|
149
|
+
- lib/dns/zone/rr/srv.rb
|
150
|
+
- lib/dns/zone/rr/txt.rb
|
151
|
+
- lib/dns/zone/test_case.rb
|
152
|
+
- lib/dns/zone/version.rb
|
153
|
+
- test/rr/a_test.rb
|
154
|
+
- test/rr/aaaa_test.rb
|
155
|
+
- test/rr/cname_test.rb
|
156
|
+
- test/rr/mx_test.rb
|
157
|
+
- test/rr/ns_test.rb
|
158
|
+
- test/rr/record_test.rb
|
159
|
+
- test/rr/soa_test.rb
|
160
|
+
- test/rr/srv_test.rb
|
161
|
+
- test/rr/txt_test.rb
|
162
|
+
- test/rr_test.rb
|
163
|
+
- test/version_test.rb
|
164
|
+
- test/zone_test.rb
|
165
|
+
homepage: https://github.com/lantins/dns-zone
|
166
|
+
licenses:
|
167
|
+
- MIT
|
168
|
+
metadata: {}
|
169
|
+
post_install_message:
|
170
|
+
rdoc_options: []
|
171
|
+
require_paths:
|
172
|
+
- lib
|
173
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - "~>"
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '1.9'
|
178
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - ">"
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: 1.3.1
|
183
|
+
requirements: []
|
184
|
+
rubyforge_project:
|
185
|
+
rubygems_version: 2.2.0
|
186
|
+
signing_key:
|
187
|
+
specification_version: 4
|
188
|
+
summary: A Ruby library for building and parsing DNS zone files.
|
189
|
+
test_files: []
|
190
|
+
has_rdoc:
|