File Coverage

blib/lib/Tie/SecureHash.pm
Criterion Covered Total %
statement 171 258 66.2
branch 107 158 67.7
condition 15 35 42.8
subroutine 18 23 78.2
pod 3 6 50.0
total 314 480 65.4


line stmt bran cond sub pod time code
1             package Tie::SecureHash;
2              
3 4     4   4605 use strict;
  4         7  
  4         201  
4             our ($VERSION, $strict, $fast, $dangerous, $loud);
5 4     4   17 use Carp;
  4         6  
  4         19213  
6              
7             $VERSION = '1.10';
8              
9             sub import {
10 4     4   37 my ($pkg, @args) = @_;
11 4         14 my $args = join(' ', @args);
12 4         19 $strict = $args =~ /\bstrict\b/;
13 4         15 $fast = $args =~ /\bfast\b/;
14 4         10 $dangerous = $args =~ /\bdangerous\b/;
15 4         8 $loud = $args =~ /\bloud\b/;
16 4 50 66     25 croak qq{$pkg can't be both "strict" and "fast"} if $strict && $fast;
17 4 100       19125 $strict = 1 if $loud;
18             }
19              
20             # TAKE A LIST OF POSSIBLE CLASSES FOR AN IMPLICIT KEY AND REMOVE NON-CONTENDERS
21              
22             sub _winnow {
23 64     64   184 my ($caller, $nonpublic, @classes) = @_;
24              
25             # REMOVE CLASSES NOT IN HIERARCHY FOR NON-PUBLIC KEY
26              
27 64 100       162 @classes = grep { $caller->isa($_) } @classes if $nonpublic;
  44         195  
28              
29             # COMPARE REMAINING KEYS PAIRWISE, ELIMINATING "SHADOWED" KEYS...
30              
31 64         187 I: for(my $i=0; $i<$#classes; )
32             {
33 34         93 J: for(my $j=$i+1; $j<@classes; )
34             {
35 46 100       246 if ($classes[$j]->isa($classes[$i])) {
    50          
36             # CLASS J SHADOWS I FROM $caller
37 26 100       125 if ($caller->isa($classes[$j])) {
    100          
38 18         26 splice @classes,$i,1;
39 18         52 next I;
40             }
41             # CLASS J BELOW CALLING PACKAGE
42             # (SO CALLING PACKAGE COULDN'T INHERIT IT)
43             elsif ($classes[$j]->isa($caller)) {
44 2         6 splice @classes,$j,1;
45 2         10 next J;
46             }
47             } elsif ($classes[$i]->isa($classes[$j])) {
48             # CLASS I SHADOWS J FROM $caller
49 0 0       0 if ($caller->isa($classes[$i])) {
    0          
50 0         0 splice @classes,$j,1;
51 0         0 next J;
52             }
53             # CLASS I BELOW CALLING PACKAGE
54             # (SO CALLING PACKAGE COULDN'T INHERIT IT)
55             elsif ($classes[$i]->isa($caller)) {
56 0         0 splice @classes,$i,1;
57 0         0 next I;
58             }
59             }
60 26         68 $j++;
61              
62             }
63 16         41 $i++;
64             }
65              
66 64         175 return @classes;
67             }
68             ;
69              
70             # DETERMINE IF A KEY IS ACCESSIBLE
71              
72             sub _access {
73 1302     1302   2516 my ($self, $key, $caller, $file, $delete) = @_;
74              
75             # EXPLICIT KEYS...
76              
77 1302 100       6737 if ($key =~ /\A([\w:]*)::((_{0,2})[^:]+)\Z/) {
    100          
    50          
78 1156         3221 my ($classname, $shortkey, $mode) = ($1,$2,$3);
79 1156 100       2556 unless ($classname)
80             {
81 36         51 $classname = 'main';
82 36         89 $key = $classname.$key;
83             }
84 1156 100       3248 if ($mode eq '__') # PRIVATE
    100          
85             {
86 248 100       15385 croak "Private key $key of tied securehash inaccessible from package $caller"
87             unless $classname eq $caller;
88 116 100       303 if (exists $self->{fullkeys}{$key}) {
89             croak "Private key $key of tied securehash inaccessible from file $file"
90 96 50       317 if $self->{file}{$key} ne $file;
91             } else {
92 20 50       38 if ($delete) {
93 0         0 delete $self->{file}{$key};
94             } else {
95 20         53 $self->{file}{$key} = $file;
96             }
97             }
98             } elsif ($mode eq '_') # PROTECTED
99             {
100 318 100       20816 croak "Protected key $key of tied securehash inaccessible from package $caller"
101             unless $caller->isa($classname);
102             }
103              
104 848 100       2507 if (!exists $self->{fullkeys}{$key}) {
105 88 100 100     969 croak "Entry for key $key of tied securehash cannot be created " .
106             "from package $caller"
107             if $classname ne $caller && !$delete;
108 80 100       121 if ($delete) {
109 4         14 @{$self->{keylist}{$shortkey}} =
110 0         0 grep { $_ !~ /$classname/ }
111 4         9 @{$self->{keylist}{$shortkey}}
  4         14  
112             } else {
113 76         78 push @{$self->{keylist}{$shortkey}}, $classname;
  76         295  
114             }
115             }
116             }
117              
118             # IMPLICIT PRIVATE KEY (MUST BE IN CALLING CLASS)
119             elsif ($key =~ /\A(__[^:]+)\Z/) {
120             carp qq{Accessing securehash via unqualified key {"$key"}\n}.
121             qq{will be unsafe in 'fast' mode. Use {"${caller}::$key"}}
122 36 50 66     123 if $strict && $ENV{UNSAFE_WARN};
123 36 100       670 if (!exists $self->{fullkeys}{"${caller}::$key"}) {
124             croak "Private key '$key' of tied securehash is inaccessible from package $caller"
125 22 100       1051 if exists $self->{keylist}{$key};
126 12         1664 croak "Private key '${caller}::$key' does not exist in tied securehash"
127             }
128 14         31 $key = "${caller}::$key";
129 14 50       53 if (exists $self->{fullkeys}{$key}) {
130             croak "Private key $key of tied securehash inaccessible from file $file"
131 14 50       50 if $self->{file}{$key} ne $file;
132             }
133             }
134              
135             # IMPLICIT PROTECTED OR PUBLIC KEY
136             # (PROTECTED KEY MUST BE IN ANCESTRAL HIERARCHY OF CALLING CLASS)
137             elsif ($key =~ /\A((_?)[^:]+)\Z/) {
138 110         256 my $fullkey = "${caller}::$key";
139             carp qq{Accessing securehash via unqualified key {"$key"}\n}.
140             qq{will be unsafe in 'fast' mode. Use {"${caller}::$key"}}
141 110 50 66     403 if $strict && $ENV{UNSAFE_WARN};
142 110 100       284 if (exists $self->{fullkeys}{$fullkey}) {
143 46         102 $key = $fullkey;
144             } else {
145             my @classes = _winnow($caller, $2,
146 64 100       87 @{$self->{keylist}{$key}||[]});
  64         334  
147            
148 64 100       136 if (@classes) {
149             # TOO MANY CHOICES
150             croak "Ambiguous key '$key' (when accessed "
151             . "from package $caller).\nCould be:\n"
152 32 100       107 . join("", map {"\t${_}::$key\n"} @classes)
  24         1742  
153             . " "
154             if @classes > 1;
155 22         65 $key = $classes[0]."::$key";
156             } else # NOT ENOUGH CHOICES
157             {
158             croak +($2?"Protected":"Public")." key '$key' of tied securehash is inaccessible from package $caller"
159 32 100       882 if exists $self->{keylist}{$key};
    100          
160 24 100       2378 croak +($2?"Protected":"Public")." key '${caller}::$key' does not exist in tied securehash";
161             }
162             }
163             } else # INVALID KEY
164             {
165 0         0 croak "Invalid key '$key'";
166             }
167              
168 922 100       1753 if ($delete) {
169 4         23 return delete $self->{fullkeys}{$key};
170             }
171 918         2707 return \$self->{fullkeys}{$key};
172             }
173              
174             sub _dangerous_access {
175 74     74   148 my ($self,$key,$caller, $action) = @_;
176 74 50       255 _complain(@_) if $strict;
177 74         31603 require mro;
178 74         907 my @isa = @{mro::get_linear_isa($caller)}; # mro seems to return a weird read only arrayref
  74         352  
179 74 50       258 pop @isa if $isa[-1] eq 'Exporter';
180 74         133 my @candidate_keys = map { "$_::$key" } @isa;
  131         438  
181 74         110 my $val;
182 74         129 foreach my $k (@candidate_keys) {
183 95 100       217 if ($action eq 'DELETE') {
184 1         3 my $deleted;
185 1 50       5 if (exists $self->{fullkeys}->{$k}) {
186 0         0 delete $self->{fullkeys}->{$k};
187 0         0 $deleted = 1;
188             }
189 1 50       3 last if $deleted;
190             } else {
191 94         202 $val = $self->{fullkeys}->{$k};
192 94 100       251 last if $val;
193             }
194             }
195 74         238 return \$val;
196             }
197              
198             sub _complain { # override complain with Role::Tiny to customise dump.
199 83     83   155 my ($self, $key, $caller, $action) = @_;
200 83         144 $DB::single=1;
201 83 50       10918 carp "Ran an expensive dangerous $action due to unqualified key $key being sent in to hash for $caller" if $strict;
202 83 50       6835 if ($loud) {
203 83         1434 require Data::Dumper;
204 83         7949 Data::Dumper->import;
205 83         315 carp Dumper ($self->{fullkeys});
206             }
207             }
208              
209             # NOTE THAT NEW MAY TIE AND BLESS INTO THE SAME CLASS
210             # IF NOTHING MORE APPROPRIATE IS SPECIFIED
211              
212             sub new {
213 4     4 1 1249 my %self = ();
214 4   33     30 my $class = ref($_[0])||$_[0];
215 4   33     35 my $blessclass = ref($_[1])||$_[1]||$class;
216 4 100       28 my $impl = tie %self, $class unless $fast;
217 4         11 my $self = bless \%self, $blessclass;
218 4         12 splice(@_,0,2);
219 4 50       17 if (@_) # INITIALIZATION ARGUMENTS PRESENT
220             {
221 0         0 my ($ancestor, $file);
222 0         0 my $i = 0;
223 0         0 while ( ($ancestor,$file) = caller($i++) ) {
224 0 0       0 last if $ancestor eq $blessclass;
225             }
226 0 0       0 $file = "" if ! defined $file; # dms 14 Mar 2000: satisfy -w switch
227 0         0 my ($key, $value);
228 0         0 while (($key,$value) = splice(@_,0,2)) {
229 0 0       0 my $fullkey = $key=~/::/ ? $key : "${blessclass}::$key";
230 0 0       0 if ($fast) {
231 0         0 $self->{$fullkey} = $value;
232             } else {
233 0         0 $impl->{fullkeys}{$fullkey} = $value;
234 0         0 push @{$impl->{keylist}{$key}}, $blessclass;
  0         0  
235 0 0       0 $impl->{file}{$fullkey} = $file
236             if $key =~ /\A__/;
237             }
238             }
239             }
240              
241 4         15 return $self;
242             }
243              
244             # USEFUL METHODS TO DUMP INFORMATION
245              
246             sub debug {
247 0     0 0 0 my $self = tied %{$_[0]};
  0         0  
248 0   0     0 my ($caller, $file, $line, $sub) = (caller,(caller(1))[3]||"(none)");
249 0 0       0 return _simple_debug($_[0],$caller, $file, $line, $sub) unless $self;
250 0         0 my ($key, $val);
251 0         0 my %sorted = ();
252 0         0 while ($key = CORE::each %{$self->{fullkeys}}) {
  0         0  
253 0         0 $key =~ m/\A(.*?)([^:]*)\Z/;
254 0         0 push @{$sorted{$1}}, $key;
  0         0  
255             }
256              
257 0         0 print STDERR "\nIn subroutine '$sub' called from package '$caller' ($file, line $line):\n";
258 0         0 foreach my $class (CORE::keys %sorted) {
259 0         0 print STDERR "\n\t$class\n";
260 0         0 foreach $key ( @{$sorted{$class}} ) {
  0         0  
261 0         0 print STDERR "\t\t";
262 0         0 my ($shortkey) = $key =~ /.*::(.*)/;
263 0         0 my $explanation = "";
264 0 0       0 if (eval { _access($self,$shortkey,$caller, $file); 1 }) {
  0 0       0  
  0         0  
265 0         0 print STDERR '(+)';
266             } elsif ($@ =~ /\AAmbiguous key/) {
267 0         0 print STDERR '(?)';
268 0         0 ($explanation = $@) =~ s/.*\n//;
269 0         0 $explanation =~ s/.*\n\Z//;
270 0         0 $explanation =~ s/\ACould/Ambiguous unless fully qualified. Could/;
271 0         0 $explanation =~ s/^(?!\Z)/\t\t\t>>> /gm;
272             } else {
273 0         0 print STDERR '(-)';
274 0 0 0     0 if ($shortkey =~ /\A__/ && $@ =~ /file/) {
    0          
275 0         0 $explanation = "\t\t\t>>> Private entry of $class\n\t\t\t>>> declared in file $self->{file}{$key}\n\t\t\t>>> is inaccessable from file $file.\n"
276             } elsif ($shortkey =~ /\A__/) {
277 0         0 $explanation = "\t\t\t>>> Private entry of $class\n\t\t\t>>> is inaccessable from package $caller.\n"
278             } else {
279 0         0 $explanation = "\t\t\t>>> Protected entry of $class\n\t\t\t>>> is inaccessible outside its hierarchy (i.e. from $caller).\n"
280             }
281            
282             }
283 0         0 my $val = $self->{fullkeys}{$key};
284 0 0       0 if (defined $val) {
285 0         0 $val = "'$val'";
286             } else {
287 0         0 $val = "undef";
288             }
289 0         0 print STDERR " '$shortkey'\t=> $val";
290 0 0       0 print STDERR "\n$explanation" if $explanation;
291 0         0 print STDERR "\n";
292             }
293             }
294             }
295              
296             sub _simple_debug {
297 0     0   0 my ($self,$caller, $file, $line, $sub) = @_;
298 0         0 my ($key, $val);
299 0         0 my %sorted = ();
300 0         0 while ($key = CORE::each %{$self}) {
  0         0  
301 0         0 $key =~ m/\A(.*?)([^:]*)\Z/;
302 0         0 push @{$sorted{$1}}, $key;
  0         0  
303             }
304              
305 0         0 print "\nIn subroutine '$sub' called from package '$caller' ($file, line $line):\n";
306 0         0 foreach my $class (CORE::keys %sorted) {
307 0         0 print "\n\t$class\n";
308 0         0 foreach $key ( @{$sorted{$class}} ) {
  0         0  
309 0         0 print "\t\t";
310 0         0 print " '$key'\t=> '$self->{$key}'\n";
311             }
312             }
313             }
314              
315              
316 35     35 0 19006 sub each($) { CORE::each %{$_[0]} }
  35         157  
317 0     0 1 0 sub keys($) { CORE::keys %{$_[0]} }
  0         0  
318 4     4 0 168 sub values($) { CORE::values %{$_[0]} }
  4         22  
319 0     0 1 0 sub exists($$) { CORE::exists $_[0]->{$_[1]} }
320              
321             sub TIEHASH { # ($class, @args)
322 3   33 3   15 my $class = ref($_[0]) || $_[0];
323 3 100       15 if ($strict) {
    50          
324             carp qq{Tie'ing a securehash directly will be unsafe in 'fast' mode.\n}.
325             qq{Use Tie::SecureHash::new instead}
326             unless (caller 1)[3] =~ /\A(.*?)::([^:]*)\Z/
327             && $2 eq "new"
328 2 50 33     545 && "$1"->isa('Tie::SecureHash') && $ENV{UNSAFE_WARN};
      33        
      33        
329             } elsif ($fast) {
330 0         0 carp qq{Tie'ing a securehash directly should never happen in 'fast' mode.\n}.
331             qq{Use Tie::SecureHash::new instead}
332             }
333 3         16 bless {}, $class;
334             }
335              
336             sub FETCH { # ($self, $key)
337 595     595   16863 my ($self, $key) = @_;
338 595         925 my $entry;
339 595 100       5480 if (! $dangerous) {
    100          
340 460         1541 $entry = _access($self,$key,(caller)[0..1]);
341             } elsif ($key =~ /::/) {
342 72         411 $entry = \$self->{fullkeys}->{$key};
343             } else {
344 63         216 my $caller = (caller)[0];
345 63         186 $entry = $self->_dangerous_access($key, $caller, 'FETCH');
346             }
347 555 50       4017 return $$entry if $entry;
348 0         0 return;
349             }
350              
351             sub STORE { # ($self, $key, $value)
352 151     151   11030 my ($self, $key, $value) = @_;
353 151         181 my $entry;
354 151 100       609 if (! $dangerous) {
    100          
355 100         339 $entry = _access($self,$key,(caller)[0..1]);
356             } elsif ($key =~ /::/) {
357 41         205 $self->{fullkeys}->{$key} = $value;
358 41         89 $entry = \$self->{fullkeys}->{$key};
359             } else {
360 10         30 my $caller = (caller)[0];
361 10         63 $entry = $self->_dangerous_access($key,$caller, 'STORE');
362             }
363 115 50       842 return $$entry = $value if $entry;
364 0         0 return;
365             }
366              
367             sub DELETE { # ($self, $key)
368 9     9   387 my ($self, $key) = @_;
369 9 100       34 if (! $dangerous) {
    100          
370 6         29 return _access($self,$key,(caller)[0..1],'DELETE');
371             }
372             elsif ($key =~ /::/) {
373 2         7 delete $self->{fullkeys}->{$key};
374             }
375             else {
376 1         3 my $caller = (caller)[0];
377 1         4 return $self->_dangerous_access($key, $caller, 'DELETE');
378             }
379             }
380              
381              
382             sub CLEAR { # ($self)
383 6     6   639 my ($self) = @_;
384 6 100       23 if ($dangerous) {
385 2         13 %$self = ();
386             }
387             else {
388 4         17 my ($caller, $file) = caller;
389             my @inaccessibles =
390 152         1971 grep { ! eval { _access($self,$_,$caller,$file); 1 } }
  152         358  
  92         294  
391 4         11 CORE::keys %{$self->{fullkeys}};
  4         66  
392             croak "Unable to assign to securehash because the following existing keys\nare inaccessible from package $caller and cannot be deleted:\n" .
393 4 50       139 join("\n", map {"\t$_"} @inaccessibles) . "\n "
  60         573  
394             if @inaccessibles;
395 0         0 %{$self} = ();
  0         0  
396             }
397             }
398              
399             sub EXISTS # ($self, $key)
400             {
401 146     146   5947 my ($self, $key) = @_;
402 146 100       358 if (! $dangerous) {
    100          
403 128         527 my @context = (caller)[0..1];
404 128 100       238 eval { _access($self,$key,@context); 1 } ? 1 : '';
  128         298  
  114         742  
405             }
406             elsif ($key =~ /::/) {
407 9         36 return exists $self->{fullkeys}->{$key};
408             }
409             else {
410 9         19 my $caller = (caller)[0];
411 9 50       55 _complain($self, $key, $caller, 'EXISTS') if $strict;
412 9         1446 return exists $self->{fullkeys}->{"$caller::$key"};
413             }
414             }
415              
416             sub FIRSTKEY # ($self)
417             {
418 18     18   2835 my ($self) = @_;
419 18         26 CORE::keys %{$self->{fullkeys}};
  18         40  
420 18         58 goto &NEXTKEY;
421             }
422              
423             sub NEXTKEY # ($self)
424             {
425 246     246   12580 my $self = $_[0];
426 246 100       546 if ($dangerous) {
427 6         6 return CORE::each %{$self->{fullkeys}};
  6         20  
428             }
429 240         271 my $key;
430 240         780 my @context = (caller)[0..1];
431 240         400 while (defined($key = CORE::each %{$self->{fullkeys}})) {
  468         1707  
432 456 100       809 last if eval { _access($self,$key,@context) };
  456         884  
433 228 50       7766 carp "Attempt to iterate inaccessible key '$key' will be unsafe in 'fast' mode. Use explicit keys" if $ENV{UNSAFE_WARN};
434            
435             }
436 240         1182 return $key;
437             }
438              
439       0     sub DESTROY { # ($self)
440             # NOTHING TO DO
441             # (BE CAREFUL SINCE IT DOES DOUBLE DUTY FOR tie AND bless)
442             }
443              
444              
445             1;
446             __END__