File Coverage

blib/lib/Data/Validate/DNS/NAPTR/Regexp.pm
Criterion Covered Total %
statement 98 99 98.9
branch 41 42 97.6
condition 6 7 85.7
subroutine 10 10 100.0
pod 4 4 100.0
total 159 162 98.1


line stmt bran cond sub pod time code
1             package Data::Validate::DNS::NAPTR::Regexp;
2              
3             our $VERSION = '0.007';
4              
5 4     4   138460 use 5.008000;
  4         16  
  4         202  
6              
7 4     4   32 use strict;
  4         9  
  4         281  
8 4     4   24 use warnings;
  4         17  
  4         7145  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13              
14             our @EXPORT_OK = qw(is_naptr_regexp naptr_regexp_error);
15              
16             our @EXPORT = @EXPORT_OK;
17              
18             my $last_error;
19              
20             sub new {
21 48     48 1 65299 my ($class) = @_;
22              
23 48         252 return bless {}, $class;
24             }
25              
26             sub _set_error {
27 48     48   89 my ($where, $error) = @_;
28              
29 48 100       104 if ($where) {
30 24         55 $where->{error} = $error;
31             } else {
32 24         43 $last_error = $error;
33             }
34             }
35              
36             sub error {
37 72     72 1 106 my ($self) = @_;
38              
39 72 100       144 if ($self) {
40 48         249 return $self->{error};
41             } else {
42 24         120 return $last_error;
43             }
44             }
45              
46             sub naptr_regexp_error {
47 48     48 1 162 goto &error;
48             }
49              
50             sub is_naptr_regexp {
51 96     96 1 32929 my ($self, $string) = @_;
52              
53             # Called as a function?
54 96 100 66     701 if (defined $self && !ref $self) {
55 48         68 $string = $self;
56              
57 48         60 $self = undef;
58              
59 48         67 $last_error = undef;
60             } else {
61 48         121 $self->{error} = undef;
62             }
63              
64 96 50       228 if (!defined $string) {
65 0         0 return 1;
66             }
67              
68 96 100       397 if ($string =~ /\n/) {
69 4         16 _set_error($self, "Contains new-lines");
70              
71 4         19 return 0;
72             }
73              
74             # Convert from master-file format
75 92         225 $string = _cstring_from_text($self, $string);
76              
77 92 100       235 if (!defined $string) {
78 16         91 return 0;
79             }
80              
81             # Empty string okay
82 76 100       170 if (length $string == 0) {
83 2         8 return 2;
84             }
85              
86 74 100       183 if ($string =~ /\0/) {
87 4         21 _set_error($self, "Contains null bytes");
88              
89 4         30 return 0;
90             }
91              
92 70         269 $string =~ s/^(.)//;
93              
94 70         131 my $delim = $1;
95              
96 70 100       192 if ($delim =~ /^[0-9\\i\0]$/) {
97 10         36 _set_error($self, "Delimiter ($delim) cannot be a flag, digit or null");
98              
99 10         46 return 0;
100             }
101              
102 60         560 $delim = qr/\Q$delim\E/;
103              
104             # Convert double-backslashes to \0 for easy parsing.
105 60         125 $string =~ s/\\\\/\0/g;
106              
107             # Now anything preceeded by a '\' is an escape sequence and can be
108             # ignored.
109              
110 60 100       1088 unless ($string =~ /^
111             (.*) (?
112             (.*) (?
113             (.*)$/x
114             ) {
115 4         12 _set_error($self, "Bad syntax, missing replace/end delimiter");
116              
117 4         27 return 0;
118             }
119              
120 56   100     353 my ($find, $replace, $flags) = ($1, $2, ($3 || ''));
121              
122             # Extra delimiters? Broken
123 56         98 for my $f ($find, $replace, $flags) {
124 164 100       927 if ($f =~ /(?
125 2         6 _set_error($self, "Extra delimiters");
126              
127 2         12 return 0;
128             }
129             }
130              
131             # Count backrefs in replace and make sure it matches up.
132 54         229 my %brefs = map { $_ => 1 } $replace=~ /\\([0-9])/g;
  14         65  
133              
134             # And so ends our fun with escapes. Convert those nulls back to double
135             # backslashes
136 54         222 $_ =~ s/\0/\\\\/g for ($find, $replace, $flags);
137              
138             # Validate flags
139 54         11742 for my $f (split //, $flags) {
140 18 100       62 if ($f eq 'i') {
141             # Ok!
142             } else {
143 4         16 _set_error($self, "Bad flag: $f");
144              
145 4         26 return 0;
146             }
147             }
148              
149 50 100       151 if ($brefs{0}) {
150 2         8 _set_error($self, "Bad backref '0'");
151              
152 2         14 return 0;
153             }
154              
155             # Validate capture count
156 48         107 my $nsubs = _count_nsubs($find);
157              
158 48         143 my ($highest) = sort {$a <=> $b} keys %brefs;
  2         12  
159 48   100     218 $highest ||= 0;
160              
161 48 100       119 if ($nsubs < $highest) {
162 2         7 _set_error($self, "More backrefs in replacement than captures in match");
163              
164 2         14 return 0;
165             }
166              
167 46         750 return 3;
168             }
169              
170             # Convert master-file character string to data
171             sub _cstring_from_text {
172 92     92   141 my ($self, $string) = @_;
173              
174 92         102 my $ret;
175              
176             # look for escape sequences, one at a time.
177             # $1 is data before escape, $2 is \ if found, $3 is what's escaped
178 92         545 while ($string =~ /\G(.*?)(\\(\d{1,3}|.)?)?/g) {
179 6068         12241 my $before = $1;
180              
181             # Unescaped double quote?
182 6068 100       11566 if ($before =~ /"/) {
183 2         8 _set_error($self, 'Unescaped double quote');
184              
185 2         6 return;
186             }
187              
188 6066         6204 $ret .= $before;
189              
190             # Got an escape
191 6066 100       26151 if ($2) {
192 82         137 my $seq = $3;
193              
194 82 100       181 if (!defined $seq) {
195 2         8 _set_error($self, 'Trailing backslash');
196              
197 2         42 return;
198             }
199              
200             # Some byte? Take it
201 80 100       338 if ($seq !~ /\d/) {
    100          
    100          
202 44         174 $ret .= $seq;
203             } elsif ($seq !~ /\d\d\d/) {
204 4         14 _set_error($self, "Bad escape sequence '\\$seq'");
205              
206 4         13 return;
207             } elsif ($seq > 255) {
208 4         15 _set_error($self, "Escape sequence out of range '\\$seq'");
209              
210 4         11 return;
211             } else {
212             # Good, take it
213 28         146 $ret .= chr($seq);
214             }
215             }
216             }
217              
218 80 100       231 if (length $ret > 255) {
219 4         33 _set_error($self, "Must be less than 256 bytes");
220              
221 4         16 return;
222             }
223              
224 76         221 return $ret;
225             }
226              
227             # Count the number of captures in the RE
228             sub _count_nsubs {
229 52     52   2696 my ($regex) = @_;
230              
231             # Assume any ( not preceded by a \ is a capture start
232 52         219 my @captures = $regex =~ /(?
233              
234 52         162 return 0+@captures;
235             }
236              
237             1;
238             __END__