File Coverage

Parser.yp
Criterion Covered Total %
statement 91 97 93.8
branch 70 80 87.5
condition n/a
subroutine 28 31 90.3
pod 4 4 100.0
total 193 212 91.0


line stmt bran cond sub pod time code
1             %{#
2             # Validate::SPF::Parser source file
3             #
4             # Author: Anton Gerasimov
5             #
6              
7 10     10   5435 use Regexp::Common qw( net );
  10         22508  
  10         54  
8              
9             my $input;
10              
11             my %errors = (
12             E_DEFAULT => "Just error",
13             E_SYNTAX => "Syntax error near token '%s'",
14             E_INVALID_VERSION => "Invalid SPF version",
15             E_IPADDR_EXPECTED => "Expected ip or network address",
16             E_DOMAIN_EXPECTED => "Expected domain name",
17             E_UNEXPECTED_BITMASK => "Unexpected bitmask",
18             E_UNEXPECTED_IPADDR => "Unexpected ip address",
19             E_UNEXPECTED_DOMAIN => "Unexpected domain name",
20             );
21              
22             %}
23              
24             %%
25              
26 10     10 1 330321 spf
27             : chunks
28 10 50   61   56 { $_[1] }
  61         123  
29             ;
30              
31             version
32             : VERSION
33             {
34 3 100   3   13 $_[1] eq 'v=spf1' and
35             return $_[0]->_ver_generic( $_[1] );
36              
37 2         7 $_[0]->raise_error( 'E_INVALID_VERSION', $_[1] );
38             }
39             ;
40              
41             chunks
42             : chunks chunk
43 0 0   0   0 { push( @{$_[1]}, $_[2] ) if defined $_[2]; $_[1] }
  0         0  
  0         0  
