File Coverage

blib/lib/lib/filter.pm
Criterion Covered Total %
statement 11 141 7.8
branch 2 106 1.8
condition 0 22 0.0
subroutine 4 11 36.3
pod 0 2 0.0
total 17 282 6.0


line stmt bran cond sub pod time code
1             package lib::filter;
2              
3             #use 5.008009; # the first version where Module::CoreList becomes core
4 2     2   14 use strict 'subs', 'vars'; # no need to avoid strict & warnings, because Config uses them
  2         4  
  2         84  
5 2     2   10 use warnings;
  2         4  
  2         51  
6 2     2   10 use Config;
  2         4  
  2         366  
7              
8             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
9             our $DATE = '2021-08-29'; # DATE
10             our $DIST = 'lib-filter'; # DIST
11             our $VERSION = '0.281'; # VERSION
12              
13             # BEGIN snippet from Module::Path::More, with mods/simplification
14             my $SEPARATOR;
15             BEGIN {
16 2 50   2   31 if ($^O =~ /^(dos|os2)/i) {
    50          
17 0         0 $SEPARATOR = '\\';
18             } elsif ($^O =~ /^MacOS/i) {
19 0         0 $SEPARATOR = ':';
20             } else {
21 2         4205 $SEPARATOR = '/';
22             }
23             }
24             sub module_path {
25 0     0 0   my ($file, $inc) = @_;
26              
27 0           foreach my $dir (@$inc) {
28 0 0         next if !defined($dir);
29 0 0         next if ref($dir);
30 0           my $path = $dir . $SEPARATOR . $file;
31 0 0         return $path if -f $path;
32             }
33 0           undef;
34             }
35             # END snippet from Module::Path::More
36              
37             sub _open_handle {
38 0     0     my $path = shift;
39 0 0         open my($fh), "<", $path
40             or die "Can't open $path: $!";
41 0           $fh;
42             }
43              
44             my $handler;
45             my $hook;
46             my ($orig_inc, $orig_inc_sorted_by_len);
47              
48 0     0 0   sub lib::filter::INC { goto $handler }
49              
50             sub import {
51 0     0     my ($class, %opts) = @_;
52              
53 0           my $dbgh = "[lib::filter]";
54              
55 0           for (keys %opts) {
56 0 0         die "Unknown option $_"
57             unless /\A(
58             debug|
59             allow_core|allow_noncore|
60             extra_inc|
61             allow|allow_list|allow_re|
62             allow_is_recursive|
63             disallow|disallow_list|disallow_re|
64             filter
65             )\z/x;
66             }
67              
68 0 0         $opts{debug} = $ENV{PERL_LIB_FILTER_DEBUG} unless defined($opts{debug});
69              
70 0 0         $opts{allow_core} = 1 if !defined($opts{allow_core});
71 0 0         $opts{allow_noncore} = 1 if !defined($opts{allow_noncore});
72              
73 0 0 0       if ($opts{filter} && !ref($opts{filter})) {
74             # convenience, for when filter is specified from command-line (-M)
75 0           $opts{filter} = eval $opts{filter}; ## no critic: BuiltinFunctions::ProhibitStringyEval
76 0 0         die "Error in filter code: $@" if $@;
77             }
78              
79 0 0         if ($opts{extra_inc}) {
80 0           unshift @INC, split(/:/, $opts{extra_inc});
81             }
82              
83 0 0         if (!$orig_inc) {
84 0           $orig_inc = [@INC];
85 0           $orig_inc_sorted_by_len = [sort {length($b) <=> length($a)} @INC];
  0            
86             }
87              
88 0           my $core_inc = [@Config{qw(privlibexp archlibexp)}];
89 0           my $noncore_inc = [grep {$_ ne $Config{privlibexp} &&
90 0 0         $_ ne $Config{archlibexp}} @$orig_inc];
91 0           my %allow;
92 0 0         if ($opts{allow}) {
93 0           for (split /\s*;\s*/, $opts{allow}) {
94 0           $allow{$_} = "allow";
95             }
96             }
97 0 0         if ($opts{allow_list}) {
98             open my($fh), "<", $opts{allow_list}
99 0 0         or die "Can't open allow_list file '$opts{allow_list}': $!";
100 0           while (my $line = <$fh>) {
101 0           $line =~ s/^\s+//;
102 0 0         $line =~ /^(\w+(?:::\w+)*)/ or next;
103 0   0       $allow{$1} ||= "allow_list";
104             }
105             }
106              
107 0           my %disallow;
108 0 0         if ($opts{disallow}) {
109 0           for (split /\s*;\s*/, $opts{disallow}) {
110 0           $disallow{$_} = "disallow";
111 0           (my $pm = "$_.pm") =~ s!::!/!g; delete $INC{$pm};
  0            
112             }
113             }
114 0 0         if ($opts{disallow_list}) {
115             open my($fh), "<", $opts{disallow_list}
116 0 0         or die "Can't open disallow_list file '$opts{disallow_list}': $!";
117 0           while (my $line = <$fh>) {
118 0           $line =~ s/^\s+//;
119 0 0         $line =~ /^(\w+(?:::\w+)*)/ or next;
120 0   0       $disallow{$1} ||= "disallow_list";
121 0           (my $pm = "$1.pm") =~ s!::!/!g; delete $INC{$pm};
  0            
122             }
123             }
124              
125             $handler = sub {
126 0     0     my ($self, $file) = @_;
127              
128 0           my @caller = caller(0);
129              
130 0 0         warn "$dbgh hook called for $file (from package $caller[0] file $caller[1])\n" if $opts{debug};
131              
132 0           my $path;
133             FILTER:
134             {
135 0           my $mod = $file; $mod =~ s/\.pm$//; $mod =~ s!/!::!g;
  0            
  0            
  0            
136 0           my $err_prefix = "Can't locate $file";
137 0 0         if ($opts{filter}) {
138 0           local $_ = $mod;
139 0 0         warn "$dbgh Checking against custom filter ...\n" if $opts{debug};
140 0 0         unless ($opts{filter}->($mod)) {
141 0           die "$err_prefix (module '$mod' is disallowed (filter))";
142             }
143             }
144 0 0 0       if ($opts{disallow_re} && $mod =~ /$opts{disallow_re}/) {
145 0           die "$err_prefix (module '$mod' is disallowed (disallow_re))";
146             }
147 0 0         if ($disallow{$mod}) {
148 0           die "$err_prefix (module '$mod' is disallowed ($disallow{$mod}))";
149             }
150 0 0 0       if ($opts{allow_re} && $mod =~ /$opts{allow_re}/) {
151 0 0         warn "$dbgh module $mod matches allow_re\n" if $opts{debug};
152 0           $path = module_path($file, $orig_inc);
153 0 0         last FILTER if $path;
154 0           die "$err_prefix (module '$mod' is allowed (allow_re) but can't locate $file in \@INC (\@INC contains: ".join(" ", @INC)."))";
155             }
156 0 0         if ($allow{$mod}) {
157 0 0         warn "$dbgh module $mod matches $allow{$mod}\n" if $opts{debug};
158 0           $path = module_path($file, $orig_inc);
159 0 0         last FILTER if $path;
160 0           die "$err_prefix (module '$mod' is allowed ($allow{$mod}) but can't locate $file in \@INC (\@INC contains: ".join(" ", @INC)."))";
161             }
162 0 0         if ($opts{allow_is_recursive}) {
163 0           my $caller_pkg_from_file;
164 0           for (@$orig_inc_sorted_by_len) {
165             #print "D:\$_=<$_> vs $caller[1]\n";
166 0 0         if (index($caller[1], $_) == 0) {
167 0           $caller_pkg_from_file = substr($caller[1], length($_)+1);
168             #print "D:caller_pkg_from_file=<$caller_pkg_from_file>\n";
169 0           $caller_pkg_from_file =~ s/\.pm\z//;
170 0           $caller_pkg_from_file =~ s/\Q$Config{archname}\E.//;
171 0           $caller_pkg_from_file =~ s![/\\]!::!g;
172             }
173             }
174 0           for my $caller_pkg (grep {defined} $caller[0], $caller_pkg_from_file) {
  0            
175 0           (my $pm = "$caller_pkg.pm") =~ s!::!/!g;
176 0 0         if (exists $INC{$pm}) {
177 0           $path = module_path($file, $orig_inc);
178 0 0         if ($path) {
179 0 0         warn "$dbgh module '$mod' allowed because it is require'd by $caller_pkg (allow_is_recursive=1)\n" if $opts{debug};
180 0           last FILTER;
181             }
182             }
183             }
184             }
185              
186 0           my $inc;
187 0 0 0       if ($opts{allow_noncore} && $opts{allow_core}) {
    0          
    0          
188 0           $inc = $orig_inc;
189             } elsif ($opts{allow_core}) {
190 0           $inc = $core_inc;
191             } elsif ($opts{allow_noncore}) {
192 0           $inc = $noncore_inc;
193             }
194 0 0         if ($inc) {
195 0 0         warn "$dbgh searching $file in (".join(", ", @$inc).")\n" if $opts{debug};
196 0           $path = module_path($file, $inc);
197             }
198 0 0         last FILTER if $path;
199             } # FILTER
200              
201 0 0         unless ($path) {
202 0 0         warn "$dbgh $file not found\n" if $opts{debug};
203 0           return;
204             }
205              
206 0 0         warn "$dbgh $file found at $path\n" if $opts{debug};
207 0           $INC{$file} = $path;
208 0           return _open_handle($path);
209 0           };
210 0     0     $hook = bless(sub{"dummy"}, __PACKAGE__);
  0            
211              
212             @INC = (
213             $hook,
214             grep {
215 0           my $mod = $_;
  0            
216 0 0 0       if ("$mod" eq "$hook") {
    0 0        
    0          
217 0           0;
218 0           } elsif ($opts{allow_core} && grep {$mod eq $_} @$core_inc) {
219 0           1;
220 0           } elsif ($opts{allow_noncore} && grep {$mod eq $_} @$noncore_inc) {
221 0           1;
222             } else {
223 0           0;
224             }
225             } @$orig_inc,
226             );
227             #use DD; dd $orig_inc;
228             #use DD; dd \@INC;
229             }
230              
231             sub unimport {
232 0 0   0     return unless $hook;
233 0           @INC = grep { "$_" ne "$hook" } @INC;
  0            
234             }
235              
236             1;
237             # ABSTRACT: Allow/disallow loading modules
238              
239             __END__