File Coverage

blib/lib/File/KDBX/IO.pm
Criterion Covered Total %
statement 123 207 59.4
branch 31 58 53.4
condition 19 53 35.8
subroutine 30 63 47.6
pod 15 38 39.4
total 218 419 52.0


line stmt bran cond sub pod time code
1             package File::KDBX::IO;
2             # ABSTRACT: Base IO class for KDBX-related streams
3              
4 10     3645   5355 use warnings;
  10         22  
  10         335  
5 10     1824   46 use strict;
  10         22  
  10         186  
6              
7 10     10   1548 use Devel::GlobalDestruction;
  10         2178  
  10         65  
8 10     10   641 use File::KDBX::Constants qw(:bool);
  10         28  
  10         4215  
9 10     10   69 use File::KDBX::Util qw(:class :empty);
  10         15  
  10         940  
10 10     10   60 use List::Util qw(sum0);
  10         16  
  10         517  
11 10     10   51 use Ref::Util qw(is_blessed_ref is_ref is_scalarref);
  10         21  
  10         459  
12 10     10   1456 use Symbol qw(gensym);
  10         2208  
  10         534  
13 10     10   57 use namespace::clean;
  10         20  
  10         66  
14              
15             extends 'IO::Handle';
16              
17             our $VERSION = '0.906'; # VERSION
18              
19 0     0   0 sub _croak { require Carp; goto &Carp::croak }
  0         0  
