line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package File::KDBX::Loader; |
2
|
|
|
|
|
|
|
# ABSTRACT: Load KDBX files |
3
|
|
|
|
|
|
|
|
4
|
5
|
|
|
5
|
|
28
|
use warnings; |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
156
|
|
5
|
5
|
|
|
5
|
|
23
|
use strict; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
115
|
|
6
|
|
|
|
|
|
|
|
7
|
5
|
|
|
5
|
|
21
|
use File::KDBX::Constants qw(:magic :header :version); |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
996
|
|
8
|
5
|
|
|
5
|
|
33
|
use File::KDBX::Error; |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
243
|
|
9
|
5
|
|
|
5
|
|
25
|
use File::KDBX::Util qw(:class :io); |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
491
|
|
10
|
5
|
|
|
5
|
|
32
|
use File::KDBX; |
|
5
|
|
|
|
|
6
|
|
|
5
|
|
|
|
|
137
|
|
11
|
5
|
|
|
5
|
|
1343
|
use IO::Handle; |
|
5
|
|
|
|
|
15541
|
|
|
5
|
|
|
|
|
179
|
|
12
|
5
|
|
|
5
|
|
28
|
use Module::Load (); |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
91
|
|
13
|
5
|
|
|
5
|
|
21
|
use Ref::Util qw(is_ref is_scalarref); |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
199
|
|
14
|
5
|
|
|
5
|
|
26
|
use Scalar::Util qw(looks_like_number openhandle); |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
191
|
|
15
|
5
|
|
|
5
|
|
29
|
use namespace::clean; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
26
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '0.905'; # VERSION |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub new { |
21
|
30
|
|
|
30
|
1
|
57
|
my $class = shift; |
22
|
30
|
|
|
|
|
66
|
my $self = bless {}, $class; |
23
|
30
|
|
|
|
|
78
|
$self->init(@_); |
24
|
|
|
|
|
|
|
} |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub init { |
28
|
60
|
|
|
60
|
1
|
84
|
my $self = shift; |
29
|
60
|
|
|
|
|
151
|
my %args = @_; |
30
|
|
|
|
|
|
|
|
31
|
60
|
|
|
|
|
184
|
@$self{keys %args} = values %args; |
32
|
|
|
|
|
|
|
|
33
|
60
|
|
|
|
|
208
|
return $self; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub _rebless { |
37
|
51
|
|
|
51
|
|
83
|
my $self = shift; |
38
|
51
|
|
66
|
|
|
144
|
my $format = shift // $self->format; |
39
|
|
|
|
|
|
|
|
40
|
51
|
|
|
|
|
111
|
my $sig2 = $self->kdbx->sig2; |
41
|
51
|
|
|
|
|
118
|
my $version = $self->kdbx->version; |
42
|
|
|
|
|
|
|
|
43
|
51
|
|
|
|
|
70
|
my $subclass; |
44
|
|
|
|
|
|
|
|
45
|
51
|
100
|
33
|
|
|
236
|
if (defined $format) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
46
|
21
|
|
|
|
|
35
|
$subclass = $format; |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
elsif (defined $sig2 && $sig2 == KDBX_SIG2_1) { |
49
|
0
|
|
|
|
|
0
|
$subclass = 'KDB'; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
elsif (looks_like_number($version)) { |
52
|
30
|
|
|
|
|
60
|
my $major = $version & KDBX_VERSION_MAJOR_MASK; |
53
|
30
|
|
|
|
|
111
|
my %subclasses = ( |
54
|
|
|
|
|
|
|
KDBX_VERSION_2_0() => 'V3', |
55
|
|
|
|
|
|
|
KDBX_VERSION_3_0() => 'V3', |
56
|
|
|
|
|
|
|
KDBX_VERSION_4_0() => 'V4', |
57
|
|
|
|
|
|
|
); |
58
|
30
|
50
|
|
|
|
107
|
$subclass = $subclasses{$major} |
59
|
|
|
|
|
|
|
or throw sprintf('Unsupported KDBX file version: %x', $version), version => $version; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
else { |
62
|
0
|
|
|
|
|
0
|
throw sprintf('Unknown file version: %s', $version), version => $version; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
51
|
|
|
|
|
219
|
Module::Load::load "File::KDBX::Loader::$subclass"; |
66
|
51
|
|
|
|
|
2501
|
bless $self, "File::KDBX::Loader::$subclass"; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub reset { |
71
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
72
|
0
|
|
|
|
|
0
|
%$self = (); |
73
|
0
|
|
|
|
|
0
|
return $self; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub load { |
78
|
9
|
|
|
9
|
1
|
16
|
my $self = shift; |
79
|
9
|
|
|
|
|
18
|
my $src = shift; |
80
|
9
|
50
|
33
|
|
|
67
|
return $self->load_handle($src, @_) if openhandle($src) || $src eq '-'; |
81
|
9
|
50
|
|
|
|
26
|
return $self->load_string($src, @_) if is_scalarref($src); |
82
|
9
|
50
|
33
|
|
|
59
|
return $self->load_file($src, @_) if !is_ref($src) && defined $src; |
83
|
0
|
|
|
|
|
0
|
throw 'Programmer error: Must pass a stringref, filepath or IO handle to read'; |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub load_string { |
88
|
21
|
|
|
21
|
1
|
33
|
my $self = shift; |
89
|
21
|
50
|
|
|
|
73
|
my $str = shift or throw 'Expected string to load'; |
90
|
21
|
50
|
|
|
|
72
|
my %args = @_ % 2 == 0 ? @_ : (key => shift, @_); |
91
|
|
|
|
|
|
|
|
92
|
21
|
|
|
|
|
35
|
my $key = delete $args{key}; |
93
|
21
|
|
33
|
|
|
77
|
$args{kdbx} //= $self->kdbx; |
94
|
|
|
|
|
|
|
|
95
|
21
|
100
|
|
|
|
53
|
my $ref = is_scalarref($str) ? $str : \$str; |
96
|
|
|
|
|
|
|
|
97
|
21
|
50
|
|
|
|
258
|
open(my $fh, '<', $ref) or throw "Failed to open string buffer: $!"; |
98
|
|
|
|
|
|
|
|
99
|
21
|
50
|
|
|
|
64
|
$self = $self->new if !ref $self; |
100
|
21
|
|
|
|
|
48
|
$self->init(%args, fh => $fh)->_read($fh, $key); |
101
|
12
|
|
|
|
|
57
|
return $args{kdbx}; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub load_file { |
106
|
9
|
|
|
9
|
1
|
13
|
my $self = shift; |
107
|
9
|
|
|
|
|
14
|
my $filepath = shift; |
108
|
9
|
50
|
|
|
|
62
|
my %args = @_ % 2 == 0 ? @_ : (key => shift, @_); |
109
|
|
|
|
|
|
|
|
110
|
9
|
|
|
|
|
23
|
my $key = delete $args{key}; |
111
|
9
|
|
33
|
|
|
41
|
$args{kdbx} //= $self->kdbx; |
112
|
|
|
|
|
|
|
|
113
|
9
|
50
|
|
|
|
579
|
open(my $fh, '<:raw', $filepath) or throw 'Open file failed', filepath => $filepath; |
114
|
|
|
|
|
|
|
|
115
|
9
|
50
|
|
|
|
46
|
$self = $self->new if !ref $self; |
116
|
9
|
|
|
|
|
38
|
$self->init(%args, fh => $fh, filepath => $filepath)->_read($fh, $key); |
117
|
8
|
|
|
|
|
61
|
return $args{kdbx}; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub load_handle { |
122
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
123
|
0
|
|
|
|
|
0
|
my $fh = shift; |
124
|
0
|
0
|
|
|
|
0
|
my %args = @_ % 2 == 0 ? @_ : (key => shift, @_); |
125
|
|
|
|
|
|
|
|
126
|
0
|
0
|
|
|
|
0
|
$fh = *STDIN if $fh eq '-'; |
127
|
|
|
|
|
|
|
|
128
|
0
|
|
|
|
|
0
|
my $key = delete $args{key}; |
129
|
0
|
|
0
|
|
|
0
|
$args{kdbx} //= $self->kdbx; |
130
|
|
|
|
|
|
|
|
131
|
0
|
0
|
|
|
|
0
|
$self = $self->new if !ref $self; |
132
|
0
|
|
|
|
|
0
|
$self->init(%args, fh => $fh)->_read($fh, $key); |
133
|
0
|
|
|
|
|
0
|
return $args{kdbx}; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub kdbx { |
138
|
346
|
|
|
346
|
1
|
440
|
my $self = shift; |
139
|
346
|
50
|
|
|
|
641
|
return File::KDBX->new if !ref $self; |
140
|
346
|
50
|
|
|
|
557
|
$self->{kdbx} = shift if @_; |
141
|
346
|
|
33
|
|
|
1113
|
$self->{kdbx} //= File::KDBX->new; |
142
|
30
|
50
|
|
30
|
1
|
96
|
} |
143
|
21
|
50
|
|
21
|
1
|
92
|
|
144
|
30
|
|
50
|
|
|
127
|
|
145
|
21
|
|
33
|
|
|
314
|
has format => undef, is => 'ro'; |
146
|
|
|
|
|
|
|
has inner_format => 'XML', is => 'ro'; |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub read_magic_numbers { |
150
|
30
|
|
|
30
|
1
|
47
|
my $self = shift; |
151
|
30
|
|
|
|
|
45
|
my $fh = shift; |
152
|
30
|
|
33
|
|
|
71
|
my $kdbx = shift // $self->kdbx; |
153
|
|
|
|
|
|
|
|
154
|
30
|
50
|
|
|
|
64
|
read_all $fh, my $magic, 12 or throw 'Failed to read file signature'; |
155
|
|
|
|
|
|
|
|
156
|
30
|
|
|
|
|
111
|
my ($sig1, $sig2, $version) = unpack('L<3', $magic); |
157
|
|
|
|
|
|
|
|
158
|
30
|
50
|
|
|
|
72
|
if ($kdbx) { |
159
|
30
|
|
|
|
|
92
|
$kdbx->sig1($sig1); |
160
|
30
|
|
|
|
|
84
|
$kdbx->sig2($sig2); |
161
|
30
|
|
|
|
|
77
|
$kdbx->version($version); |
162
|
30
|
50
|
|
|
|
116
|
$self->_rebless if ref $self; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
|
165
|
30
|
50
|
|
|
|
96
|
return wantarray ? ($sig1, $sig2, $version, $magic) : $magic; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
0
|
0
|
|
0
|
|
0
|
sub _fh { $_[0]->{fh} or throw 'IO handle not set' } |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub _read { |
171
|
30
|
|
|
30
|
|
45
|
my $self = shift; |
172
|
30
|
|
|
|
|
42
|
my $fh = shift; |
173
|
30
|
|
|
|
|
36
|
my $key = shift; |
174
|
|
|
|
|
|
|
|
175
|
30
|
|
|
|
|
87
|
my $kdbx = $self->kdbx; |
176
|
30
|
0
|
33
|
|
|
70
|
$key //= $kdbx->key ? $kdbx->key->reload : undef; |
177
|
30
|
|
|
|
|
103
|
$kdbx->reset; |
178
|
|
|
|
|
|
|
|
179
|
30
|
50
|
|
|
|
85
|
read_all $fh, my $buf, 1 or throw 'Failed to read the first byte', type => 'parser'; |
180
|
30
|
|
|
|
|
59
|
my $first = ord($buf); |
181
|
30
|
|
|
|
|
253
|
$fh->ungetc($first); |
182
|
30
|
50
|
|
|
|
69
|
if ($first != KDBX_SIG1_FIRST_BYTE) { |
183
|
|
|
|
|
|
|
# not a KDBX file... try skipping the outer layer |
184
|
0
|
|
|
|
|
0
|
return $self->_read_inner_body($fh); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
30
|
|
|
|
|
76
|
my $magic = $self->read_magic_numbers($fh, $kdbx); |
188
|
30
|
50
|
|
|
|
106
|
$kdbx->sig1 == KDBX_SIG1 or throw 'Invalid file signature', type => 'parser', sig1 => $kdbx->sig1; |
189
|
|
|
|
|
|
|
|
190
|
30
|
50
|
|
|
|
186
|
if (ref($self) =~ /::(?:KDB|V[34])$/) { |
191
|
30
|
50
|
|
|
|
68
|
defined $key or throw 'Must provide a master key', type => 'key.missing'; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
30
|
|
|
|
|
95
|
my $headers = $self->_read_headers($fh); |
195
|
|
|
|
|
|
|
|
196
|
30
|
|
|
|
|
45
|
eval { |
197
|
30
|
|
|
|
|
96
|
$self->_read_body($fh, $key, "$magic$headers"); |
198
|
|
|
|
|
|
|
}; |
199
|
30
|
100
|
|
|
|
132
|
if (my $err = $@) { |
200
|
10
|
|
|
|
|
26
|
throw "Failed to load KDBX file: $err", |
201
|
|
|
|
|
|
|
error => $err, |
202
|
|
|
|
|
|
|
compression_error => $IO::Uncompress::Gunzip::GunzipError, |
203
|
|
|
|
|
|
|
crypt_error => $File::KDBX::IO::Crypt::ERROR, |
204
|
|
|
|
|
|
|
hash_error => $File::KDBX::IO::HashBLock::ERROR, |
205
|
|
|
|
|
|
|
hmac_error => $File::KDBX::IO::HmacBLock::ERROR; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub _read_headers { |
210
|
30
|
|
|
30
|
|
42
|
my $self = shift; |
211
|
30
|
|
|
|
|
40
|
my $fh = shift; |
212
|
|
|
|
|
|
|
|
213
|
30
|
|
|
|
|
66
|
my $headers = $self->kdbx->headers; |
214
|
30
|
|
|
|
|
55
|
my $all_raw = ''; |
215
|
|
|
|
|
|
|
|
216
|
30
|
|
|
|
|
147
|
while (my ($type, $val, $raw) = $self->_read_header($fh)) { |
217
|
247
|
|
|
|
|
383
|
$all_raw .= $raw; |
218
|
247
|
100
|
|
|
|
419
|
last if $type == HEADER_END; |
219
|
217
|
|
|
|
|
604
|
$headers->{$type} = $val; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
30
|
|
|
|
|
70
|
return $all_raw; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
0
|
|
|
0
|
|
0
|
sub _read_body { die "Not implemented" } |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
sub _read_inner_body { |
228
|
21
|
|
|
21
|
|
37
|
my $self = shift; |
229
|
|
|
|
|
|
|
|
230
|
21
|
|
|
|
|
42
|
my $current_pkg = ref $self; |
231
|
21
|
|
|
|
|
111
|
require Scope::Guard; |
232
|
21
|
|
|
21
|
|
181
|
my $guard = Scope::Guard->new(sub { bless $self, $current_pkg }); |
|
21
|
|
|
|
|
266
|
|
233
|
|
|
|
|
|
|
|
234
|
21
|
|
|
|
|
277
|
$self->_rebless($self->inner_format); |
235
|
21
|
|
|
|
|
87
|
$self->_read_inner_body(@_); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
1; |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
__END__ |