File Coverage

blib/lib/TinyDNS/Record.pm
Criterion Covered Total %
statement 86 91 94.5
branch 22 30 73.3
condition 17 30 56.6
subroutine 13 13 100.0
pod 1 9 11.1
total 139 173 80.3


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3            
4             TinyDNS::Record - Parse a single TinyDNS Record.
5            
6             =head1 DESCRIPTION
7            
8             This module provides an object API to a single TinyDNS record/line.
9            
10             It is not quite valid because:
11            
12             =over 8
13            
14             =item *
15             We ignore NS and SOA records, which Amazon would handle for us.
16            
17             =item *
18             Our TXT records handling uses "T" not ":".
19            
20             =item *
21             Our MX record handling allows a name to be set with no IP.
22            
23             =back
24            
25             There are probably other differences.
26            
27             =cut
28              
29             =head1 AUTHOR
30            
31             Steve Kemp <steve@steve.org.uk>
32            
33             =cut
34              
35             =head1 COPYRIGHT AND LICENSE
36            
37             Copyright (C) 2014 Steve Kemp <steve@steve.org.uk>.
38            
39             This code was developed for an online Git-based DNS hosting solution,
40             which can be found at:
41            
42             =over 8
43            
44             =item *
45             https://dns-api.com/
46            
47             =back
48            
49             This library is free software. You can modify and or distribute it under
50             the same terms as Perl itself.
51            
52             =cut
53              
54              
55 3     3   19 use strict;
  3         7  
  3         90  
56 3     3   15 use warnings;
  3         5  
  3         95  
57              
58             package TinyDNS::Record;
59              
60              
61 3     3   15 use Carp;
  3         4  
  3         280  
62              
63              
64             #
65             # Allow our object to treated as a string.
66             #
67 3     3   4698 use overload '""' => 'stringify';
  3         3131  
  3         20  
