File Coverage

blib/lib/Net/DNS/Text.pm
Criterion Covered Total %
statement 76 76 100.0
branch 14 14 100.0
path n/a
condition 2 2 100.0
subroutine 15 15 100.0
pod 7 7 100.0
total 114 114 100.0


line stmt bran path cond sub pod time code
1               package Net::DNS::Text;
2                
3 28       28   216242 use strict;
  28           48  
  28           902  
4 28       28   133 use warnings;
  28           42  
  28           1993  
5                
6               our $VERSION = (qw$Id: Text.pm 2043 2026-01-14 13:35:59Z willem $)[2];
7                
8                
9               =head1 NAME
10                
11               Net::DNS::Text - DNS text representation
12                
13               =head1 SYNOPSIS
14                
15               use Net::DNS::Text;
16                
17               $object = Net::DNS::Text->new('example');
18               $string = $object->string;
19                
20               $object = Net::DNS::Text->decode( \$data, $offset );
21               ( $object, $next ) = Net::DNS::Text->decode( \$data, $offset );
22                
23               $data = $object->encode;
24               $text = $object->value;
25                
26               =head1 DESCRIPTION
27                
28               The C module implements a class of text objects
29               with associated class and instance methods.
30                
31               Each text object instance has a fixed identity throughout its
32               lifetime.
33                
34               =cut
35                
36                
37 28       28   534 use integer;
  28           73  
  28           149  
38 28       28   644 use Carp;
  28           46  
  28           2758  
39                
40                
41 28           51 use constant ASCII => ref eval {
42 28           104 require Encode;
43 28           140 Encode::find_encoding('ascii');
44 28       28   176 };
  28           52  
45                
46 28           46 use constant UTF8 => scalar eval { ## not UTF-EBCDIC [see Unicode TR#16 3.6]
47 28           37393 Encode::encode_utf8( chr(182) ) eq pack( 'H*', 'C2B6' );
48 28       28   3698 };
  28           81  
