File Coverage

blib/lib/Net/PublicSuffixList.pm
Criterion Covered Total %
statement 157 187 83.9
branch 14 26 53.8
condition 9 15 60.0
subroutine 29 29 100.0
pod 17 17 100.0
total 226 274 82.4


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