File Coverage

Parser.yp
Criterion Covered Total %
statement 110 115 95.6
branch 84 92 91.3
condition 4 4 100.0
subroutine 35 37 94.5
pod 4 4 100.0
total 237 252 94.0


line stmt bran cond sub pod time code
1             %{#
2             # Validate::SPF::Parser source file
3             #
4             # Author: Anton Gerasimov
5             #
6              
7 13     13   6770 use Regexp::Common qw( net );
  13         28459  
  13         65  
8 13     13   42822 use utf8;
  13         121  
  13         53  
9              
10             binmode( STDOUT, ':utf8' );
11              
12             my $input;
13              
14             my %errors = (
15             E_DEFAULT => "Just error",
16             E_SYNTAX => "Syntax error near token '%s'",
17             E_INVALID_VERSION => "Invalid SPF version",
18             E_IPADDR_EXPECTED => "Expected ip or network address",
19             E_DOMAIN_EXPECTED => "Expected domain name",
20             E_UNEXPECTED_BITMASK => "Unexpected bitmask",
21             E_UNEXPECTED_IPADDR => "Unexpected ip address",
22             E_UNEXPECTED_DOMAIN => "Unexpected domain name",
23             );
24              
25             %}
26              
27             %%
28              
29 13     13 1 454959 spf
30             : chunks
31 13 50   67   72 { $_[1] }
  67         132  
32             ;
33              
34             version
35             : VERSION
36             {
37 2 100   2   13 $_[1] eq 'v=spf1' and
38             return $_[0]->_ver_generic( $_[1] );
39              
40 1         5 $_[0]->raise_error( 'E_INVALID_VERSION', $_[1] );
41             }
42             ;
43              
44             chunks
45             : chunks chunk
46 0 0   0   0 { push( @{$_[1]}, $_[2] ) if defined $_[2]; $_[1] }
  0         0  
  0         0  