49                
50                
51               =head1 METHODS
52                
53               =head2 new
54                
55               $object = Net::DNS::Text->new('example');
56                
57               Creates a text object which encapsulates a single character
58               string component of a resource record.
59                
60               Arbitrary single-byte characters can be represented by \ followed
61               by exactly three decimal digits. Such characters are devoid of
62               any special meaning.
63                
64               A character preceded by \ represents itself, without any special
65               interpretation.
66                
67               =cut
68                
69               my ( %escape, %escapeUTF8, %unescape ); ## precalculated escape tables
70                
71               sub new {
72 479       479 1 10924 my $self = bless [], shift;
73 479           705 local $_ = &_encode_utf8;
74                
75 477           968 s/^\042(.*)\042$/$1/s; # strip paired quotes
76                
77 477           693 s/\134([\060-\071]{3})/$unescape{$1}/eg; # restore numeric escapes
  218           425  
78 477           600 s/\134([^\134])/$1/g; # restore character escapes
79 477           593 s/\134\134/\134/g; # restore escaped escapes
80                
81 477           848 while ( length $_ > 255 ) {
82 2           3 my $chunk = substr( $_, 0, 255 ); # carve into chunks
83 2           7 $chunk =~ s/[\300-\377][\200-\277]*$//;
84 2           4 push @$self, $chunk;
85 2           5 substr( $_, 0, length $chunk ) = '';
86               }
87 477           816 push @$self, $_;
88                
89 477           1260 return $self;
90               }
91                
92                
93               =head2 decode
94                
95               $object = Net::DNS::Text->decode( \$buffer, $offset );
96                
97               ( $object, $next ) = Net::DNS::Text->decode( \$buffer, $offset );
98                
99               Creates a text object which represents the decoded data at the
100               indicated offset within the data buffer.
101                
102               The argument list consists of a reference to a scalar containing
103               the wire-format data and offset of the text data.
104                
105               The returned offset value indicates the start of the next item in
106               the data buffer.
107                
108               =cut
109                
110               sub decode {
111 16607       16607 1 20500 my $class = shift;
112 16607           14974 my $buffer = shift; # reference to data buffer
113 16607     100     26535 my $offset = shift || 0; # offset within buffer
114 16607           15102 my $size = shift; # specify size of unbounded text
115                
116 16607 100         20833 unless ( defined $size ) {
117 16599           21502 $size = unpack "\@$offset C", $$buffer;
118 16599           15961 $offset++;
119               }
120                
121 16607           15305 my $next = $offset + $size;
122 16607 100         20496 croak 'corrupt wire-format data' if $next > length $$buffer;
123                
124 16605           44123 my $self = bless [unpack( "\@$offset a$size", $$buffer )], $class;
125                
126 33210 100         29622 return wantarray ? ( $self, $next ) : $self;
127               }
128                
129                
130               =head2 encode
131                
132               $data = $object->encode;
133                
134               Returns the wire-format encoded representation of the text object
135               suitable for inclusion in a DNS packet buffer.
136                
137               =cut
138                
139               sub encode {
140 360       360 1 345 my $self = shift;
141 360           449 return join '', map { pack( 'C a*', length $_, $_ ) } @$self;
  360           1044  
142               }
143                
144                
145               =head2 raw
146                
147               $data = $object->raw;
148                
149               Returns the wire-format encoded representation of the text object
150               without the explicit length field.
151                
152               =cut
153                
154               sub raw {
155 31       31 1 578 my $self = shift;
156 31           50 return join '', map { pack( 'a*', $_ ) } @$self;
  31           171  
157               }
158                
159                
160               =head2 value
161                
162               $value = $text->value;
163                
164               Character string representation of the text object.
165                
166               =cut
167                
168               sub value {
169 59 100     59 1 883 return unless defined wantarray;
170 33           41 my $self = shift;
171 33           95 return _decode_utf8( join '', @$self );
172               }
173                
174                
175               =head2 string
176                
177               $string = $text->string;
178                
179               Conditionally quoted RFC1035 zone file representation of the text object.
180                
181               =cut
182                
183               sub string {
184 40       40 1 1425 my $self = shift;
185                
186 40           67 my @s = map { split '', $_ } @$self; # escape special and ASCII non-printable
  41           163  
187 40           54 my $s = _decode_utf8( join '', map { $escape{$_} } @s );
  652           851  
188 40 100         538 return $s =~ /[ \t\n\r\f();]|^$/ ? qq("$s") : $s; # quote special characters and empty string
189               }
190                
191                
192               =head2 unicode
193                
194               $string = $text->unicode;
195                
196               Conditionally quoted Unicode representation of the text object.
197                
198               =cut
199                
200               sub unicode {
201 898       898 1 1058 my $self = shift;
202                
203 898           1151 my @s = map { split '', $_ } @$self; # escape special and non-printable
  898           20911  
204 898           2313 my $s = _decode_utf8( join '', map { $escapeUTF8{$_} } @s );
  196416           226074  
205 898 100         42246 return $s =~ /[ \t\n\r\f();]|^$/ ? qq("$s") : $s; # quote special characters and empty string
206               }
207                
208                
209               ########################################
210                
211               # perlcc: address of encoding objects must be determined at runtime
212               my $ascii = ASCII ? Encode::find_encoding('ascii') : undef; # Osborn's Law:
213               my $utf8 = UTF8 ? Encode::find_encoding('utf8') : undef; # Variables won't; constants aren't.
214                
215                
216               sub _decode_utf8 { ## UTF-8 to perl internal encoding
217 971       971   1511 local $_ = shift;
218                
219               # partial transliteration for non-ASCII character encodings
220               tr
221 971           970 [\040-\176\000-\377]
222               [ !"#$%&'()*+,\-./0-9:;<=>?@A-Z\[\\\]^_`a-z{|}~?] unless ASCII;
223                
224 971           1169 my $z = length($_) - length($_); # pre-5.18 taint workaround
225 971           3957 return ASCII ? substr( ( UTF8 ? $utf8 : $ascii )->decode($_), $z ) : $_;
226               }
227                
228                
229               sub _encode_utf8 { ## perl internal encoding to UTF-8
230 479       479   616 local $_ = shift;
231 479 100         1050 croak 'argument undefined' unless defined $_;
232                
233               # partial transliteration for non-ASCII character encodings
234               tr
235 477           484 [ !"#$%&'()*+,\-./0-9:;<=>?@A-Z\[\\\]^_`a-z{|}~]
236               [\040-\176] unless ASCII;
237                
238 477           669 my $z = length($_) - length($_); # pre-5.18 taint workaround
239 477           2454 return ASCII ? substr( ( UTF8 ? $utf8 : $ascii )->encode($_), $z ) : $_;
240               }
241                
242                
243               %escape = eval { ## precalculated ASCII escape table
244               my %table = map { ( chr($_) => chr($_) ) } ( 0 .. 127 );
245                
246               foreach my $n ( 0 .. 31, 34, 92, 127 .. 255 ) { # numerical escape
247               my $codepoint = sprintf( '%03u', $n );
248                
249               # transliteration for non-ASCII character encodings
250               $codepoint =~ tr [0-9] [\060-\071];
251                
252               $table{chr($n)} = pack 'C a3', 92, $codepoint;
253               }
254                
255               return %table;
256               };
257                
258               %escapeUTF8 = eval { ## precalculated UTF-8 escape table
259               my @octet = UTF8 ? ( 128 .. 191, 194 .. 254 ) : ();
260               return ( %escape, map { ( chr($_) => chr($_) ) } @octet );
261               };
262                
263                
264               %unescape = eval { ## precalculated numeric escape table
265               my %table;
266                
267               foreach my $n ( 0 .. 255 ) {
268               my $key = sprintf( '%03u', $n );
269                
270               # transliteration for non-ASCII character encodings
271               $key =~ tr [0-9] [\060-\071];
272                
273               $table{$key} = pack 'C', $n;
274               }
275               $table{"\060\071\062"} = pack 'C2', 92, 92; # escaped escape
276                
277               return %table;
278               };
279                
280                
281               1;
282               __END__