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     3311   4263 use warnings;
  10         21  
  10         271  
5 10     1445   44 use strict;
  10         15  
  10         165  
6              
7 10     10   1386 use Devel::GlobalDestruction;
  10         1813  
  10         54  
8 10     10   586 use File::KDBX::Constants qw(:bool);
  10         18  
  10         2589  
9 10     10   51 use File::KDBX::Util qw(:class :empty);
  10         16  
  10         912  
10 10     10   53 use List::Util qw(sum0);
  10         16  
  10         460  
11 10     10   47 use Ref::Util qw(is_blessed_ref is_ref is_scalarref);
  10         16  
  10         445  
12 10     10   1053 use Symbol qw(gensym);
  10         1979  
  10         465  
13 10     10   57 use namespace::clean;
  10         13  
  10         47  
14              
15             extends 'IO::Handle';
16              
17             our $VERSION = '0.904'; # 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   3574 no strict 'refs'; ## no critic (ProhibitNoStrict)
  10         24  
  10         18605  
31             *$attr = sub {
32 2595     2595   2789 my $self = shift;
33 2595 100       3856 *$self->{$attr} = shift if @_;
34 2595 100 100     10650 *$self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
35             };
36             }
37              
38             sub new {
39 94   33 94 1 263 my $class = shift || (caller)[0];
40 94   33     349 my $self = bless gensym, ref($class) || $class;
41 94 50       2744 tie *$self, $self if 5.005 <= $];
42 94         260 return $self;
43             }
44              
45             sub DESTROY {
46 94 50   94   20771 return if in_global_destruction;
47 94         1306 local ($., $@, $!, $^E, $?);
48 94         188 my $self = shift;
49 94         269 $self->close;
50             }
51              
52             sub close {
53 147     147 0 298 my $self = shift;
54 147   100     292 my $fh = $self->_fh // return TRUE;
55 94         378 $self->_POPPED($fh);
56 94         245 $self->_fh(undef);
57 94         431 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 26720 sub read { shift->sysread(@_) }
68             sub print {
69 86     86 0 200 my $self = shift;
70 86         161 for my $buf (@_) {
71 86 50       271 return FALSE if !$self->write($buf, length($buf));
72             }
73 86         299 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 394 my $self = shift;
80 322         99036 my ($out, $len, $offset) = @_;
81 322 50       6728 $out = \$_[0] if !is_scalarref($out);
82 322   50     1112 $offset //= 0;
83              
84 322 100       666 $self->_mode('r') if !$self->_mode;
85              
86 322 50       534 my $fh = $self->_fh or return 0;
87 322 50 66     799 return 0 if defined $len && $len == 0;
88              
89 322         632 my $append = $self->_append_output;
90 322 100       611 if (!$append) {
    100          
91 256 50       390 if (!$offset) {
92 256         340 $$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         20 $$out = '';
105             }
106              
107 322   100     635 $len ||= 0;
108              
109 322         578 my $buffer = $self->_buffer_in;
110 322         607 my $buffer_len = $self->_buffer_in_length;
111              
112 322 50 66     661 if (!$len && !$offset) {
113 66 50       116 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       190 my $fill = $self->_FILL($fh) or return 0;
125 55 50       121 if ($append) {
126 55         19717 $$out .= $fill;
127             }
128             else {
129 0         0 $$out = $fill;
130             }
131 55         317 return length($fill);
132             }
133             }
134              
135 256         440 while ($buffer_len < $len) {
136 160         412 my $fill = $self->_FILL($fh);
137 160 100       409 last if empty $fill;
138 52         226 $self->_buffer_in_add($fill);
139 52         116 $buffer_len += length($fill);
140             }
141              
142 256         347 my $read_len = 0;
143 256   100     706 while ($read_len < $len && @$buffer) {
144 183         244 my $wanted = $len - $read_len;
145 183         263 my $read = shift @$buffer;
146 183 100       307 if ($wanted < length($read)) {
147 134         330 $$out .= substr($read, 0, $wanted, '');
148 134         321 unshift @$buffer, $read;
149 134         295 $read_len += $wanted;
150             }
151             else {
152 49         97 $$out .= $read;
153 49         154 $read_len += length($read);
154             }
155             }
156              
157 256         661 return $read_len;
158             }
159             sub syswrite {
160 116     116 0 256 my ($self, $buf, $len, $offset) = @_;
161 116   33     229 $len //= length($buf);
162 116   50     415 $offset //= 0;
163              
164 116 100       276 $self->_mode('w') if !$self->_mode;
165              
166 116         351 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 25 my $self = shift;
182              
183 11 50 0     33 if (!defined $/) { # SLURP
    0 0        
    0          
184 11         43 local *$self->{_append_output} = 1;
185 11         17 my $data;
186 11         52 1 while 0 < $self->read($data);
187 11         20219 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 4638 my ($self, $buf, $len, $offset) = @_;
218 116         322 return $self->syswrite($buf, $len, $offset) == $len;
219             }
220             sub error {
221 6     6 1 797 my $self = shift;
222 6         61 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 52     52   74 sub _buffer_in_add { push @{shift->_buffer_in}, @_ }
  52         93  
273 322     322   387 sub _buffer_in_length { sum0 map { length($_) } @{shift->_buffer_in} }
  131         344  
  322         445  
274              
275 78     78   108 sub _buffer_out_add { push @{shift->_buffer_out}, @_ }
  78         190  
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   484 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   143 goto &getlines if wantarray;
293 11         84 goto &getline;
294             }
295              
296 35     35 0 25130 sub binmode { 1 }
297              
298             {
299 10     10   68 no warnings 'once';
  10         17  
  10         1979  
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   61 no strict 'refs'; ## no critic (ProhibitNoStrict)
  10         19  
  10         257  
364 10     10   46 no warnings 'redefine';
  10         17  
  10         1788  
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__