File Coverage

blib/lib/Crypt/SecretBuffer/PEM/Headers.pm
Criterion Covered Total %
statement 144 167 86.2
branch 54 74 72.9
condition 8 27 29.6
subroutine 30 41 73.1
pod 12 12 100.0
total 248 321 77.2


line stmt bran cond sub pod time code
1             package Crypt::SecretBuffer::PEM::Headers;
2             # VERSION
3             # ABSTRACT: Inspect or alter arrayref of PEM headers as if it was a hashref
4             $Crypt::SecretBuffer::PEM::Headers::VERSION = '0.020';
5 1     1   1904 use strict;
  1         3  
  1         46  
6 1     1   7 use warnings;
  1         2  
  1         64  
7 1     1   9 use Scalar::Util 'blessed';
  1         2  
  1         6347  
8 1     1   13 use Carp;
  1         2  
  1         103  
9 1     1   9 use Crypt::SecretBuffer qw( span );
  1         2  
  1         11  
10             if ("$]" < 5.016) {
11             eval 'sub fc { lc($_[0]) }'
12             } else {
13             eval 'sub fc { CORE::fc($_[0]) }';
14             }
15              
16              
17             sub new {
18 5     5 1 2489 my $class= shift;
19 5         15 my $self= bless {}, $class;
20 5         19 while (@_) {
21 5         19 my ($k, $v)= splice(@_, 0, 2);
22 5         21 $self->$k($v);
23             }
24 5   50     15 $self->{raw_kv_array} ||= [];
25 5         19 $self;
26             }
27              
28              
29             sub raw_kv_array {
30 6 100   6 1 18 if (@_ > 1) {
31 5         10 my $kv= $_[1];
32 5 50 33     34 ref $kv eq 'ARRAY' && ($#$kv & 1)
33             or croak "Expected even-length arrayref";
34 5         40 $_[0]{raw_kv_array}= $kv;
35 5         17 return $_[0];
36             }
37             $_[0]{raw_kv_array}
38 1         6 }
39              
40             sub unicode_keys {
41 3 100   3 1 12 if (@_ > 1) {
42 1         4 $_[0]{unicode_keys}= !!$_[1];
43 1         4 return $_[0];
44             }
45             $_[0]{unicode_keys}
46 2         11 }
47             sub unicode_values {
48 21 100   21 1 52 if (@_ > 1) {
49 1         3 $_[0]{unicode_values}= !!$_[1];
50 1         4 return $_[0];
51             }
52             $_[0]{unicode_values}
53 20         60 }
54             sub trim_keys {
55 2 50   2 1 9 if (@_ > 1) {
56 2         7 $_[0]{trim_keys}= !!$_[1];
57 2         6 return $_[0];
58             }
59             $_[0]{trim_keys}
60 0         0 }
61             sub caseless_keys {
62 4 50   4 1 19 if (@_ > 1) {
63 4         13 $_[0]{caseless_keys}= !!$_[1];
64 4         15 return $_[0];
65             }
66             $_[0]{caseless_keys}
67 0         0 }
68              
69             sub _find_key_idx {
70 16     16   38 my ($self, $key, $first_only)= @_;
71             #print "# _find_key_idx($key)\n";
72 16         33 my $kv= $self->{raw_kv_array};
73 16         43 my ($uni, $trim, $fc)= @{$self}{'unicode_keys','trim_keys','caseless_keys'};
  16         46  
74 16         29 my @ret;
75 16 100       39 if ($uni) {
76 1 50       4 $key= fc($key) if $fc;
77 1         7 for (0..($#$kv-1)/2) {
78 1         34 my $k= $kv->[$_*2];
79 1         7 utf8::decode($k);
80 1 50       4 $k =~ s/^\s+// if $trim;
81 1 50       4 $k =~ s/\s+\z// if $trim;
82 1 50 33     34 push(@ret, $_*2) && $first_only && last
    50 50        
83             if $key eq ($fc? fc($k) : $k);
84             }
85             } else {
86 15         46 utf8::downgrade($key);
87 15 100       289 $key= fc($key) if $fc;
88 15         63 for (0..($#$kv-1)/2) {
89 59         148 my $k= $kv->[$_*2];
90 59 100       177 $k =~ s/^\s+// if $trim;
91 59 100       168 $k =~ s/\s+\z// if $trim;
92 59 100 33     1016 push(@ret, $_*2) && $first_only && last
    100 50        
93             if $key eq ($fc? fc($k) : $k);
94             }
95             }
96             #print "# found at [".join(',', @ret)."]\n";
97 16         49 return \@ret;
98             }
99              
100             sub _find_distinct_key_idx {
101 4     4   9 my $self= shift;
102 4         8 my $kv= $self->{raw_kv_array};
103             #print "_find_distinct_key_idx raw_kv = [".join(',', @$kv)."]\n";
104 4         10 my ($uni, $trim, $fc)= @{$self}{'unicode_keys','trim_keys','caseless_keys'};
  4         12  
105 4         9 my (@ret, %seen);
106 4         19 for (0..($#$kv-1)/2) {
107 10         23 my $k= $kv->[$_*2];
108 10 50       28 utf8::decode($k) if $uni;
109 10 100       29 $k =~ s/^\s+// if $trim;
110 10 100       28 $k =~ s/\s+\z// if $trim;
111             push @ret, $_*2
112 10 100       170 unless $seen{$fc? fc($k) : $k}++;
    100          
113             }
114             #print "# _find_distinct_key_idx = [".join(',', @ret)."]\n";
115 4         16 return \@ret;
116             }
117              
118              
119             sub keys {
120 4     4 1 9 my $self= shift;
121 4         13 my $idxs= $self->_find_distinct_key_idx;
122 4         10 my $kv= $self->{raw_kv_array};
123 4         10 return @{$kv}[@$idxs];
  4         21  
124             }
125              
126              
127             sub get_array {
128 12     12 1 29 my ($self, $key)= @_;
129 12         33 my $ret= $self->_find_key_idx($key);
130 12         25 my $kv= $self->{raw_kv_array};
131 12         44 $_= $kv->[$_+1] for @$ret;
132 12 100       32 if ($self->unicode_values) {
133 1         6 utf8::decode($_) for @$ret
134             }
135 12         26 return $ret;
136             }
137              
138              
139             sub get {
140 12     12 1 40 my $vals= shift->get_array(@_);
141 12 100       91 return @$vals > 1? $vals : $vals->[0];
142             }
143              
144              
145             sub _validate_new_key {
146 2     2   6 my $key= shift;
147 2 50       8 croak "Key must be a plain scalar"
148             if ref $key;
149 2 50 33     26 croak "Key '$key' contains ':', control characters, or leading/trailing whitespace"
      33        
150             if $key =~ /[:\0-\x1F\x7F]/ or $key =~ /^\s+/ or $key =~ /\s+\z/;
151             }
152             sub _validate_value {
153 8     8   14 my $val= shift;
154 8 50       20 if (ref $val) {
155 0 0 0     0 croak "Value is not a SecretBuffer or Span (stringify the PEM header values before assigning them)"
      0        
156             unless blessed($val) && (
157             $val->isa('Crypt::SecretBuffer')
158             || $val->isa('Crypt::SecretBuffer::Span')
159             );
160             }
161             }
162              
163             sub set {
164 3     3 1 11 my ($self, $key, $value)= @_;
165 3         7 my $kv= $self->{raw_kv_array};
166 3         9 my $idxs= $self->_find_key_idx($key);
167 3         8 my $idx= shift @$idxs;
168 3 100       9 if (!defined $idx) {
169 1         6 _validate_new_key($key);
170 1 50       5 $self->unicode_keys? utf8::encode($key) : utf8::downgrade($key);
171             } else {
172 2         6 $key= $kv->[$idx];
173             }
174 3         6 my @ins;
175 3 100       13 for (ref $value eq 'ARRAY'? @$value : $value) {
176 7         23 _validate_value(my $v= $_);
177 7 50       23 $self->unicode_values? utf8::encode($v) : utf8::downgrade($v)
    50          
178             unless ref $v;
179 7         18 push @ins, $key, $v;
180             }
181 3         10 splice(@$kv, $_, 2) for reverse @$idxs;
182 3 100       8 $idx= @$kv unless defined $idx;
183 3         12 splice(@$kv, $idx, 2, @ins);
184 3         12 $self;
185             }
186              
187              
188             sub append {
189 1     1 1 5 my ($self, $key, $value)= @_;
190 1         5 _validate_new_key($key);
191 1         5 _validate_value($value);
192 1 50       5 $self->unicode_keys? utf8::encode($key) : utf8::downgrade($key);
193 1 50       6 $self->unicode_values? utf8::encode($value) : utf8::downgrade($value)
    50          
194             unless ref $value;
195 1         3 push @{$self->raw_kv_array}, $key, $value;
  1         5  
196             }
197              
198              
199             sub delete {
200 1     1 1 4 my ($self, $key)= @_;
201 1         5 my $idxs= $self->_find_key_idx($key);
202 1         3 my $kv= $self->{raw_kv_array};
203 1         8 my @ret= map $kv->[$_+1], @$idxs;
204 1         6 splice(@$kv, $_, 2) for reverse @$idxs;
205 1         5 return @ret;
206             }
207              
208             sub _create_tied_hashref {
209 4     4   7 my $self= shift;
210 4         9 my %hash;
211 4         27 tie %hash, 'Crypt::SecretBuffer::PEM::Headers::_HASH', $self;
212 4         52 return bless \%hash, 'Crypt::SecretBuffer::PEM::Headers::_Proxy';
213             }
214              
215             sub Crypt::SecretBuffer::PEM::Headers::_HASH::TIEHASH {
216 4     4   12 my ($classname, $headers)= @_;
217 4         20 bless [ $headers, [] ], $classname;
218             }
219 8     8   29 sub Crypt::SecretBuffer::PEM::Headers::_HASH::FETCH { $_[0][0]->get($_[1]) }
220 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_HASH::STORE { $_[0][0]->set($_[1], $_[2]) }
221 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_HASH::DELETE { $_[0][0]->delete($_[1]) }
222 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_HASH::CLEAR { @{ $_[0][0]->raw_kv_array }= () }
  0         0  
223 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_HASH::EXISTS { !!@{ $_[0][0]->_find_key_idx($_[1], 1) } }
  0         0  
224 4     4   265 sub Crypt::SecretBuffer::PEM::Headers::_HASH::FIRSTKEY { $_[0][1]= [ $_[0][0]->keys ]; shift @{$_[0][1]} }
  4         50  
  4         40  
225 8     8   17 sub Crypt::SecretBuffer::PEM::Headers::_HASH::NEXTKEY { shift @{$_[0][1]} }
  8         32  
226             # This class is used to bless the tied hash making it both a magic
227             # hashref and an object with methods.
228 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::raw_kv_array { tied(%{+shift})->[0]->raw_kv_array(@_) }
  0         0  
229 1     1   283 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::unicode_keys { tied(%{+shift})->[0]->unicode_keys(@_) }
  1         8  
230 1     1   238 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::unicode_values { tied(%{+shift})->[0]->unicode_values(@_) }
  1         6  
231 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::trim_keys { tied(%{+shift})->[0]->trim_keys(@_) }
  0         0  
232 1     1   3 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::caseless_keys { tied(%{+shift})->[0]->caseless_keys(@_) }
  1         7  
233 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::keys { tied(%{+shift})->[0]->keys(@_) }
  0         0  
234 1     1   267 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::get { tied(%{+shift})->[0]->get(@_) }
  1         6  
235 0     0     sub Crypt::SecretBuffer::PEM::Headers::_Proxy::get_array { tied(%{+shift})->[0]->get_array(@_) }
  0            
236 0     0     sub Crypt::SecretBuffer::PEM::Headers::_Proxy::set { tied(%{+shift})->[0]->set(@_) }
  0            
237 0     0     sub Crypt::SecretBuffer::PEM::Headers::_Proxy::delete { tied(%{+shift})->[0]->delete(@_) }
  0            
238 0     0     sub Crypt::SecretBuffer::PEM::Headers::_Proxy::append { tied(%{+shift})->[0]->append(@_) }
  0            
239              
240             # avoid depending on namespace::clean
241             delete @{Crypt::SecretBuffer::PEM::Headers::}{qw( carp croak confess span fc blessed )};
242              
243             1;
244              
245             __END__