File Coverage

blib/lib/Mail/SPF/Term.pm
Criterion Covered Total %
statement 105 161 65.2
branch 0 28 0.0
condition 0 15 0.0
subroutine 23 33 69.7
pod 1 10 10.0
total 129 247 52.2


line stmt bran cond sub pod time code
1             #
2             # Mail::SPF::Term
3             # SPF record term class.
4             #
5             # (C) 2005-2012 Julian Mehnle
6             # 2005 Shevek
7             # $Id: Term.pm 57 2012-01-30 08:15:31Z julian $
8             #
9             ##############################################################################
10              
11             package Mail::SPF::Term;
12              
13             =head1 NAME
14              
15             Mail::SPF::Term - SPF record term class
16              
17             =cut
18              
19 1     1   24107 use warnings;
  1         2  
  1         37  
20 1     1   6 use strict;
  1         2  
  1         33  
21              
22 1     1   1125 use utf8; # Hack to keep Perl 5.6 from whining about /[\p{}]/.
  1         11  
  1         6  
23              
24 1     1   40 use base 'Mail::SPF::Base';
  1         2  
  1         578  
25              
26             use overload
27 1         9 '""' => 'stringify',
28 1     1   6 fallback => 1;
  1         1  
29              
30 1     1   1254 use NetAddr::IP;
  1         54962  
  1         5  
31              
32 1     1   1372 use constant TRUE => (0 == 0);
  1         2  
  1         80  
33 1     1   4 use constant FALSE => not TRUE;
  1         1  
  1         70  
34              
35 1     1   4 use constant name_pattern => qr/ \p{IsAlpha} [\p{IsAlnum}\-_.]* /x;
  1     1   2  
  1         14  
  1         22067  
  1         2  
  1         90  
36              
37 1     1   5 use constant macro_literal_pattern => qr/[!-\$&-~]/;
  1         1  
  1         59  
38 1     1   4 use constant macro_delimiter => qr/[.\-+,\/_=]/;
  1         1  
  1         49  
39 1     1   4 use constant macro_transformers_pattern => qr/\d*r?/;
  1         1  
  1         76  
40 1         1 use constant macro_expand_pattern => qr/
41             \%
42             (?:
43 1         2 { \p{IsAlpha} ${\macro_transformers_pattern} ${\macro_delimiter}* } |
  1         16  
44             [%_-]
45             )
46 1     1   4 /x;
  1         2  
47              
48 1         2 use constant macro_string_pattern => qr/
49             (?:
50 1         1 ${\macro_expand_pattern} |
  1         16  
51             ${\macro_literal_pattern}
52             )*
53 1     1   166 /x;
  1         2  
54              
55 1         142 use constant toplabel_pattern => qr/
56             \p{IsAlnum}+ - [\p{IsAlnum}-]* \p{IsAlnum} |
57             \p{IsAlnum}* \p{IsAlpha} \p{IsAlnum}*
58 1     1   4498 /x;
  1         3  
59              
60 1         2 use constant domain_end_pattern => qr/
61 1         2 \. ${\toplabel_pattern} \.? |
  1         30  
62             ${\macro_expand_pattern}
63 1     1   5 /x;
  1         1  
64              
65 1     1   686 use constant domain_spec_pattern => qr/ ${\macro_string_pattern} ${\domain_end_pattern} /x;
  1         3  
  1         2  
  1         2  
  1         33  
66              
67 1     1   778 use constant qnum_pattern => qr/ 25[0-5] | 2[0-4]\d | 1\d\d | [1-9]\d | \d /x;
  1         2  
  1         79  
68 1     1   5 use constant ipv4_address_pattern => qr/ ${\qnum_pattern} (?: \. ${\qnum_pattern} ){3} /x;
  1         3  
  1         1  
  1         2  
  1         96  
69              
70 1     1   1012 use constant hexword_pattern => qr/\p{IsXDigit}{1,4}/;
  1         2  
  1         125  
71 1         2 use constant two_hexwords_or_ipv4_address_pattern => qr/
72 1         3 ${\hexword_pattern} : ${\hexword_pattern} | ${\ipv4_address_pattern}
  1         3  
  1         19  
73 1     1   6 /x;
  1         2  
74 1         3 use constant ipv6_address_pattern => qr/
75             # x:x:x:x:x:x:x:x | x:x:x:x:x:x:n.n.n.n
76 1         2 (?: ${\hexword_pattern} : ){6} ${\two_hexwords_or_ipv4_address_pattern} |
  1         2  
  1         3  