68              
69              
70              
71             =begin doc
72            
73             Constructor.
74            
75             Set the type of the object.
76            
77             =end doc
78            
79             =cut
80              
81             sub new
82             {
83 59     59 0 36885     my ( $proto, $line ) = (@_);
84 59   33     341     my $class = ref($proto) || $proto;
85              
86 59         94     my $self = {};
87 59         134     bless( $self, $class );
88              
89             #
90             # The record-type is the first character.
91             #
92 59         398     my $rec = substr( $line, 0, 1 );
93              
94             #
95             # Remove the record-type from the line
96             #
97 59         121     $line = substr( $line, 1 );
98              
99             #
100             # Tokenize - NOTE This is ignored for TXT records,
101             # (because a TXT record used for SPF might have an embedded
102             # ":" for example.)
103             #
104 59         331     my @data = split( /:/, $line );
105              
106             #
107             # Nasty parsing for each record type..
108             #
109             # We should do better.
110             #
111             #
112 59 100 66     382     if ( ( $rec eq '+' ) || ( $rec eq '=' ) )
    100 66        
    100 66        
    100          
    100          
    50          
113                 {
114              
115             # name : ipv4 : ttl
116 25         69         $self->{ 'type' } = "A";
117 25         46         $self->{ 'name' } = $data[0];
118 25         42         $self->{ 'value' } = $data[1];
119 25   50     81         $self->{ 'ttl' } = $data[2] || 300;
120                 }
121                 elsif ( $rec eq '6' )
122                 {
123              
124             # name : ipv6 : ttl
125 21         53         $self->{ 'type' } = "AAAA";
126 21         47         $self->{ 'name' } = $data[0];
127 21   50     70         $self->{ 'ttl' } = $data[2] || 300;
128              
129             #
130             # Convert an IPv6 record of the form:
131             # "200141c8010b01010000000000000010"
132             # to the expected value:
133             # "2001:41c8:010b:0101:0000:0000:0000:0010".
134             #
135 21         32         my $ipv6 = $data[1];
136 21         196         my @tmp = ( $ipv6 =~ m/..../g );
137 21         111         $self->{ 'value' } = join( ":", @tmp );
138                 }
139                 elsif ( $rec eq '@' )
140                 {
141              
142             #
143             # @xxx:name:ttl
144             # @xxx:[ip]:name:ttl
145             #
146 6 100       22         if ( scalar(@data) == 4 )
147                     {
148 3         92             $self->{ 'type' } = "MX";
149 3         7             $self->{ 'name' } = $data[0];
150 3   50     16             $self->{ 'priority' } = $data[3] || "15";
151 3   50     20             $self->{ 'ttl' } = $data[4] || 300;
152 3         15             $self->{ 'value' } = $self->{ 'priority' } . " " . $data[2];
153                     }
154 6 100       24         if ( scalar(@data) == 3 )
155                     {
156 3         10             $self->{ 'type' } = "MX";
157 3         9             $self->{ 'name' } = $data[0];
158 3   50     14             $self->{ 'priority' } = $data[2] || "15";
159 3   50     25             $self->{ 'ttl' } = $data[3] || 300;
160 3         13             $self->{ 'value' } = $self->{ 'priority' } . " " . $data[1];
161                     }
162                 }
163                 elsif ( ( $rec eq 'c' ) || ( $rec eq 'C' ) )
164                 {
165              
166             #
167             # name : dest : [ttl]
168             #
169 2         6         $self->{ 'type' } = "CNAME";
170 2         5         $self->{ 'name' } = $data[0];
171 2         5         $self->{ 'value' } = $data[1];
172 2   50     7         $self->{ 'ttl' } = $data[2] || 300;
173                 }
174                 elsif ( ( $rec eq 't' ) || ( $rec eq 'T' ) )
175                 {
176              
177             #
178             # name : "data " : [TTL]
179             #
180 3 50       25         if ( $line =~ /([^:]+):"([^"]+)":([0-9]+)$/ )
181                     {
182 3         7             $self->{ 'type' } = "TXT";
183 3         10             $self->{ 'name' } = $1;
184 3         12             $self->{ 'value' } = "\"$2\"";
185 3         10             $self->{ 'ttl' } = $3;
186                     }
187                     else
188                     {
189 0         0             die "Invalid TXT record - $line\n";
190                     }
191                 }
192                 elsif ( $rec eq '^' )
193                 {
194              
195             #
196             # ptr : "rdns " : [TTL]
197             #
198 2         4         $self->{ 'type' } = "PTR";
199 2         5         $self->{ 'name' } = $data[0];
200 2         3         $self->{ 'value' } = $data[1];
201 2   100     13         $self->{ 'ttl' } = $data[2] || 300;
202                 }
203                 else
204                 {
205 0         0         carp "Unknown record type [$rec]: $line";
206 0         0         return undef;
207                 }
208 59         757     return $self;
209              
210             }
211              
212              
213             =begin doc
214            
215             Is the given record valid? If it has a type then it must be.
216            
217             =end doc
218            
219             =cut
220              
221             sub valid
222             {
223 49     49 0 22786     my ($self) = (@_);
224              
225 49 50       245     return ( $self->{ 'type' } ? 1 : 0 );
226             }
227              
228              
229             =begin doc
230            
231             Get the type of record this object holds.
232            
233             =end doc
234            
235             =cut
236              
237             sub type
238             {
239 158     158 0 193     my ($self) = (@_);
240              
241 158         660     return ( $self->{ 'type' } );
242             }
243              
244              
245             =begin doc
246            
247             Get the TTL of this object.
248            
249             =end doc
250            
251             =cut
252              
253             sub ttl
254             {
255 153     153 0 179     my ($self) = (@_);
256 153   50     948     return ( $self->{ 'ttl' } || 300 );
257             }
258              
259              
260             =begin doc
261            
262             Get the name of this record.
263            
264             =end doc
265            
266             =cut
267              
268             sub name
269             {
270 158     158 1 180     my ($self) = (@_);
271 158         499     return ( $self->{ 'name' } );
272             }
273              
274              
275             =begin doc
276            
277             Get the value of this record.
278            
279             =end doc
280            
281             =cut
282              
283             sub value
284             {
285 162     162 0 194     my ($self) = (@_);
286              
287 162         481     return ( $self->{ 'value' } );
288             }
289              
290              
291             =begin doc
292            
293             Add a new value to the existing record.
294            
295             This is added by the L<TinyDNS::Reader::Merged> module.
296            
297             =end doc
298            
299             =cut
300              
301             sub add
302             {
303 1     1 0 2     my ( $self, $addition ) = (@_);
304              
305 1         3     my $value = $self->{ 'value' };
306 1 50       6     if ( ref \$value eq "SCALAR" )
307                 {
308 1         2         my $x;
309 1         3         push( @$x, $value );
310 1         2         push( @$x, $addition );
311 1         5         $self->{ 'value' } = $x;
312                 }
313                 else
314                 {
315 0         0         push( @$value, $addition );
316 0         0         $self->{ 'value' } = $value;
317                 }
318             }
319              
320              
321             =begin doc
322            
323             Conver the record to a string, suitable for printing.
324            
325             =end doc
326            
327             =cut
328              
329             sub stringify
330             {
331 69     69 0 1283     my ($self) = (@_);
332 69         97     my $txt = "";
333              
334 69 50       175     $txt .= ( "Type " . $self->type() . "\n" ) if ( $self->type() );
335 69 50       170     $txt .= ( " Name:" . $self->name() . "\n" ) if ( $self->name() );
336 69 50       191     $txt .= ( " Value:" . $self->value() . "\n" ) if ( $self->value() );
337 69 50       144     $txt .= ( " TTL:" . $self->ttl() . "\n" ) if ( $self->ttl() );
338              
339             }
340              
341             sub hash
342             {
343 10     10 0 13     my ($self) = (@_);
344              
345 10         10     my $hash;
346 10         16     $hash .= $self->type();
347 10         21     $hash .= $self->name();
348 10         20     $hash .= $self->value();
349 10         21     $hash .= $self->ttl();
350              
351 10         27     return ($hash);
352             }
353              
354             1;
355