20              
21             my %ATTRS = (
22             _append_output => 0,
23             _buffer_in => sub { [] },
24             _buffer_out => sub { [] },
25             _error => undef,
26             _fh => undef,
27             _mode => '',
28             );
29             while (my ($attr, $default) = each %ATTRS) {
30 10     10   4203 no strict 'refs'; ## no critic (ProhibitNoStrict)
  10         21  
  10         20066  
31             *$attr = sub {
32 2591     2591   2869 my $self = shift;
33 2591 100       4052 *$self->{$attr} = shift if @_;
34 2591 100 100     12164 *$self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
35             };
36             }
37              
38             sub new {
39 94   33 94 1 264 my $class = shift || (caller)[0];
40 94   33     365 my $self = bless gensym, ref($class) || $class;
41 94 50       2938 tie *$self, $self if 5.005 <= $];
42 94         254 return $self;
43             }
44              
45             sub DESTROY {
46 94 50   94   25144 return if in_global_destruction;
47 94         1455 local ($., $@, $!, $^E, $?);
48 94         169 my $self = shift;
49 94         267 $self->close;
50             }
51              
52             sub close {
53 147     147 0 374 my $self = shift;
54 147   100     317 my $fh = $self->_fh // return TRUE;
55 94         401 $self->_POPPED($fh);
56 94         244 $self->_fh(undef);
57 94         451 return $fh->close;
58             }
59             sub eof {
60 0     0 0 0 my $self = shift;
61 0 0       0 return FALSE if @{$self->_buffer_in};
  0         0  
62 0   0     0 my $fh = $self->_fh // return TRUE;
63 0         0 local *$self->{_error} = *$self->{_error};
64 0   0     0 my $char = $self->getc || return TRUE;
65 0         0 $self->ungetc($char);
66             }
67 322     322 0 24960 sub read { shift->sysread(@_) }
68             sub print {
69 86     86 0 215 my $self = shift;
70 86         174 for my $buf (@_) {
71 86 50       263 return FALSE if !$self->write($buf, length($buf));
72             }
73 86         347 return TRUE;
74             }
75 0     0 0 0 sub printf { shift->print(sprintf(@_)) }
76 0     0 0 0 sub say { shift->print(@_, "\n") }
77 0 0 0 0 0 0 sub getc { my $c; (shift->read($c, 1) // 0) == 1 ? $c : undef }
  0         0  
78             sub sysread {
79 322     322 0 426 my $self = shift;
80 322         115040 my ($out, $len, $offset) = @_;
81 322 50       12473 $out = \$_[0] if !is_scalarref($out);
82 322   50     1369 $offset //= 0;
83              
84 322 100       744 $self->_mode('r') if !$self->_mode;
85              
86 322 50       656 my $fh = $self->_fh or return 0;
87 322 50 66     866 return 0 if defined $len && $len == 0;
88              
89 322         699 my $append = $self->_append_output;
90 322 100       641 if (!$append) {
    100          
91 256 50       348 if (!$offset) {
92 256         332 $$out = '';
93             }
94             else {
95 0 0       0 if (length($$out) < $offset) {
96 0         0 $$out .= "\0" x ($offset - length($$out));
97             }
98             else {
99 0         0 substr($$out, $offset) = '';
100             }
101             }
102             }
103             elsif (!defined $$out) {
104 11         23 $$out = '';
105             }
106              
107 322   100     734 $len ||= 0;
108              
109 322         629 my $buffer = $self->_buffer_in;
110 322         705 my $buffer_len = $self->_buffer_in_length;
111              
112 322 50 66     785 if (!$len && !$offset) {
113 66 50       167 if (@$buffer) {
114 0         0 my $blen = length($buffer->[0]);
115 0 0       0 if ($append) {
116 0         0 $$out .= shift @$buffer;
117             }
118             else {
119 0         0 $$out = shift @$buffer;
120             }
121 0         0 return $blen;
122             }
123             else {
124 66 100       297 my $fill = $self->_FILL($fh) or return 0;
125 55 50       210 if ($append) {
126 55         27217 $$out .= $fill;
127             }
128             else {
129 0         0 $$out = $fill;
130             }
131 55         653 return length($fill);
132             }
133             }
134              
135 256         448 while ($buffer_len < $len) {
136 171         429 my $fill = $self->_FILL($fh);
137 171 100       389 last if empty $fill;
138 63         271 $self->_buffer_in_add($fill);
139 63         472 $buffer_len += length($fill);
140             }
141              
142 256         324 my $read_len = 0;
143 256   100     707 while ($read_len < $len && @$buffer) {
144 193         286 my $wanted = $len - $read_len;
145 193         279 my $read = shift @$buffer;
146 193 100       310 if ($wanted < length($read)) {
147 133         348 $$out .= substr($read, 0, $wanted, '');
148 133         339 unshift @$buffer, $read;
149 133         315 $read_len += $wanted;
150             }
151             else {
152 60         124 $$out .= $read;
153 60         186 $read_len += length($read);
154             }
155             }
156              
157 256         663 return $read_len;
158             }
159             sub syswrite {
160 116     116 0 214 my ($self, $buf, $len, $offset) = @_;
161 116   33     229 $len //= length($buf);
162 116   50     416 $offset //= 0;
163              
164 116 100       267 $self->_mode('w') if !$self->_mode;
165              
166 116         336 return $self->_WRITE(substr($buf, $offset, $len), $self->_fh);
167             }
168              
169             sub autoflush {
170 0     0 0 0 my $self = shift;
171 0   0     0 my $fh = $self->_fh // return FALSE;
172 0         0 return $fh->autoflush(@_);
173             }
174              
175             sub opened {
176 0     0 1 0 my $self = shift;
177 0   0     0 my $fh = $self->_fh // return FALSE;
178 0         0 return TRUE;
179             }
180             sub getline {
181 11     11 1 29 my $self = shift;
182              
183 11 50 0     49 if (!defined $/) { # SLURP
    0 0        
    0          
184 11         52 local *$self->{_append_output} = 1;
185 11         20 my $data;
186 11         165 1 while 0 < $self->read($data);
187 11         23983 return $data;
188             }
189 0         0 elsif (is_scalarref($/) && ${$/} =~ /^\d+$/ && 0 < ${$/}) {
  0         0  
190             # RECORD MODE
191 0         0 goto &_not_implemented;
192             }
193             elsif (length $/ == 0) {
194             # PARAGRAPH MODE
195 0         0 goto &_not_implemented;
196             }
197             else {
198             # LINE MODE
199 0         0 goto &_not_implemented;
200             }
201             }
202             sub getlines {
203 0     0 1 0 my $self = shift;
204 0 0       0 wantarray or _croak 'Must call getlines in list context';
205 0         0 my @lines;
206 0         0 while (defined (my $line = $self->getline)) {
207 0         0 push @lines, $line;
208             }
209 0         0 return @lines;
210             }
211             sub ungetc {
212 0     0 1 0 my ($self, $ord) = @_;
213 0         0 unshift @{$self->_buffer_in}, chr($ord);
  0         0  
214 0         0 return;
215             }
216             sub write {
217 116     116 1 4368 my ($self, $buf, $len, $offset) = @_;
218 116         323 return $self->syswrite($buf, $len, $offset) == $len;
219             }
220             sub error {
221 6     6 1 882 my $self = shift;
222 6         73 return !!$self->_error;
223             }
224             sub clearerr {
225 0     0 1 0 my $self = shift;
226 0   0     0 my $fh = $self->_fh // return -1;
227 0         0 $self->_error(undef);
228 0         0 return;
229             }
230             sub sync {
231 0     0 1 0 my $self = shift;
232 0   0     0 my $fh = $self->_fh // return undef;
233 0         0 return $fh->sync;
234             }
235             sub flush {
236 0     0 1 0 my $self = shift;
237 0   0     0 my $fh = $self->_fh // return undef;
238 0         0 $self->_FLUSH($fh);
239 0         0 return $fh->flush;
240             }
241             sub printflush {
242 0     0 1 0 my $self = shift;
243 0         0 my $orig = $self->autoflush;
244 0         0 my $r = $self->print(@_);
245 0         0 $self->autoflush($orig);
246 0         0 return $r;
247             }
248             sub blocking {
249 0     0 1 0 my $self = shift;
250 0   0     0 my $fh = $self->_fh // return TRUE;
251 0         0 return $fh->blocking(@_);
252             }
253              
254 0     0 0 0 sub format_write { goto &_not_implemented }
255 0     0 1 0 sub new_from_fd { goto &_not_implemented }
256 0     0 0 0 sub fcntl { goto &_not_implemented }
257 0     0 0 0 sub fileno { goto &_not_implemented }
258 0     0 0 0 sub ioctl { goto &_not_implemented }
259 0     0 0 0 sub stat { goto &_not_implemented }
260 0     0 0 0 sub truncate { goto &_not_implemented }
261 0     0 0 0 sub format_page_number { goto &_not_implemented }
262 0     0 0 0 sub format_lines_per_page { goto &_not_implemented }
263 0     0 0 0 sub format_lines_left { goto &_not_implemented }
264 0     0 0 0 sub format_name { goto &_not_implemented }
265 0     0 0 0 sub format_top_name { goto &_not_implemented }
266 0     0 0 0 sub input_line_number { goto &_not_implemented }
267 0     0 1 0 sub fdopen { goto &_not_implemented }
268 0     0 1 0 sub untaint { goto &_not_implemented }
269              
270             ##############################################################################
271              
272 63     63   91 sub _buffer_in_add { push @{shift->_buffer_in}, @_ }
  63         105  
273 322     322   413 sub _buffer_in_length { sum0 map { length($_) } @{shift->_buffer_in} }
  130         379  
  322         505  
274              
275 63     63   103 sub _buffer_out_add { push @{shift->_buffer_out}, @_ }
  63         147  
276 0     0   0 sub _buffer_out_length { sum0 map { length($_) } @{shift->_buffer_out} }
  0         0  
  0         0  
277              
278 0     0   0 sub _not_implemented { _croak 'Operation not supported' }
279              
280             ##############################################################################
281              
282             sub TIEHANDLE {
283 94 50   94   521 return $_[0] if is_blessed_ref($_[0]);
284 0         0 die 'wat';
285             }
286              
287             sub UNTIE {
288 0     0   0 my $self = shift;
289             }
290              
291             sub READLINE {
292 11 50   11   201 goto &getlines if wantarray;
293 11         122 goto &getline;
294             }
295              
296 35     35 0 24400 sub binmode { 1 }
297              
298             {
299 10     10   104 no warnings 'once';
  10         20  
  10         2198  
300              
301             *READ = \&read;
302             # *READLINE = \&getline;
303             *GETC = \&getc;
304             *FILENO = \&fileno;
305             *PRINT = \&print;
306             *PRINTF = \&printf;
307             *WRITE = \&syswrite;
308             # *SEEK = \&seek;
309             # *TELL = \&tell;
310             *EOF = \&eof;
311             *CLOSE = \&close;
312             *BINMODE = \&binmode;
313             }
314              
315 0     0     sub _FILL { die 'Not implemented' }
316              
317             ##############################################################################
318              
319             if ($ENV{DEBUG_IO}) {
320             my %debug = (level => 0);
321             for my $method (qw{
322             new
323             new_from_fd
324             close
325             eof
326             fcntl
327             fileno
328             format_write
329             getc
330             ioctl
331             read
332             print
333             printf
334             say
335             stat
336             sysread
337             syswrite
338             truncate
339              
340             autoflush
341             format_page_number
342             format_lines_per_page
343             format_lines_left
344             format_name
345             format_top_name
346             input_line_number
347              
348             fdopen
349             opened
350             getline
351             getlines
352             ungetc
353             write
354             error
355             clearerr
356             sync
357             flush
358             printflush
359             blocking
360              
361             untaint
362             }) {
363 10     10   66 no strict 'refs'; ## no critic (ProhibitNoStrict)
  10         26  
  10         611  
364 10     10   59 no warnings 'redefine';
  10         24  
  10         2002  
365             my $orig = *$method{CODE};
366             *$method = sub {
367             local $debug{level} = $debug{level} + 2;
368             my $indented_method = (' ' x $debug{level}) . $method;
369             my $self = shift;
370             print STDERR sprintf('%-20s -> %s (%s)', $indented_method, $self,
371             join(', ', map { defined $_ ? substr($_, 0, 16) : 'undef' } @_)), "\n";
372             my $r = $orig->($self, @_) // 'undef';
373             print STDERR sprintf('%-20s <- %s [%s]', $indented_method, $self, $r), "\n";
374             return $r;
375             };
376             }
377             }
378              
379             1;
380              
381             __END__