77             # x::x:x:x:x:x:x | x::x:x:x:x:n.n.n.n
78 1         2 (?: ${\hexword_pattern} : ){1} : (?: ${\hexword_pattern} : ){4} ${\two_hexwords_or_ipv4_address_pattern} |
  1         3  
  1         2  
79             # x[:x]::x:x:x:x:x | x[:x]::x:x:x:n.n.n.n
80 1         2 (?: ${\hexword_pattern} : ){1,2} : (?: ${\hexword_pattern} : ){3} ${\two_hexwords_or_ipv4_address_pattern} |
  1         1  
  1         2  
81             # x[:...]::x:x:x:x | x[:...]::x:x:n.n.n.n
82 1         1 (?: ${\hexword_pattern} : ){1,3} : (?: ${\hexword_pattern} : ){2} ${\two_hexwords_or_ipv4_address_pattern} |
  1         1  
  1         3  
83             # x[:...]::x:x:x | x[:...]::x:n.n.n.n
84 1         1 (?: ${\hexword_pattern} : ){1,4} : (?: ${\hexword_pattern} : ){1} ${\two_hexwords_or_ipv4_address_pattern} |
  1         2  
  1         1  
85             # x[:...]::x:x | x[:...]::n.n.n.n
86 1         1 (?: ${\hexword_pattern} : ){1,5} : ${\two_hexwords_or_ipv4_address_pattern} |
  1         3  
87             # x[:...]::x | -
88 1         1 (?: ${\hexword_pattern} : ){1,6} : ${\hexword_pattern} |
  1         1  
89             # x[:...]:: | -
90 1         1 (?: ${\hexword_pattern} : ){1,7} : |
91             # ::[...:]x | -
92 1         2 :: (?: ${\hexword_pattern} : ){0,6} ${\hexword_pattern} |
  1         1  
93             # - | ::[...:]n.n.n.n
94 1         70 :: (?: ${\hexword_pattern} : ){0,5} ${\two_hexwords_or_ipv4_address_pattern} |
95             # :: | -
96             ::
97 1     1   502 /x;
  1         3  
