File Coverage

blib/lib/Mail/SPF/Term.pm
Criterion Covered Total %
statement 102 158 64.5
branch 0 28 0.0
condition 0 15 0.0
subroutine 22 32 68.7
pod 1 10 10.0
total 125 243 51.4


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