47             | chunk
48 67 100   67   213 { defined $_[1] ? [ $_[1] ] : [ ] }
49             ;
50              
51             chunk
52             : version
53             | mechanism
54             | modifier
55             ;
56              
57             mechanism
58             : with_ipaddress
59             | with_domain_bitmask
60             | with_bitmask
61             | with_domain
62             ;
63              
64             modifier
65             : LITERAL
66             {
67             # print "got (LITERAL): $_[1]\n";
68              
69             # for known literals - specific error
70 6 100   6   59 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[1] )
71             if $_[1] =~ /\A(redirect|exp)\Z/i;
72              
73             # for unknown literals - syntax error
74 6         27 $_[0]->YYError;
75              
76 6         14 return;
77             }
78             | LITERAL '=' DOMAIN
79             {
80             # print "got (LITERAL_DOMAIN): $_[1] = $_[3]\n";
81              
82 3 100   3   31 return unless $_[1] =~ /\A(redirect|exp)\Z/i;
83              
84 2         12 return $_[0]->_mod_generic( $_[1], $_[3] );
85             }
86             | LITERAL '=' LITERAL
87             {
88             # print "got (LITERAL_LITERAL): $_[1] = $_[3]\n";
89              
90             # looks like "version"
91 3 100   3   12 if ( $_[1] eq 'v' ) {
92 2         8 my $ctx = $_[1] . '=' . $_[3];
93              
94 2 50       5 return $_[0]->_ver_generic( $ctx ) if $_[3] eq 'spf1';
95              
96 2         9 $_[0]->raise_error( 'E_INVALID_VERSION', $ctx );
97             }
98              
99 3         8 return;
100             }
101             | LITERAL '=' IPADDRESS
102             {
103             # print "got (LITERAL_IPADDRESS): $_[1] = $_[3]\n";
104              
105             # known literals
106 5 100   5   58 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[3] )
107             if $_[1] =~ /\A(redirect|exp)\Z/i;
108              
109 5         9 return;
110             }
111             | LITERAL '=' IPADDRESS '/' BITMASK
112             {
113             # print "got (LITERAL_IPADDRESS_BITMASK): $_[1] = $_[3] / $_[5]\n";
114              
115             # known literals
116 5 100   5   54 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[3] . '/' . $_[5] )
117             if $_[1] =~ /\A(redirect|exp)\Z/i;
118              
119 5         18 return;
120             }
121             ;
122              
123             # ptr, exists, include, mx, a, all
124             with_domain
125             : MECHANISM
126             {
127 6 100   6   62 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $_[1] )
128             if $_[1] =~ /ip[46]/i;
129 6 50       23 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[1] )
130             if $_[1] =~ /\A(exists|include)\Z/i;
131              
132 6 100       49 $_[0]->_mech_domain( '+', $_[1], $_[1] =~ /all/i ? undef : '@' );
133             }
134             | QUALIFIER MECHANISM
135             {
136 23 100   23   137 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $_[1] . $_[2] )
137             if $_[2] =~ /ip[46]/i;
138 23 100       81 $_[0]->raise_error( 'E_DOMAIN_EXPECTED', $_[1] . $_[2] )
139             if $_[2] =~ /\A(exists|include)\Z/i;
140              
141 23 100       155 $_[0]->_mech_domain( $_[1], $_[2], $_[2] =~ /all/i ? undef : '@' );
142             }
143             | MECHANISM ':' DOMAIN
144             {
145 7     7   26 my $ctx = $_[1] . ':' . $_[3];
146              
147 7 100       41 $_[0]->raise_error( 'E_UNEXPECTED_DOMAIN', $ctx )
148             if $_[1] =~ /all/i;
149              
150 7         32 $_[0]->_mech_domain( '+', $_[1], $_[3] );
151             }
152             | QUALIFIER MECHANISM ':' DOMAIN
153             {
154 14     14   50 my $ctx = $_[1] . $_[2] . ':' . $_[4];
155              
156 14 100       68 $_[0]->raise_error( 'E_UNEXPECTED_DOMAIN', $ctx )
157             if $_[2] =~ /all/i;
158              
159 14         60 $_[0]->_mech_domain( $_[1], $_[2], $_[4] );
160             }
161             ;
162              
163             # mx, a
164             with_bitmask
165             : MECHANISM '/' BITMASK
166             {
167 4     4   16 my $ctx = $_[1] . '/' . $_[3];
168              
169 4 100       38 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $ctx )
170             if $_[1] =~ /ip[46]/i;
171              
172 4 100       40 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
173             if $_[1] =~ /\A(ptr|all|exists|include)\Z/i;
174              
175 4         87 $_[0]->_mech_domain_bitmask( '+', $_[1], '@', $_[3] );
176             }
177             | QUALIFIER MECHANISM '/' BITMASK
178             {
179 6     6   28 my $ctx = $_[1] . $_[2] . '/' . $_[4];
180              
181 6 50       31 $_[0]->raise_error( 'E_IPADDR_EXPECTED', $ctx )
182             if $_[2] =~ /ip[46]/i;
183              
184 6 100       45 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
185             if $_[2] =~ /\A(ptr|all|exists|include)\Z/i;
186              
187 6         28 $_[0]->_mech_domain_bitmask( $_[1], $_[2], '@', $_[4] );
188             }
189             ;
190              
191             # mx, a
192             with_domain_bitmask
193             : MECHANISM ':' DOMAIN '/' BITMASK
194             {
195 5     5   57 my $ctx = $_[1] . ':' . $_[3] . '/' . $_[5];
196              
197 5 100       36 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
198             if $_[1] =~ /\A(ptr|all|exists|include)\Z/i;
199              
200 5         28 $_[0]->_mech_domain_bitmask( '+', $_[1], $_[3], $_[5] );
201             }
202             | QUALIFIER MECHANISM ':' DOMAIN '/' BITMASK
203             {
204 6     6   29 my $ctx = $_[1] . $_[2] . ':' . $_[4] . '/' . $_[6];
205              
206 6 100       51 $_[0]->raise_error( 'E_UNEXPECTED_BITMASK', $ctx )
207             if $_[2] =~ /\A(ptr|all|exists|include)\Z/i;
208              
209 6         36 $_[0]->_mech_domain_bitmask( $_[1], $_[2], $_[4], $_[6] );
210             }
211             ;
212              
213             # ip4, ip6
214             with_ipaddress
215             : MECHANISM ':' IPADDRESS
216             {
217 8     8   35 my $ctx = $_[1] . ':' . $_[3];
218              
219 8 100       96 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
220             if $_[1] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
221              
222 8         63 $_[0]->_mech_ipaddr_bitmask( '+', $_[1], $_[3], undef );
223             }
224             | QUALIFIER MECHANISM ':' IPADDRESS
225             {
226 10     10   40 my $ctx = $_[1] . $_[2] . ':' . $_[4];
227              
228 10 100       96 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
229             if $_[2] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
230              
231 10         50 $_[0]->_mech_ipaddr_bitmask( $_[1], $_[2], $_[4], undef );
232             }
233             | MECHANISM ':' IPADDRESS '/' BITMASK
234             {
235 4     4   19 my $ctx = $_[1] . ':' . $_[3] . '/' . $_[5];
236              
237 4 100       32 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
238             if $_[1] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
239              
240 4         22 $_[0]->_mech_ipaddr_bitmask( '+', $_[1], $_[3], $_[5] );
241             }
242             | QUALIFIER MECHANISM ':' IPADDRESS '/' BITMASK
243             {
244 14     14   67 my $ctx = $_[1] . $_[2] . ':' . $_[4] . '/' . $_[6];
245              
246 14 100       145 $_[0]->raise_error( 'E_UNEXPECTED_IPADDR', $ctx )
247             if $_[2] =~ /\A(a|mx|ptr|all|exists|include)\Z/i;
248              
249 14         65 $_[0]->_mech_ipaddr_bitmask( $_[1], $_[2], $_[4], $_[6] );
250             }
251 13         1999 ;
252              
253             %%
254              
255             sub parse {
256 13     139 1 113 my ( $self, $text ) = @_;
  139         379621  
257              
258 139         797 $input = $self->YYData->{INPUT} = $text;
259 139         309 delete $self->YYData->{ERRMSG};
260              
261 139         697 return $self->YYParse( yylex => \&_lexer, yyerror => \&_error );
262             }
263              
264             sub error {
265 73     73 1 10752 my ( $self ) = @_;
266 73         195 return $self->YYData->{ERRMSG};
267             }
268              
269             sub _build_error {
270 73     73   117 my ( $self, $code, $context, @extra ) = @_;
271              
272 73 50       238 $code = 'E_DEFAULT' unless exists $errors{$code};
273              
274 73         547 $self->YYData->{ERRMSG} = {
275             text => sprintf( $errors{$code} => @extra ),
276             code => $code,
277             context => $context,
278             };
279             }
280              
281             sub raise_error {
282 62     62 1 130 my ( $self, @params ) = @_;
283              
284 62         147 $self->_build_error( @params );
285 62         311 $self->YYError;
286             }
287              
288             sub _error {
289 73     73   81 my ( $self ) = @_;
290              
291 73 100       238 unless ( exists $self->YYData->{ERRMSG} ) {
292 11   100     86 substr( $input, index( $input, ($self->YYCurval || '') ), 0, '<*>' );
293              
294 11   100     37 $self->_build_error( 'E_SYNTAX', $input, ($self->YYCurval || '') );
295             }
296              
297 73         129 return;
298             }
299              
300             sub _lexer {
301 573     573   575 my ( $parser ) = @_;
302              
303 573         1054 $parser->YYData->{INPUT} =~ s/^\s*//;
304              
305 573         1192 for ( $parser->YYData->{INPUT} ) {
306             # printf( "[debug] %s\n", $_ );
307              
308 573 100       1355 s/^(v\=spf1)\b//i
309             and return ( 'VERSION', $1 );
310              
311 571 100       11719 s/^(\/)\b//i
312             and return ( '/', '/' );
313 527 100       1074 s/^(\:)\b//i
314             and return ( ':', ':' );
315 458 100       783 s/^(\=)\b//i
316             and return ( '=', '=' );
317              
318             # qualifiers
319 439 100       1312 s/^([-~\+\?])\b//i
320             and return ( 'QUALIFIER', $1 );
321              
322             # mechanisms
323 365 100       1485 s/^(all|ptr|a|mx|ip4|ip6|exists|include)\b//i
324             and return ( 'MECHANISM', $1 );
325              
326 257 100       1364 s/^($RE{net}{IPv4}{dec}|$RE{net}{IPv6}{-sep=>':'})\b//i
327             and return ( 'IPADDRESS', $1 );
328              
329 211 100       67839 s/^([_\.a-z\d][\-a-z\d]*\.[\.\-a-z\d]*[a-z\d]?)\b//i
330             and return ( 'DOMAIN', $1 );
331              
332 176 100       749 s/^(\d{1,3})\b//i
333             and return ( 'BITMASK', $1 );
334              
335 132 100       583 s/^([a-z\d\.\-_]+)\b//i
336             and return ( 'LITERAL', $1 );
337              
338             # garbage
339 104 100       499 s/^(.+)\b//i
340             and return ( 'UNKNOWN', $1 );
341             }
342              
343             # EOF
344 97         327 return ( '', undef );
345             }
346              
347             # generic modifier
348             sub _mod_generic {
349 2     2   5 my ( $self, $mod, $domain ) = @_;
350              
351             return +{
352 2 50       18 type => 'mod',
353             modifier => lc $mod,
354             (
355             $domain
356             ? ( domain => $domain ) :
357             ( )
358             ),
359             };
360             }
361              
362             # generic skip
363             sub _skip_generic {
364 0     0   0 my ( $self, $token, $val ) = @_;
365              
366             return +{
367 0         0 type => 'skip',
368             token => lc $token,
369             value => $val,
370             };
371             }
372              
373             # generic version
374             sub _ver_generic {
375 1     1   1 my ( $self, $ver ) = @_;
376              
377             return +{
378 1         6 type => 'ver',
379             version => lc $ver,
380             };
381             }
382              
383              
384             # generic mechanism
385             sub _mech_generic {
386 107     107   163 my ( $self, $qualifier, $mech, $domain, $ipaddr, $bitmask ) = @_;
387              
388             return +{
389 107 100       922 type => 'mech',
    100          
    100          
    100          
390             qualifier => $qualifier,
391             mechanism => lc $mech,
392             (
393             $domain
394             ? ( domain => $domain ) :
395             ( )
396             ),
397             (
398             $ipaddr
399             ? ( ( defined $bitmask ? 'network' : 'ipaddress' ) => $ipaddr )
400             : ( )
401             ),
402             (
403             defined $bitmask
404             ? ( bitmask => $bitmask )
405             : ( )
406             ),
407             };
408             }
409              
410             sub _mech_domain {
411 50     50   97 my ( $self, $qualifier, $mech, $domain ) = @_;
412              
413 50         163 return $self->_mech_generic( $qualifier, $mech, $domain, undef, undef );
414             }
415              
416             sub _mech_domain_bitmask {
417 21     21   47 my ( $self, $qualifier, $mech, $domain, $bitmask ) = @_;
418              
419 21         61 return $self->_mech_generic( $qualifier, $mech, $domain, undef, $bitmask );
420             }
421              
422             sub _mech_ipaddr_bitmask {
423 36     36   97 my ( $self, $qualifier, $mech, $ipaddr, $bitmask ) = @_;
424              
425 36         101 return $self->_mech_generic( $qualifier, $mech, undef, $ipaddr, $bitmask );
426             }