98              
99             =head1 DESCRIPTION
100              
101             An object of class B represents a term within an SPF record.
102             Mail::SPF::Term cannot be instantiated directly. Create an instance of a
103             concrete sub-class instead.
104              
105             =head2 Constructor
106              
107             The following constructor is provided:
108              
109             =over
110              
111             =item B: returns I
112              
113             I. Creates a new SPF record term object.
114              
115             %options is a list of key/value pairs, however Mail::SPF::Term itself specifies
116             no constructor options.
117              
118             =item B: returns I;
119             throws I, I
120              
121             I. Creates a new SPF record term object by parsing the string and
122             any options given.
123              
124             =cut
125              
126             sub new_from_string {
127 0     0 1   my ($self, $text, %options) = @_;
128 0           $self = $self->new(%options, text => $text);
129 0           $self->parse();
130 0           return $self;
131             }
132              
133             =back
134              
135             =head2 Class methods
136              
137             The following class methods are provided:
138              
139             =over
140              
141             =item B: returns I
142              
143             Returns a regular expression that matches any legal name for an SPF record
144             term.
145              
146             =back
147              
148             =head2 Instance methods
149              
150             The following instance methods are provided:
151              
152             =over
153              
154             =cut
155              
156             sub parse_domain_spec {
157 0     0 0   my ($self, $required) = @_;
158 0 0         if ($self->{parse_text} =~ s/^(${\$self->domain_spec_pattern})//) {
  0 0          
159 0           my $domain_spec = $1;
160 0           $domain_spec =~ s/^(.*?)\.?$/\L$1/;
161 0           $self->{domain_spec} = Mail::SPF::MacroString->new(text => $domain_spec);
162             }
163             elsif ($required) {
164 0           throw Mail::SPF::ETermDomainSpecExpected(
165             "Missing required domain-spec in '" . $self->text . "'");
166             }
167 0           return;
168             }
169              
170             sub parse_ipv4_address {
171 0     0 0   my ($self, $required) = @_;
172 0 0         if ($self->{parse_text} =~ s/^(${\$self->ipv4_address_pattern})//) {
  0 0          
173 0           $self->{ip_address} = $1;
174             }
175             elsif ($required) {
176 0           throw Mail::SPF::ETermIPv4AddressExpected(
177             "Missing required IPv4 address in '" . $self->text . "'");
178             }
179 0           return;
180             }
181              
182             sub parse_ipv4_prefix_length {
183 0     0 0   my ($self, $required) = @_;
184 0 0         if ($self->{parse_text} =~ s#^/(\d+)##) {
    0          
185 0 0 0       $1 >= 0 and $1 <= 32 and $1 !~ /^0./
      0        
186             or throw Mail::SPF::ETermIPv4PrefixLengthExpected(
187             "Invalid IPv4 prefix length encountered in '" . $self->text . "'");
188 0           $self->{ipv4_prefix_length} = $1;
189             }
190             elsif (not $required) {
191 0           $self->{ipv4_prefix_length} = $self->default_ipv4_prefix_length;
192             }
193             else {
194 0           throw Mail::SPF::ETermIPv4PrefixLengthExpected(
195             "Missing required IPv4 prefix length in '" . $self->text . "'");
196             }
197 0           return;
198             }
199              
200             sub parse_ipv4_network {
201 0     0 0   my ($self, $required) = @_;
202 0           $self->parse_ipv4_address($required);
203 0           $self->parse_ipv4_prefix_length();
204 0           $self->{ip_network} = NetAddr::IP->new($self->{ip_address}, $self->{ipv4_prefix_length});
205 0           return;
206             }
207              
208             sub parse_ipv6_address {
209 0     0 0   my ($self, $required) = @_;
210 0 0         if ($self->{parse_text} =~ s/^(${\$self->ipv6_address_pattern})(?=\/|$)//) {
  0 0          
211 0           $self->{ip_address} = $1;
212             }
213             elsif ($required) {
214 0           throw Mail::SPF::ETermIPv6AddressExpected(
215             "Missing required IPv6 address in '" . $self->text . "'");
216             }
217 0           return;
218             }
219              
220             sub parse_ipv6_prefix_length {
221 0     0 0   my ($self, $required) = @_;
222 0 0         if ($self->{parse_text} =~ s#^/(\d+)##) {
    0          
223 0 0 0       $1 >= 0 and $1 <= 128 and $1 !~ /^0./
      0        
224             or throw Mail::SPF::ETermIPv6PrefixLengthExpected(
225             "Invalid IPv6 prefix length encountered in '" . $self->text . "'");
226 0           $self->{ipv6_prefix_length} = $1;
227             }
228             elsif (not $required) {
229 0           $self->{ipv6_prefix_length} = $self->default_ipv6_prefix_length;
230             }
231             else {
232 0           throw Mail::SPF::ETermIPv6PrefixLengthExpected(
233             "Missing required IPv6 prefix length in '" . $self->text . "'");
234             }
235 0           return;
236             }
237              
238             sub parse_ipv6_network {
239 0     0 0   my ($self, $required) = @_;
240 0           $self->parse_ipv6_address($required);
241 0           $self->parse_ipv6_prefix_length();
242 0           $self->{ip_network} = NetAddr::IP->new(
243             $self->{ip_address}, $self->{ipv6_prefix_length});
244 0           return;
245             }
246              
247             sub parse_ipv4_ipv6_prefix_lengths {
248 0     0 0   my ($self) = @_;
249 0           $self->parse_ipv4_prefix_length();
250 0 0 0       if (
251             defined($self->{ipv4_prefix_length}) and # an IPv4 prefix length has been parsed, and
252             $self->{parse_text} =~ s#^/## # another slash is following
253             ) {
254             # Parse an IPv6 prefix length:
255 0           $self->parse_ipv6_prefix_length(TRUE);
256             }
257 0           return;
258             }
259              
260             =item B: returns I; throws I
261              
262             Returns the unparsed text of the term. Throws a I
263             exception if the term was created synthetically instead of being parsed, and no
264             text was provided.
265              
266             =cut
267              
268             sub text {
269 0     0 0   my ($self) = @_;
270 0 0         defined($self->{text})
271             or throw Mail::SPF::ENoUnparsedText;
272 0           return $self->{text};
273             }
274              
275             =item B: returns I
276              
277             I. Returns the name of the term.
278              
279             =back
280              
281             =head1 SEE ALSO
282              
283             L, L, L, L
284              
285             L
286              
287             For availability, support, and license information, see the README file
288             included with Mail::SPF.
289              
290             =head1 AUTHORS
291              
292             Julian Mehnle , Shevek
293              
294             =cut
295              
296             TRUE;