44             | chunk
45 61 50   61   226 { defined $_[1] ? [ $_[1] ] : [ ] }
46             ;
47              
48             chunk
49             : version
50             | mechanism
51             | modifier
52             ;
53              
54             mechanism
55             : with_ipaddress
56             | with_domain_bitmask
57             | with_bitmask
58             | with_domain
59             ;
60              
61             modifier
62             : MODIFIER '=' DOMAIN
63             {
64 0     0   0 $_[0]->_mod_generic( $_[1], $_[3] );
65             }
66             ;
67              
68             # ptr, exists, include, mx, a, all
69             with_domain
70             : MECHANISM
71             {
72 6 100   6   49 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $_[1] )
73             if $_[1] =~ /ip[46]/i;
74 6 50       23 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[1] )
75             if $_[1] =~ /\A(exists|include)\Z/i;
76              
77 6 100       45 $_[0]->_mech_domain( '+', $_[1], $_[1] =~ /all/i ? undef : '@' );
78             }
79             | QUALIFIER MECHANISM
80             {
81 23 100   23   158 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $_[1] . $_[2] )
82             if $_[2] =~ /ip[46]/i;
83 23 100       89 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[1] . $_[2] )
84             if $_[2] =~ /\A(exists|include)\Z/i;
85              
86 23 100       177 $_[0]->_mech_domain( $_[1], $_[2], $_[2] =~ /all/i ? undef : '@' );
87             }
88             | MECHANISM ':' DOMAIN
89             {
90 7     7   33 my $ctx = $_[1] . ':' . $_[3];
91              
92 7 100       63 $_[0]->raise_error( 'E_UNEXPECTED_DOMAIN', $ctx )
93             if $_[1] =~ /all/i;
94              
95 7         40 $_[0]->_mech_domain( '+', $_[1], $_[3] );
96             }
97             | QUALIFIER MECHANISM ':' DOMAIN
98             {
99 14     14   55 my $ctx = $_[1] . $_[2] . ':' . $_[4];
100              
101 14 100       81 $_[0]->raise_error( 'E_UNEXPECTED_DOMAIN', $ctx )
102             if $_[2] =~ /all/i;
103              
104 14         74 $_[0]->_mech_domain( $_[1], $_[2], $_[4] );
105             }
106             ;
107              
108             # mx, a
109             with_bitmask
110             : MECHANISM '/' BITMASK
111             {
112 4     4   17 my $ctx = $_[1] . '/' . $_[3];
113              
114 4 100       45 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $ctx )
115             if $_[1] =~ /ip[46]/i;
116              
117 4 100       39 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
118             if $_[1] =~ /\A(ptr|all|exists|include)\Z/i;
119              
120 4         21 $_[0]->_mech_domain_bitmask( '+', $_[1], '@', $_[3] );
121             }
122             | QUALIFIER MECHANISM '/' BITMASK
123             {
124 6     6   29 my $ctx = $_[1] . $_[2] . '/' . $_[4];
125              
126 6 50       41 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $ctx )
127             if $_[2] =~ /ip[46]/i;
128              
129 6 100       59 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
130             if $_[2] =~ /\A(ptr|all|exists|include)\Z/i;
131              
132 6         35 $_[0]->_mech_domain_bitmask( $_[1], $_[2], '@', $_[4] );
133             }
134             ;
135              
136             # mx, a
137             with_domain_bitmask
138             : MECHANISM ':' DOMAIN '/' BITMASK
139             {
140 5     5   29 my $ctx = $_[1] . ':' . $_[3] . '/' . $_[5];
141              
142 5 100       53 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
143             if $_[1] =~ /\A(ptr|all|exists|include)\Z/i;
144              
145 5         33 $_[0]->_mech_domain_bitmask( '+', $_[1], $_[3], $_[5] );
146             }
147             | QUALIFIER MECHANISM ':' DOMAIN '/' BITMASK
148             {
149 6     6   30 my $ctx = $_[1] . $_[2] . ':' . $_[4] . '/' . $_[6];
150              
151 6 100       61 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
152             if $_[2] =~ /\A(ptr|all|exists|include)\Z/i;
153              
154 6         35 $_[0]->_mech_domain_bitmask( $_[1], $_[2], $_[4], $_[6] );
155             }
156             ;
157              
158             # ip4, ip6
159             with_ipaddress
160             : MECHANISM ':' IPADDRESS
161             {
162 8     8   36 my $ctx = $_[1] . ':' . $_[3];
163              
164 8 100       155 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
165             if $_[1] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
166              
167 8         46 $_[0]->_mech_ipaddr_bitmask( '+', $_[1], $_[3], undef );
168             }
169             | QUALIFIER MECHANISM ':' IPADDRESS
170             {
171 10     10   46 my $ctx = $_[1] . $_[2] . ':' . $_[4];
172              
173 10 100       105 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
174             if $_[2] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
175              
176 10         59 $_[0]->_mech_ipaddr_bitmask( $_[1], $_[2], $_[4], undef );
177             }
178             | MECHANISM ':' IPADDRESS '/' BITMASK
179             {
180 4     4   23 my $ctx = $_[1] . ':' . $_[3] . '/' . $_[5];
181              
182 4 100       45 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
183             if $_[1] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
184              
185 4         25 $_[0]->_mech_ipaddr_bitmask( '+', $_[1], $_[3], $_[5] );
186             }
187             | QUALIFIER MECHANISM ':' IPADDRESS '/' BITMASK
188             {
189 14     14   71 my $ctx = $_[1] . $_[2] . ':' . $_[4] . '/' . $_[6];
190              
191 14 100       183 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
192             if $_[2] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
193              
194 14         103 $_[0]->_mech_ipaddr_bitmask( $_[1], $_[2], $_[4], $_[6] );
195             }
196 10         1509 ;
197              
198             %%
199              
200             sub parse {
201 10     117 1 277 my ( $self, $text ) = @_;
  117         372387  
202              
203 117         867 $input = $self->YYData->{INPUT} = $text;
204 117         282 delete $self->YYData->{ERRMSG};
205              
206 117         712 return $self->YYParse( yylex => \&_lexer, yyerror => \&_error );
207             }
208              
209             sub error {
210 57     57 1 8902 my ( $self ) = @_;
211 57         151 return $self->YYData->{ERRMSG};
212             }
213              
214             sub _build_error {
215 57     57   104 my ( $self, $code, $context, @extra ) = @_;
216              
217 57 50       174 $code = 'E_DEFAULT' unless exists $errors{$code};
218              
219 57         492 $self->YYData->{ERRMSG} = {
220             text => sprintf( $errors{$code} => @extra ),
221             code => $code,
222             context => $context,
223             };
224             }
225              
226             sub raise_error {
227 49     49 1 114 my ( $self, @params ) = @_;
228              
229 49         137 $self->_build_error( @params );
230 49         283 $self->YYError;
231             }
232              
233             sub _error {
234 57     57   75 my ( $self ) = @_;
235              
236 57 100       134 unless ( exists $self->YYData->{ERRMSG} ) {
237 8         68 substr( $input, index( $input, $self->YYCurval ), 0, '<*>' );
238              
239 8         31 $self->_build_error( 'E_SYNTAX', $input, $self->YYCurval );
240             }
241              
242 57         109 return;
243             }
244              
245             sub _lexer {
246 489     489   483 my ( $parser ) = @_;
247              
248 489         988 $parser->YYData->{INPUT} =~ s/^\s*//;
249              
250 489         1146 for ( $parser->YYData->{INPUT} ) {
251             # printf( "[debug] %s\n", $_ );
252              
253 489 100       1192 s/^(v\=spf\d)\b//i
254             and return ( 'VERSION', $1 );
255              
256 486 100       1179 s/^(\/)\b//i
257             and return ( '/', '/' );
258 447 100       1076 s/^(\:)\b//i
259             and return ( ':', ':' );
260 378 100       700 s/^(\=)\b//i
261             and return ( '=', '=' );
262              
263             # qualifiers
264 377 100       1433 s/^([-~\+\?])\b//i
265             and return ( 'QUALIFIER', $1 );
266              
267             # mechanisms
268 303 100       1468 s/^(all|ptr|a|mx|ip4|ip6|exists|include)\b//i
269             and return ( 'MECHANISM', $1 );
270              
271             # modifiers
272 195 50       416 s/^(redirect|exp)\b//i
273             and return ( 'MODIFIER', $1 );
274              
275 195 100       1322 s/^($RE{net}{IPv4}{dec}|$RE{net}{IPv6}{-sep=>':'})\b//i
276             and return ( 'IPADDRESS', $1 );
277              
278 159 100       57097 s/^([_\.a-z\d][\-a-z\d]*\.[\.\-a-z\d]*[a-z\d]?)\b//i
279             and return ( 'DOMAIN', $1 );
280              
281 127 100       735 s/^(\d{1,3})\b//i
282             and return ( 'BITMASK', $1 );
283              
284             # garbage
285 88 100       470 s/^(.+)\b//i
286             and return ( 'UNKNOWN', $1 );
287             }
288              
289             # EOF
290 81         345 return ( '', undef );
291             }
292              
293             # generic modifier
294             sub _mod_generic {
295 0     0   0 my ( $self, $mod, $domain ) = @_;
296              
297             return +{
298 0 0       0 type => 'mod',
299             modifier => lc $mod,
300             (
301             $domain
302             ? ( domain => $domain ) :
303             ( )
304             ),
305             };
306             }
307              
308             # generic version
309             sub _ver_generic {
310 1     1   1 my ( $self, $ver ) = @_;
311              
312             return +{
313 1         5 type => 'ver',
314             version => lc $ver,
315             };
316             }
317              
318              
319             # generic mechanism
320             sub _mech_generic {
321 107     107   192 my ( $self, $qualifier, $mech, $domain, $ipaddr, $bitmask ) = @_;
322              
323             return +{
324 107 100       1119 type => 'mech',
    100          
    100          
    100          
325             qualifier => $qualifier,
326             mechanism => lc $mech,
327             (
328             $domain
329             ? ( domain => $domain ) :
330             ( )
331             ),
332             (
333             $ipaddr
334             ? ( ( defined $bitmask ? 'network' : 'ipaddress' ) => $ipaddr )
335             : ( )
336             ),
337             (
338             defined $bitmask
339             ? ( bitmask => $bitmask )
340             : ( )
341             ),
342             };
343             }
344              
345             sub _mech_domain {
346 50     50   108 my ( $self, $qualifier, $mech, $domain ) = @_;
347              
348 50         161 return $self->_mech_generic( $qualifier, $mech, $domain, undef, undef );
349             }
350              
351             sub _mech_domain_bitmask {
352 21     21   50 my ( $self, $qualifier, $mech, $domain, $bitmask ) = @_;
353              
354 21         104 return $self->_mech_generic( $qualifier, $mech, $domain, undef, $bitmask );
355             }
356              
357             sub _mech_ipaddr_bitmask {
358 36     36   109 my ( $self, $qualifier, $mech, $ipaddr, $bitmask ) = @_;
359              
360 36         122 return $self->_mech_generic( $qualifier, $mech, undef, $ipaddr, $bitmask );
361             }