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.021';
5 1     1   526 use strict;
  1         2  
  1         28  
6 1     1   3 use warnings;
  1         2  
  1         34  
7 1     1   3 use Scalar::Util 'blessed';
  1         1  
  1         34  
8 1     1   3 use Carp;
  1         1  
  1         34  
9 1     1   3 use Crypt::SecretBuffer qw( span );
  1         1  
  1         20  
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 1805 my $class= shift;
19 5         41 my $self= bless {}, $class;
20 5         18 while (@_) {
21 5         18 my ($k, $v)= splice(@_, 0, 2);
22 5         19 $self->$k($v);
23             }
24 5   50     15 $self->{raw_kv_array} ||= [];
25 5         17 $self;
26             }
27              
28              
29             sub raw_kv_array {
30 6 100   6 1 17 if (@_ > 1) {
31 5         9 my $kv= $_[1];
32 5 50 33     31 ref $kv eq 'ARRAY' && ($#$kv & 1)
33             or croak "Expected even-length arrayref";
34 5         19 $_[0]{raw_kv_array}= $kv;
35 5         18 return $_[0];
36             }
37             $_[0]{raw_kv_array}
38 1         3 }
39              
40             sub unicode_keys {
41 3 100   3 1 10 if (@_ > 1) {
42 1         3 $_[0]{unicode_keys}= !!$_[1];
43 1         3 return $_[0];
44             }
45             $_[0]{unicode_keys}
46 2         9 }
47             sub unicode_values {
48 21 100   21 1 45 if (@_ > 1) {
49 1         2 $_[0]{unicode_values}= !!$_[1];
50 1         3 return $_[0];
51             }
52             $_[0]{unicode_values}
53 20         50 }
54             sub trim_keys {
55 2 50   2 1 11 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 15 if (@_ > 1) {
63 4         10 $_[0]{caseless_keys}= !!$_[1];
64 4         10 return $_[0];
65             }
66             $_[0]{caseless_keys}
67 0         0 }
68              
69             sub _find_key_idx {
70 16     16   35 my ($self, $key, $first_only)= @_;
71             #print "# _find_key_idx($key)\n";
72 16         41 my $kv= $self->{raw_kv_array};
73 16         26 my ($uni, $trim, $fc)= @{$self}{'unicode_keys','trim_keys','caseless_keys'};
  16         41  
74 16         25 my @ret;
75 16 100       35 if ($uni) {
76 1 50       3 $key= fc($key) if $fc;
77 1         5 for (0..($#$kv-1)/2) {
78 1         2 my $k= $kv->[$_*2];
79 1         5 utf8::decode($k);
80 1 50       3 $k =~ s/^\s+// if $trim;
81 1 50       3 $k =~ s/\s+\z// if $trim;
82 1 50 33     9 push(@ret, $_*2) && $first_only && last
    50 50        
83             if $key eq ($fc? fc($k) : $k);
84             }
85             } else {
86 15         40 utf8::downgrade($key);
87 15 100       262 $key= fc($key) if $fc;
88 15         50 for (0..($#$kv-1)/2) {
89 59         119 my $k= $kv->[$_*2];
90 59 100       119 $k =~ s/^\s+// if $trim;
91 59 100       116 $k =~ s/\s+\z// if $trim;
92 59 100 33     610 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         40 return \@ret;
98             }
99              
100             sub _find_distinct_key_idx {
101 4     4   9 my $self= shift;
102 4         10 my $kv= $self->{raw_kv_array};
103             #print "_find_distinct_key_idx raw_kv = [".join(',', @$kv)."]\n";
104 4         7 my ($uni, $trim, $fc)= @{$self}{'unicode_keys','trim_keys','caseless_keys'};
  4         16  
105 4         9 my (@ret, %seen);
106 4         36 for (0..($#$kv-1)/2) {
107 10         27 my $k= $kv->[$_*2];
108 10 50       25 utf8::decode($k) if $uni;
109 10 100       28 $k =~ s/^\s+// if $trim;
110 10 100       28 $k =~ s/\s+\z// if $trim;
111             push @ret, $_*2
112 10 100       156 unless $seen{$fc? fc($k) : $k}++;
    100          
113             }
114             #print "# _find_distinct_key_idx = [".join(',', @ret)."]\n";
115 4         19 return \@ret;
116             }
117              
118              
119             sub keys {
120 4     4 1 10 my $self= shift;
121 4         40 my $idxs= $self->_find_distinct_key_idx;
122 4         11 my $kv= $self->{raw_kv_array};
123 4         9 return @{$kv}[@$idxs];
  4         20  
124             }
125              
126              
127             sub get_array {
128 12     12 1 31 my ($self, $key)= @_;
129 12         35 my $ret= $self->_find_key_idx($key);
130 12         24 my $kv= $self->{raw_kv_array};
131 12         39 $_= $kv->[$_+1] for @$ret;
132 12 100       28 if ($self->unicode_values) {
133 1         19 utf8::decode($_) for @$ret
134             }
135 12         24 return $ret;
136             }
137              
138              
139             sub get {
140 12     12 1 37 my $vals= shift->get_array(@_);
141 12 100       83 return @$vals > 1? $vals : $vals->[0];
142             }
143              
144              
145             sub _validate_new_key {
146 2     2   4 my $key= shift;
147 2 50       5 croak "Key must be a plain scalar"
148             if ref $key;
149 2 50 33     29 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   9 my $val= shift;
154 8 50       11 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 7 my ($self, $key, $value)= @_;
165 3         4 my $kv= $self->{raw_kv_array};
166 3         6 my $idxs= $self->_find_key_idx($key);
167 3         4 my $idx= shift @$idxs;
168 3 100       7 if (!defined $idx) {
169 1         4 _validate_new_key($key);
170 1 50       4 $self->unicode_keys? utf8::encode($key) : utf8::downgrade($key);
171             } else {
172 2         3 $key= $kv->[$idx];
173             }
174 3         4 my @ins;
175 3 100       9 for (ref $value eq 'ARRAY'? @$value : $value) {
176 7         15 _validate_value(my $v= $_);
177 7 50       13 $self->unicode_values? utf8::encode($v) : utf8::downgrade($v)
    50          
178             unless ref $v;
179 7         9 push @ins, $key, $v;
180             }
181 3         7 splice(@$kv, $_, 2) for reverse @$idxs;
182 3 100       6 $idx= @$kv unless defined $idx;
183 3         7 splice(@$kv, $idx, 2, @ins);
184 3         8 $self;
185             }
186              
187              
188             sub append {
189 1     1 1 3 my ($self, $key, $value)= @_;
190 1         3 _validate_new_key($key);
191 1         3 _validate_value($value);
192 1 50       59 $self->unicode_keys? utf8::encode($key) : utf8::downgrade($key);
193 1 50       4 $self->unicode_values? utf8::encode($value) : utf8::downgrade($value)
    50          
194             unless ref $value;
195 1         1 push @{$self->raw_kv_array}, $key, $value;
  1         3  
196             }
197              
198              
199             sub delete {
200 1     1 1 3 my ($self, $key)= @_;
201 1         4 my $idxs= $self->_find_key_idx($key);
202 1         3 my $kv= $self->{raw_kv_array};
203 1         6 my @ret= map $kv->[$_+1], @$idxs;
204 1         4 splice(@$kv, $_, 2) for reverse @$idxs;
205 1         4 return @ret;
206             }
207              
208             sub _create_tied_hashref {
209 4     4   8 my $self= shift;
210 4         21 my %hash;
211 4         24 tie %hash, 'Crypt::SecretBuffer::PEM::Headers::_HASH', $self;
212 4         62 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         25 bless [ $headers, [] ], $classname;
218             }
219 8     8   41 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   290 sub Crypt::SecretBuffer::PEM::Headers::_HASH::FIRSTKEY { $_[0][1]= [ $_[0][0]->keys ]; shift @{$_[0][1]} }
  4         9  
  4         49  
225 8     8   15 sub Crypt::SecretBuffer::PEM::Headers::_HASH::NEXTKEY { shift @{$_[0][1]} }
  8         34  
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   147 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::unicode_keys { tied(%{+shift})->[0]->unicode_keys(@_) }
  1         5  
230 1     1   130 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::unicode_values { tied(%{+shift})->[0]->unicode_values(@_) }
  1         4  
231 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::trim_keys { tied(%{+shift})->[0]->trim_keys(@_) }
  0         0  
232 1     1   2 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::caseless_keys { tied(%{+shift})->[0]->caseless_keys(@_) }
  1         6  
233 0     0   0 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::keys { tied(%{+shift})->[0]->keys(@_) }
  0         0  
234 1     1   144 sub Crypt::SecretBuffer::PEM::Headers::_Proxy::get { tied(%{+shift})->[0]->get(@_) }
  1         4  
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__