File Coverage

blib/lib/WWW/YaCyBlacklist.pm
Criterion Covered Total %
statement 79 111 71.1
branch 23 40 57.5
condition 3 6 50.0
subroutine 14 17 82.3
pod 9 9 100.0
total 128 183 69.9


line stmt bran cond sub pod time code
1 4     4   1211413 use strict;
  4         24  
  4         172  
2 4     4   23 use warnings;
  4         9  
  4         474  
3              
4             package WWW::YaCyBlacklist;
5             # ABSTRACT: a Perl module to parse and execute YaCy blacklists
6              
7             our $AUTHORITY = 'cpan:IBRAUN';
8             $WWW::YaCyBlacklist::VERSION = '0.8';
9              
10 4     4   2659 use Moose;
  4         2050743  
  4         23  
11 4     4   25918 use Moose::Util::TypeConstraints;
  4         9  
  4         31  
12 4     4   8922 use IO::All;
  4         46246  
  4         33  
13 4     4   2326 use URI::URL;
  4         36565  
  4         6434  
14             require 5.8.0;
15              
16              
17             # Needed if RegExps do not compile
18             has 'use_regex' => (
19             is => 'ro',
20             isa => 'Bool',
21             default => 1,
22             );
23              
24              
25             has 'filename' => (
26             is => 'rw',
27             isa => 'Str',
28             default => 'ycb.black',
29             );
30              
31             has 'file_charset' => (
32             is => 'ro',
33             isa => 'Str',
34             default => 'UTF-8',
35             init_arg => undef,
36             );
37              
38             has 'origorder' => (
39             is => 'rw',
40             isa => 'Int',
41             default => 0,
42             init_arg => undef,
43             );
44              
45              
46             has 'sortorder' => (
47             is => 'rw',
48             isa => 'Bool',
49             default => 0,
50             );
51              
52              
53             has 'sorting' => (
54             is => 'rw',
55             isa => enum([qw[ alphabetical length origorder random reverse_host ]]),
56             default => 'origorder',
57             );
58              
59             has 'patterns' => (
60             is=>'rw',
61             isa => 'HashRef',
62             traits => [ 'Hash' ],
63             default => sub { {} },
64             init_arg => undef,
65             );
66              
67             sub _check_host_regex {
68              
69 26     26   69 my ($self, $pattern) = @_;
70              
71 26 100       97 return 0 if $pattern =~ /^[\w\-\.\*]+$/; # underscores are not allowed in domain names but sometimes happen in subdomains
72 2         24 return 1;
73             }
74              
75              
76             sub read_from_array {
77              
78 3     3 1 922 my ($self, @lines) = @_;
79              
80 3         10 foreach my $line ( @lines ) {
81 23 50       45 if ( CORE::length $line > 0 ) {
82 23         544 ${ $self->patterns }{ $line }{ 'origorder' } = $self->origorder( $self->origorder + 1 );
  23         562  
83 23         106 ( ${ $self->patterns }{ $line }{ 'host' }, ${ $self->patterns }{ $line }{ 'path' } ) = split /(?!\\)\/+?/, $line, 2;
  23         537  
  23         617  
84 23         30 ${ $self->patterns }{ $line }{ 'path' } = '/' . ${ $self->patterns }{ $line }{ 'path' };
  23         532  
  23         626  
85 23         30 ${ $self->patterns }{ $line }{ 'host_regex' } = $self->_check_host_regex( ${ $self->patterns }{ $line }{ 'host' } );
  23         552  
  23         517  
86             }
87             }
88             }
89              
90              
91             sub read_from_files {
92              
93 0     0 1 0 my ($self, @files) = @_;
94 0         0 my @lines;
95              
96 0         0 grep { push( @lines, io( $_ )->encoding( $self->file_charset )->chomp->slurp ) } @files;
  0         0  
97              
98             # chomp is not fully reliable with Windows files in Linux
99 0         0 grep { my $s = $_; $s =~ s/\r$//; $s } @lines;
  0         0  
  0         0  
  0         0  
100              
101 0         0 $self->read_from_array( @lines );
102             }
103              
104              
105             sub length {
106              
107 4     4 1 12 my $self = shift;
108 4         8 return scalar keys %{ $self->patterns };
  4         181  
109             }
110              
111              
112             sub check_url {
113              
114 69     69 1 120 my $self = shift;
115 69         88 my $url = shift;
116 69 50       418 return 1 if $url !~ /^(ht|f)tps?\:\/\//i;
117 69 50       257 $url .= '/' if $url =~ /\:\/\/[\w\-\.]+$/;
118 69         245 $url = new URI $url;
119 69 100       13674 my $pq = ( defined $url->query ) ? $url->path.'?'.$url->query : $url->path;
120              
121 69         1272 foreach my $pattern ( keys %{ $self->patterns } ) {
  69         2054  
122              
123 730         4459 my $path = '^' . ${ $self->patterns }{ $pattern }{ path } . '$';
  730         16002  
124 730 100       7925 next if $pq !~ /$path/;
125 300         435 my $host = ${ $self->patterns }{ $pattern }{ host };
  300         6274  
126              
127 300 100       306 if ( !${ $self->patterns }{ $pattern }{ host_regex } ) {
  300         6049  
128              
129 299 100       305 if ( index( ${ $self->patterns }{ $pattern }{ host }, '*') > -1 ) {
  299         5959  
130              
131 133         315 $host =~ s/\*/.*/g;
132 133 100       277 return 1 if $url->host =~ /^$host$/;
133             }
134             else {
135 166 100 66     316 return 1 if index( $url->host, ${ $self->patterns }{ $pattern }{ host } ) > -1 && $url->host =~ /^([\w\-]+\.)*$host$/;
  166         6920  
136             }
137             }
138             else {
139 1 50 33     23 return 1 if $self->use_regex && $url->host =~ /^$host$/;
140             }
141             }
142 18         124 return 0;
143             }
144              
145              
146             sub find_matches {
147              
148 2     2 1 2 my $self = shift;
149 2         4 my @urls;
150 2 100       5 grep { push( @urls, $_ ) if $self->check_url( $_ ) } @_;
  16         947  
151 2         11 return @urls;
152             }
153              
154              
155             sub find_non_matches {
156              
157 1     1 1 3 my $self = shift;
158 1         3 my @urls;
159 1 100       4 grep { push( @urls, $_ ) if !$self->check_url( $_ ) } @_;
  8         620  
160 1         7 return @urls;
161             }
162              
163              
164             sub delete_pattern {
165              
166 1     1 1 2 my $self = shift;
167 1         1 my $pattern = shift;
168 1 50       2 delete( ${ $self->patterns }{ $pattern } ) if exists( ${ $self->patterns }{ $pattern } ) ;
  1         20  
  1         30  
169             }
170              
171              
172             sub sort_list {
173              
174 0     0 1   my $self = shift;
175 0 0         return keys %{ $self->patterns } if $self->sorting eq 'random';
  0            
176 0           my @sorted_list;
177              
178 0 0         @sorted_list = sort keys %{ $self->patterns } if $self->sorting eq 'alphabetical';
  0            
179 0 0         @sorted_list = sort { CORE::length $a <=> CORE::length $b } keys %{ $self->patterns } if $self->sorting eq 'length';
  0            
  0            
180 0 0         @sorted_list = sort { ${ $self->patterns }{ $a }{ origorder } <=> ${ $self->patterns }{ $b }{ origorder } } keys %{ $self->patterns } if $self->sorting eq 'origorder';
  0            
  0            
  0            
  0            
181 0 0         @sorted_list = sort { reverse( ${ $self->patterns }{ $a }{ host } ) cmp reverse( ${ $self->patterns }{ $b }{ host } ) } keys %{ $self->patterns } if $self->sorting eq 'reverse_host';
  0            
  0            
  0            
  0            
182              
183 0 0         return @sorted_list if $self->sortorder;
184 0           return reverse( @sorted_list );
185             }
186              
187              
188             sub store_list {
189              
190 0     0 1   my $self = shift;
191 0           join( "\n", $self->sort_list( ) ) > io( $self->filename );
192             }
193              
194             1;
195 4     4   39 no Moose;
  4         7  
  4         32  
196             __PACKAGE__->meta->make_immutable;
197              
198             __END__
199              
200             =pod
201              
202             =encoding UTF-8
203              
204             =head1 NAME
205              
206             WWW::YaCyBlacklist - a Perl module to parse and execute YaCy blacklists
207              
208             =head1 VERSION
209              
210             version 0.8
211              
212             =head1 SYNOPSIS
213              
214             use WWW::YaCyBlacklist;
215              
216             my $ycb = WWW::YaCyBlacklist->new( { 'use_regex' => 1 } );
217             $ycb->read_from_array(
218             'test1.co/fullpath',
219             'test2.co/.*',
220             );
221             $ycb->read_from_files(
222             '/path/to/1.black',
223             '/path/to/2.black',
224             );
225              
226             print "Match!" if $ycb->check_url( 'http://test1.co/fullpath' );
227             my @urls = (
228             'https://www.perlmonks.org/',
229             'https://metacpan.org/',
230             );
231             my @matches = $ycb->find_matches( @urls );
232             my @nonmatches = $ycb->find_non_matches( @urls );
233              
234             $ycb->sortorder( 1 );
235             $ycb->sorting( 'alphabetical' );
236             $ycb->filename( '/path/to/new.black' );
237             $ycb->store_list( );
238              
239             =head1 METHODS
240              
241             =head2 C<new(%options)>
242              
243             =head2 C<use_regex =E<gt> 0|1> (default C<1>)
244              
245             Can only be set in the constructor and never be changed any later. If C<false>, the pattern will not get checked if the
246             C<host> part is a regular expression (but the patterns remain in the list).
247              
248             =head2 C<filename =E<gt> '/path/to/file.black'> (default C<ycb.black>)
249              
250             This is the file printed by C<store_list>
251              
252             =head2 C<sortorder =E<gt> 0|1> (default C<0>)
253              
254             0 ascending, 1 descending
255             Configures C<sort_list>
256              
257             =head2 C<sorting =E<gt> 'alphabetical|length|origorder|random|reverse_host'> (default C<'origorder>)
258              
259             Configures C<sort_list>
260              
261             =head2 C<void read_from_array( @patterns )>
262              
263             Reads a list of YaCy blacklist patterns.
264              
265             =head2 C<void read_from_files( @files )>
266              
267             Reads a list of YaCy blacklist files.
268              
269             =head2 C<int length( )>
270              
271             Returns the number of patterns in the current list.
272              
273             =head2 C<bool check_url( $URL )>
274              
275             1 if the URL was matched by any pattern, 0 otherwise.
276              
277             =head2 C<@URLS_OUT find_matches( @URLS_IN )>
278              
279             Returns all URLs which was matches by the current list.
280              
281             =head2 C<@URLS_OUT find_non_matches( @URLS_IN )>
282              
283             Returns all URLs which was not matches by the current list.
284              
285             =head2 C<void delete_pattern( $pattern )>
286              
287             Removes a pattern from the current list.
288              
289             =head2 C<@patterns sort_list( )>
290              
291             Returns a list of patterns configured by C<sorting> and C<sortorder>.
292              
293             =head2 C<void store_list( )>
294              
295             Prints the current list to a file. Executes C<sort_list( )>.
296              
297             =head1 OPERATIONAL NOTES
298              
299             C<WWW::YaCyBlacklist> checks the path part including the leading separator C</>. This protects against regexp compiling errors with leading quantifiers. So do not something like C<host.tld/^path> although YaCy allows this.
300              
301             C<check_url( )> alway returns true if the protocol of the URL is not C<https?> or C<ftps?>.
302              
303             =head1 BUGS
304              
305             YaCy does not allow host patterns with two ore more stars at the time being. C<WWW::YaCyBlacklist> does not check for this but simply executes. This is rather a YaCy bug.
306              
307             If there is something you would like to tell me, there are different channels for you:
308              
309             =over
310              
311             =item *
312              
313             L<GitHub issue tracker|https://github.com/CarlOrff/WWW-YaCyBlacklist/issues>
314              
315             =item *
316              
317             L<CPAN issue tracker|https://rt.cpan.org/Public/Dist/Display.html?WWW-YaCyBlacklist>
318              
319             =item *
320              
321             L<Project page on my homepage|https://ingram-braun.net/erga/the-www-yacyblacklist-module/>
322              
323             =item *
324              
325             L<Contact form on my homepage|https://ingram-braun.net/erga/legal-notice-and-contact/>
326              
327             =back
328              
329             =head1 SOURCE
330              
331             =over
332              
333             =item *
334              
335             L<De:Blacklists|https://wiki.yacy.net/index.php/De:Blacklists> (German).
336              
337             =item *
338              
339             L<Dev:APIlist|https://wiki.yacy.net/index.php/Dev:APIlist>
340              
341             =back
342              
343             =head1 SEE ALSO
344              
345             =over
346              
347             =item *
348              
349             L<YaCy homepage|https://yacy.net/>
350              
351             =item *
352              
353             L<YaCy community|https://community.searchlab.eu/>
354              
355             =back
356              
357             =head1 AUTHOR
358              
359             Ingram Braun <carlorff1@gmail.com>
360              
361             =head1 COPYRIGHT AND LICENSE
362              
363             This software is copyright (c) 2025 by Ingram Braun.
364              
365             This is free software; you can redistribute it and/or modify it under
366             the same terms as the Perl 5 programming language system itself.
367              
368             =cut