File Coverage

blib/lib/Net/PublicSuffixList.pm
Criterion Covered Total %
statement 155 185 83.7
branch 14 26 53.8
condition 9 15 60.0
subroutine 29 29 100.0
pod 17 17 100.0
total 224 272 82.3


line stmt bran cond sub pod time code
1             package Net::PublicSuffixList;
2 4     4   4997 use v5.26;
  4         13  
3 4     4   18 use strict;
  4         5  
  4         79  
4 4     4   18 use feature qw(signatures);
  4         7  
  4         344  
5 4     4   20 no warnings qw(experimental::signatures);
  4         7  
  4         136  
6              
7 4     4   17 use warnings;
  4         26  
  4         94  
8 4     4   17 no warnings;
  4         6  
  4         120  
9              
10 4     4   19 use Carp qw(carp);
  4         7  
  4         206  
11 4     4   22 use File::Basename qw(basename dirname);
  4         6  
  4         228  
12 4     4   22 use File::Path qw(make_path);
  4         7  
  4         228  
13 4     4   433 use File::Spec::Functions qw(catfile);
  4         652  
  4         6080  
14              
15             our $VERSION = '0.500';
16              
17             =encoding utf8
18              
19             =head1 NAME
20              
21             Net::PublicSuffixList - The Mozilla Public Suffix List
22              
23             =head1 SYNOPSIS
24              
25             use Net::PublicSuffixList;
26              
27             my $psl = Net::PublicSuffixList->new;
28              
29             my $host = 'amazon.co.uk';
30              
31             # get all the suffixes in host (like, uk and co.uk)
32             my $suffixes = $psl->suffixes_in( $host );
33              
34             # get the longest suffix
35             my $suffix = $psl->longest_suffix_in( $host );
36              
37             my $hash = $psl->split_host( $host );
38              
39             =head1 DESCRIPTION
40              
41             I mostly wrote this because I was working on L and needed a
42             way to figure out which part of a URL was the registered part and with
43             was the top-level domain.
44              
45             The Public Suffix List is essentially a self-reported collection of the
46             top-level, generic, country code, or whatever domains.
47              
48             =over 4
49              
50             =item new
51              
52             Create the new object and specify how you'd like to get the data. The
53             network file is about 220Kb, so you might want to fetch it once, store
54             it, and then use C to use it.
55              
56             The constructor first tries to use a local file. If you've disabled
57             that with C or the file doesn't exist, it moves on to trying
58             the network. If you've disabled the network with C, then it
59             complains but still returns the object. You can still construct your
60             own list with C.
61              
62             Possible keys:
63              
64             list_url # the URL for the suffix list
65             local_path # the path to a local file that has the suffix list
66             no_net # do not use the network
67             no_local # do not use a local file
68             cache_dir # location to save the fetched file
69              
70             =cut
71              
72 11     11 1 21932 sub new ( $class, %args ) {
  11         21  
  11         25  
  11         14  
73 11         20 my $self = bless {}, $class;
74 11         39 $self->_init( \%args );
75             }
76              
77 11     11   14 sub _init ( $self, $args ) {
  11         13  
  11         14  
  11         13  
78 11         23 my %args = ( $self->defaults->%*, $args->%* );
79              
80 11         42 while( my($k, $v) = each %args ) {
81 55         88 $self->{$k} = $v;
82 55 100       129 if( $k eq 'local_path' ) {
83 11         334 $self->{local_file} = basename( $v );
84             }
85             }
86              
87 11         18 my $method = do {
88 11 100 66     45 if( ! $self->{no_local} and -e $self->local_path ) {
    50          
89 2         7 'fetch_list_from_local'
90             }
91             elsif( ! $self->{no_net} ) {
92 0         0 'fetch_list_from_net'
93             }
94             else {
95 9         1262 carp "No way to fetch list! Check your settings for no_local or no_net";
96 9         63 return $self;
97             }
98             };
99              
100 2         7 my $ref = $self->$method();
101              
102 2         8 $self->parse_list( $ref );
103              
104 2         8 $self;
105             }
106              
107             =item defaults
108              
109             A hash of the default values for everything.
110              
111             =cut
112              
113 11     11 1 14 sub defaults ( $self ) {
  11         13  
  11         13  
114             state $hash = {
115             list_url => $self->default_url,
116             local_path => $self->default_local_path,
117             no_net => 0,
118             no_local => 0,
119 11         35 cache_dir => catfile( $ENV{HOME}, '.publicsuffixlist' ),
120             };
121 11         51 $hash;
122             }
123              
124             =item parse_list( STRING_REF )
125              
126             Take a scalar reference to the contents of the public suffix list,
127             find all the suffices and add them to the object.
128              
129             =cut
130              
131 7     7 1 6923 sub parse_list ( $self, $list ) {
  6         8  
  6         8  
  6         8  
132 6 100       16 unless( ref $list eq 'SCALAR' ) {
133 2         143 carp "Argument is not a scalar reference";
134 2         83 return;
135             }
136 4     1   56 open my $string_fh, '<:utf8', $list;
  1         5  
  1         2  
  1         4  
137 4         659 while( <$string_fh> ) {
138 28         37 chomp;
139 28 100 100     117 next if( /\A\s*\z/ || m|\A\s*//| );
140 20         37 s/\A\Q*.//;
141 20         33 $self->add_suffix( $_ );
142             }
143 4         16 $self;
144             }
145              
146             =item add_suffix( STRING )
147              
148             Add STRING to the known public suffices. This returns the object itself.
149              
150             Before this adds the suffix, it strips off leading C<*> and C<.*>
151             characters. Some sources specify C<*.foo.bar>, but this adds C.
152              
153             =cut
154              
155 26     26 1 1144 sub add_suffix ( $self, $suffix ) {
  26         29  
  26         30  
  26         26  
156 26         55 $suffix =~ s/\A[*.]+//;
157 26         60 $self->{suffix}{$suffix}++;
158 26         66 $self
159             }
160              
161             =item remove_suffix( STRING )
162              
163             Remove the STRING as a known public suffices. This returns the object
164             itself.
165              
166             =cut
167              
168 1     1 1 2 sub remove_suffix ( $self, $suffix ) { delete $self->{suffix}{$suffix}; $self }
  1         2  
  1         2  
  1         1  
  1         2  
  1         2  
169              
170             =item suffix_exists( STRING )
171              
172             Return the invocant if the suffix exists, and the empty list otherwise.
173              
174             =cut
175              
176 50 100   50 1 11262 sub suffix_exists ( $self, $suffix ) { exists $self->{suffix}{$suffix} ? $self : () }
  50         65  
  50         60  
  50         57  
  50         208  
177              
178             =item suffixes_in( HOST )
179              
180             Return an array reference of the publix suffixes in HOST, sorted from
181             shortest to longest.
182              
183             =cut
184              
185 3     3 1 1626 sub suffixes_in ( $self, $host ) {
  3         4  
  3         6  
  3         3  
186 3         11 my @parts = reverse split /\./, $host;
187             my @suffixes =
188 6         10 map { $_->[0] }
189 9         14 grep { $_->[1] }
190 9         15 map { [ $_, $self->suffix_exists( $_ ) ] }
191 3         8 map { join '.', reverse @parts[0..$_] }
  9         24  
192             0 .. $#parts;
193              
194 3         15 \@suffixes;
195             }
196              
197             =item longest_suffix_in( HOST )
198              
199             Return the longest public suffix in HOST.
200              
201             =cut
202              
203 2     2 1 3344 sub longest_suffix_in ( $self, $host ) {
  2         4  
  2         3  
  2         3  
204 2         4 $self->suffixes_in( $host )->@[-1];
205             }
206              
207             =item split_host( HOST )
208              
209             Returns a hash reference with these keys:
210              
211             host the input value
212             suffix the longest public suffix
213             short the input value with the public suffix
214             (and leading dot) removed
215              
216             =cut
217              
218 1     1 1 2460 sub split_host ( $self, $host ) {
  1         2  
  1         1  
  1         2  
219 1         4 my $suffix = $self->longest_suffix_in( $host );
220 1         30 my $short = $host =~ s/\Q.$suffix\E\z//r;
221              
222             return {
223 1         6 host => $host,
224             suffix => $suffix,
225             short => $short
226             }
227             }
228              
229             =item fetch_list_from_local
230              
231             Fetch the public suffix list plaintext file from the path returned
232             by C. Returns a scalar reference to the text of the raw
233             UTF-8 octets.
234              
235             =cut
236              
237 3     3 1 923 sub fetch_list_from_local ( $self ) {
  3         4  
  3         4  
238 3 100       10 return if $self->{no_local};
239 2         10 open my $fh, '<:raw', $self->local_path;
240 2         6 my $data = do { local $/; <$fh> };
  2         8  
  2         51  
241 2         7 $self->{source} = 'local_file';
242 2         21 \$data;
243             }
244              
245             =item fetch_list_from_net
246              
247             Fetch the public suffix list plaintext file from the URL returned
248             by C. Returns a scalar reference to the text of the raw
249             UTF-8 octets.
250              
251             If you've set C in the object, this method attempts to
252             cache the response in that directory using C as
253             the filename. This cache is different than C although you
254             can use it as C.
255              
256             =cut
257              
258 1     1 1 1 sub fetch_list_from_net ( $self ) {
  1         9  
  1         2  
259 1 50       5 return if $self->{no_net};
260 0         0 state $rc = require Mojo::UserAgent;
261 0         0 state $ua = Mojo::UserAgent->new;
262              
263 0         0 my $path = catfile( $self->{cache_dir}, $self->default_local_file );
264 0         0 my $local_last_modified = (stat $path)[9];
265 0         0 my $headers = {};
266              
267 0 0       0 if( $self->{cache_dir} ) {
268 0         0 make_path $self->{cache_dir};
269 0 0       0 if( $local_last_modified ) {
270 0         0 $headers->{'If-Modified-Since'} = Mojo::Date->new($local_last_modified);
271             }
272             }
273              
274 0         0 my $tx = $ua->get( $self->url() => $headers );
275              
276 0         0 my $body;
277 0 0       0 if( $tx->result->code eq '304' ) {
    0          
278 0         0 open my $fh, '<:raw', $path;
279 0         0 $body = do { local $/; <$fh> };
  0         0  
  0         0  
280 0         0 close $fh;
281 0         0 $self->{source} = 'net_cached';
282             }
283             elsif( $tx->result->code eq '200' ) {
284 0         0 $body = eval { $tx->result->body };
  0         0  
285              
286 0         0 my $date = Mojo::Date->new(
287             $tx->result->headers->last_modified,
288             $tx->result->headers->date,
289             0
290             );
291              
292 0 0       0 if( $self->{cache_dir} ) {
293 0         0 open my $fh, '>:raw', $path;
294 0         0 print { $fh } $body;
  0         0  
295 0         0 close $fh;
296 0         0 utime $date->epoch, $date->epoch, $path;
297             }
298              
299 0         0 $self->{source} = 'net';
300             }
301              
302 0         0 return \$body;
303             }
304              
305             =item url
306              
307             Return the configured URL for the public suffix list.
308              
309             =cut
310              
311 2     2 1 936 sub url ( $self ) {
  2         4  
  2         3  
312 2   66     8 $self->{list_url} // $self->default_url
313             }
314              
315             =item default_url
316              
317             Return the default URL for the public suffix list.
318              
319             =cut
320              
321 5     5 1 8 sub default_url ( $self ) {
  5         7  
  5         5  
322 5         16 'https://publicsuffix.org/list/public_suffix_list.dat'
323             }
324              
325             =item local_path
326              
327             Return the configured local path for the public suffix list.
328              
329             =cut
330              
331 5     5 1 6 sub local_path ( $self ) {
  5         7  
  5         7  
332 5   33     97 $self->{local_path} // $self->default_local_path
333             }
334              
335             =item default_local_path
336              
337             Return the default local path for the public suffix list.
338              
339             =cut
340              
341 3     3 1 4 sub default_local_path ( $self ) {
  3         4  
  3         4  
342 3         4 my $this_file = __FILE__;
343 3         154 my $this_dir = dirname( $this_file );
344 3         10 my $file = catfile( $this_dir, $self->default_local_file );
345             }
346              
347             =item local_file
348              
349             Return the configured filename for the public suffix list.
350              
351             =cut
352              
353 1     1 1 700 sub local_file ( $self ) {
  1         2  
  1         2  
354 1   33     7 $self->{local_file} // $self->default_local_file
355             }
356              
357             =item default_local_file
358              
359             Return the default filename for the public suffix list.
360              
361             =cut
362              
363 3     3 1 5 sub default_local_file ( $self ) {
  3         5  
  3         4  
364 3         30 'public_suffix_list.dat'
365             }
366              
367             =back
368              
369             =head1 TO DO
370              
371              
372             =head1 SEE ALSO
373              
374             L, L, L
375              
376             =head1 SOURCE AVAILABILITY
377              
378             This source is in Github:
379              
380             http://github.com/briandfoy/net-publicsuffixlist
381              
382             =head1 AUTHOR
383              
384             brian d foy, C<< >>
385              
386             =head1 COPYRIGHT AND LICENSE
387              
388             Copyright (c) 2020, brian d foy, All Rights Reserved.
389              
390             You may redistribute this under the terms of the Artistic License 2.0.
391              
392             =cut
393              
394             1;