line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package File::KDBX::Object; |
2
|
|
|
|
|
|
|
# ABSTRACT: A KDBX database object |
3
|
|
|
|
|
|
|
|
4
|
11
|
|
|
11
|
|
6710
|
use warnings; |
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
360
|
|
5
|
11
|
|
|
11
|
|
53
|
use strict; |
|
11
|
|
|
|
|
25
|
|
|
11
|
|
|
|
|
204
|
|
6
|
|
|
|
|
|
|
|
7
|
11
|
|
|
11
|
|
42
|
use Devel::GlobalDestruction; |
|
11
|
|
|
|
|
23
|
|
|
11
|
|
|
|
|
59
|
|
8
|
11
|
|
|
11
|
|
619
|
use File::KDBX::Constants qw(:bool); |
|
11
|
|
|
|
|
20
|
|
|
11
|
|
|
|
|
992
|
|
9
|
11
|
|
|
11
|
|
63
|
use File::KDBX::Error; |
|
11
|
|
|
|
|
19
|
|
|
11
|
|
|
|
|
406
|
|
10
|
11
|
|
|
11
|
|
51
|
use File::KDBX::Util qw(:uuid); |
|
11
|
|
|
|
|
15
|
|
|
11
|
|
|
|
|
940
|
|
11
|
11
|
|
|
11
|
|
57
|
use Hash::Util::FieldHash qw(fieldhashes); |
|
11
|
|
|
|
|
25
|
|
|
11
|
|
|
|
|
494
|
|
12
|
11
|
|
|
11
|
|
55
|
use List::Util qw(any first); |
|
11
|
|
|
|
|
23
|
|
|
11
|
|
|
|
|
589
|
|
13
|
11
|
|
|
11
|
|
59
|
use Ref::Util qw(is_arrayref is_plain_arrayref is_plain_hashref is_ref); |
|
11
|
|
|
|
|
18
|
|
|
11
|
|
|
|
|
535
|
|
14
|
11
|
|
|
11
|
|
56
|
use Scalar::Util qw(blessed weaken); |
|
11
|
|
|
|
|
18
|
|
|
11
|
|
|
|
|
479
|
|
15
|
11
|
|
|
11
|
|
56
|
use namespace::clean; |
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
56
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '0.906'; # VERSION |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
fieldhashes \my (%KDBX, %PARENT, %TXNS, %REFS, %SIGNALS); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub new { |
23
|
241
|
|
|
241
|
1
|
115842
|
my $class = shift; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# copy constructor |
26
|
241
|
50
|
100
|
|
|
698
|
return $_[0]->clone if @_ == 1 && blessed $_[0] && $_[0]->isa($class); |
|
|
|
66
|
|
|
|
|
27
|
|
|
|
|
|
|
|
28
|
241
|
|
|
|
|
331
|
my $data; |
29
|
241
|
100
|
|
|
|
669
|
$data = shift if is_plain_hashref($_[0]); |
30
|
|
|
|
|
|
|
|
31
|
241
|
|
|
|
|
307
|
my $kdbx; |
32
|
241
|
100
|
|
|
|
524
|
$kdbx = shift if @_ % 2 == 1; |
33
|
|
|
|
|
|
|
|
34
|
241
|
|
|
|
|
658
|
my %args = @_; |
35
|
241
|
100
|
33
|
|
|
772
|
$args{kdbx} //= $kdbx if defined $kdbx; |
36
|
|
|
|
|
|
|
|
37
|
241
|
|
100
|
|
|
756
|
my $self = bless $data // {}, $class; |
38
|
241
|
|
|
|
|
975
|
$self->init(%args); |
39
|
241
|
100
|
|
|
|
836
|
$self->_set_nonlazy_attributes if !$data; |
40
|
241
|
|
|
|
|
6112
|
return $self; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
0
|
|
|
0
|
|
0
|
sub _set_nonlazy_attributes { die 'Not implemented' } |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub init { |
47
|
136
|
|
|
136
|
1
|
181
|
my $self = shift; |
48
|
136
|
|
|
|
|
316
|
my %args = @_; |
49
|
|
|
|
|
|
|
|
50
|
136
|
|
|
|
|
406
|
while (my ($key, $val) = each %args) { |
51
|
343
|
50
|
|
|
|
1268
|
if (my $method = $self->can($key)) { |
52
|
343
|
|
|
|
|
704
|
$self->$method($val); |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
136
|
|
|
|
|
347
|
return $self; |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub wrap { |
61
|
813
|
|
|
813
|
1
|
1116
|
my $class = shift; |
62
|
813
|
|
|
|
|
946
|
my $object = shift; |
63
|
813
|
100
|
66
|
|
|
4875
|
return $object if blessed $object && $object->isa($class); |
64
|
115
|
100
|
|
|
|
305
|
return $class->new(@_, @$object) if is_arrayref($object); |
65
|
72
|
|
|
|
|
204
|
return $class->new($object, @_); |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
|
69
|
0
|
|
|
0
|
1
|
0
|
sub label { die 'Not implemented' } |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
my %CLONE = (entries => 1, groups => 1, history => 1); |
73
|
|
|
|
|
|
|
sub clone { |
74
|
31
|
|
|
31
|
1
|
85
|
my $self = shift; |
75
|
31
|
|
|
|
|
83
|
my %args = @_; |
76
|
|
|
|
|
|
|
|
77
|
31
|
|
100
|
|
|
153
|
local $CLONE{new_uuid} = $args{new_uuid} // $args{parent} // 0; |
|
|
|
100
|
|
|
|
|
78
|
31
|
|
100
|
|
|
103
|
local $CLONE{entries} = $args{entries} // 1; |
79
|
31
|
|
100
|
|
|
79
|
local $CLONE{groups} = $args{groups} // 1; |
80
|
31
|
|
100
|
|
|
121
|
local $CLONE{history} = $args{history} // 1; |
81
|
31
|
|
50
|
|
|
78
|
local $CLONE{reference_password} = $args{reference_password} // 0; |
82
|
31
|
|
100
|
|
|
73
|
local $CLONE{reference_username} = $args{reference_username} // 0; |
83
|
|
|
|
|
|
|
|
84
|
31
|
|
|
|
|
122
|
require Storable; |
85
|
31
|
|
|
|
|
409
|
my $copy = Storable::dclone($self); |
86
|
|
|
|
|
|
|
|
87
|
31
|
100
|
66
|
|
|
90
|
if ($args{relabel} and my $label = $self->label) { |
88
|
3
|
|
|
|
|
13
|
$copy->label("$label - Copy"); |
89
|
|
|
|
|
|
|
} |
90
|
31
|
100
|
66
|
|
|
71
|
if ($args{parent} and my $parent = $self->group) { |
91
|
3
|
|
|
|
|
11
|
$parent->add_object($copy); |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
31
|
|
|
|
|
333
|
return $copy; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub STORABLE_freeze { |
98
|
64
|
|
|
64
|
0
|
110
|
my $self = shift; |
99
|
64
|
|
|
|
|
84
|
my $cloning = shift; |
100
|
|
|
|
|
|
|
|
101
|
64
|
|
|
|
|
548
|
my $copy = {%$self}; |
102
|
64
|
100
|
|
|
|
184
|
delete $copy->{entries} if !$CLONE{entries}; |
103
|
64
|
100
|
|
|
|
108
|
delete $copy->{groups} if !$CLONE{groups}; |
104
|
64
|
100
|
|
|
|
90
|
delete $copy->{history} if !$CLONE{history}; |
105
|
|
|
|
|
|
|
|
106
|
64
|
50
|
|
|
|
4261
|
return ($cloning ? Hash::Util::FieldHash::id($self) : ''), $copy; |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub STORABLE_thaw { |
110
|
64
|
|
|
64
|
0
|
143
|
my $self = shift; |
111
|
64
|
|
|
|
|
71
|
my $cloning = shift; |
112
|
64
|
|
|
|
|
75
|
my $addr = shift; |
113
|
64
|
|
|
|
|
68
|
my $copy = shift; |
114
|
|
|
|
|
|
|
|
115
|
64
|
|
|
|
|
567
|
@$self{keys %$copy} = values %$copy; |
116
|
|
|
|
|
|
|
|
117
|
64
|
50
|
|
|
|
163
|
if ($cloning) { |
118
|
64
|
|
|
|
|
131
|
my $kdbx = $KDBX{$addr}; |
119
|
64
|
100
|
|
|
|
166
|
$self->kdbx($kdbx) if $kdbx; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
64
|
50
|
|
|
|
139
|
if (defined $self->{uuid}) { |
123
|
64
|
100
|
66
|
|
|
236
|
if (($CLONE{reference_password} || $CLONE{reference_username}) && $self->can('strings')) { |
|
|
|
66
|
|
|
|
|
124
|
2
|
|
|
|
|
9
|
my $uuid = format_uuid($self->{uuid}); |
125
|
2
|
|
|
|
|
3
|
my $clone_obj = do { |
126
|
2
|
|
|
|
|
6
|
local $CLONE{new_uuid} = 0; |
127
|
2
|
|
|
|
|
5
|
local $CLONE{entries} = 1; |
128
|
2
|
|
|
|
|
3
|
local $CLONE{groups} = 1; |
129
|
2
|
|
|
|
|
4
|
local $CLONE{history} = 1; |
130
|
2
|
|
|
|
|
5
|
local $CLONE{reference_password} = 0; |
131
|
2
|
|
|
|
|
3
|
local $CLONE{reference_username} = 0; |
132
|
|
|
|
|
|
|
# Clone only the entry's data and manually bless to avoid infinite recursion. |
133
|
2
|
|
|
|
|
110
|
bless Storable::dclone({%$copy}), 'File::KDBX::Entry'; |
134
|
|
|
|
|
|
|
}; |
135
|
2
|
|
|
|
|
8
|
my $txn = $self->begin_work(snapshot => $clone_obj); |
136
|
2
|
50
|
|
|
|
6
|
if ($CLONE{reference_password}) { |
137
|
0
|
|
|
|
|
0
|
$self->password("{REF:P\@I:$uuid}"); |
138
|
|
|
|
|
|
|
} |
139
|
2
|
50
|
|
|
|
6
|
if ($CLONE{reference_username}) { |
140
|
2
|
|
|
|
|
9
|
$self->username("{REF:U\@I:$uuid}"); |
141
|
|
|
|
|
|
|
} |
142
|
2
|
|
|
|
|
6
|
$txn->commit; |
143
|
|
|
|
|
|
|
} |
144
|
64
|
100
|
|
|
|
145
|
$self->uuid(generate_uuid) if $CLONE{new_uuid}; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Dualvars aren't cloned as dualvars, so dualify the icon. |
148
|
64
|
50
|
|
|
|
215
|
$self->icon_id($self->{icon_id}) if defined $self->{icon_id}; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
sub kdbx { |
153
|
1066
|
|
|
1066
|
1
|
5207
|
my $self = shift; |
154
|
1066
|
50
|
|
|
|
2209
|
$self = $self->new if !ref $self; |
155
|
1066
|
100
|
|
|
|
1753
|
if (@_) { |
156
|
315
|
50
|
|
|
|
559
|
if (my $kdbx = shift) { |
157
|
315
|
|
|
|
|
1654
|
$KDBX{$self} = $kdbx; |
158
|
315
|
|
|
|
|
904
|
weaken $KDBX{$self}; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
else { |
161
|
0
|
|
|
|
|
0
|
delete $KDBX{$self}; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
} |
164
|
1066
|
100
|
|
|
|
4103
|
$KDBX{$self} or throw 'Object is disconnected', object => $self; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub is_connected { |
169
|
85
|
|
|
85
|
1
|
148
|
my $self = shift; |
170
|
85
|
|
|
|
|
151
|
return !!eval { $self->kdbx }; |
|
85
|
|
|
|
|
321
|
|
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
|
174
|
5
|
|
|
5
|
1
|
47
|
sub id { format_uuid(shift->uuid, @_) } |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub group { |
178
|
157
|
|
|
157
|
1
|
200
|
my $self = shift; |
179
|
|
|
|
|
|
|
|
180
|
157
|
100
|
|
|
|
287
|
if (my $new_group = shift) { |
181
|
1
|
|
|
|
|
5
|
my $old_group = $self->group; |
182
|
1
|
50
|
|
|
|
7
|
return $new_group if Hash::Util::FieldHash::id($old_group) == Hash::Util::FieldHash::id($new_group); |
183
|
|
|
|
|
|
|
# move to a new parent |
184
|
1
|
50
|
|
|
|
5
|
$self->remove(signal => 0) if $old_group; |
185
|
1
|
|
|
|
|
5
|
$self->location_changed('now'); |
186
|
1
|
|
|
|
|
18
|
$new_group->add_object($self); |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
157
|
|
|
|
|
312
|
my $id = Hash::Util::FieldHash::id($self); |
190
|
157
|
100
|
|
|
|
340
|
if (my $group = $PARENT{$self}) { |
191
|
31
|
|
|
|
|
67
|
my $method = $self->_parent_container; |
192
|
31
|
50
|
|
31
|
|
83
|
return $group if first { $id == Hash::Util::FieldHash::id($_) } @{$group->$method}; |
|
31
|
|
|
|
|
148
|
|
|
31
|
|
|
|
|
85
|
|
193
|
0
|
|
|
|
|
0
|
delete $PARENT{$self}; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
# always get lineage from root to leaf because the other way requires parent, so it would be recursive |
196
|
126
|
100
|
|
|
|
210
|
my $lineage = $self->kdbx->_trace_lineage($self) or return; |
197
|
1
|
50
|
|
|
|
5
|
my $group = pop @$lineage or return; |
198
|
1
|
|
|
|
|
3
|
$PARENT{$self} = $group; weaken $PARENT{$self}; |
|
1
|
|
|
|
|
4
|
|
199
|
1
|
|
|
|
|
3
|
return $group; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
sub _set_group { |
203
|
112
|
|
|
112
|
|
150
|
my $self = shift; |
204
|
112
|
100
|
|
|
|
189
|
if (my $parent = shift) { |
205
|
49
|
|
|
|
|
138
|
$PARENT{$self} = $parent; |
206
|
49
|
|
|
|
|
134
|
weaken $PARENT{$self}; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
else { |
209
|
63
|
|
|
|
|
165
|
delete $PARENT{$self}; |
210
|
|
|
|
|
|
|
} |
211
|
112
|
|
|
|
|
229
|
return $self; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
### Name of the parent attribute expected to contain the object |
215
|
0
|
|
|
0
|
|
0
|
sub _parent_container { die 'Not implemented' } |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub lineage { |
219
|
5
|
|
|
5
|
1
|
12
|
my $self = shift; |
220
|
5
|
|
|
|
|
9
|
my $base = shift; |
221
|
|
|
|
|
|
|
|
222
|
5
|
50
|
|
|
|
11
|
my $base_addr = $base ? Hash::Util::FieldHash::id($base) : 0; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# try leaf to root |
225
|
5
|
|
|
|
|
11
|
my @path; |
226
|
5
|
|
|
|
|
8
|
my $object = $self; |
227
|
5
|
|
|
|
|
13
|
while ($object = $object->group) { |
228
|
8
|
|
|
|
|
17
|
unshift @path, $object; |
229
|
8
|
50
|
|
|
|
23
|
last if $base_addr == Hash::Util::FieldHash::id($object); |
230
|
|
|
|
|
|
|
} |
231
|
5
|
50
|
33
|
|
|
37
|
return \@path if @path && ($base_addr == Hash::Util::FieldHash::id($path[0]) || $path[0]->is_root); |
|
|
|
33
|
|
|
|
|
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# try root to leaf |
234
|
0
|
|
|
|
|
0
|
return $self->kdbx->_trace_lineage($self, $base); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub remove { |
239
|
56
|
|
|
56
|
1
|
75
|
my $self = shift; |
240
|
56
|
|
|
|
|
133
|
my $parent = $self->group; |
241
|
56
|
100
|
|
|
|
152
|
$parent->remove_object($self, @_) if $parent; |
242
|
56
|
|
|
|
|
192
|
$self->_set_group(undef); |
243
|
56
|
|
|
|
|
161
|
return $self; |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub recycle { |
248
|
1
|
|
|
1
|
1
|
3
|
my $self = shift; |
249
|
1
|
|
|
|
|
3
|
return $self->group($self->kdbx->recycle_bin); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub recycle_or_remove { |
254
|
3
|
|
|
3
|
1
|
24
|
my $self = shift; |
255
|
3
|
|
|
|
|
5
|
my $kdbx = eval { $self->kdbx }; |
|
3
|
|
|
|
|
7
|
|
256
|
3
|
100
|
66
|
|
|
12
|
if ($kdbx && $kdbx->recycle_bin_enabled && !$self->is_recycled) { |
|
|
|
100
|
|
|
|
|
257
|
1
|
|
|
|
|
7
|
$self->recycle; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
else { |
260
|
2
|
|
|
|
|
24
|
$self->remove; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub is_recycled { |
266
|
4
|
|
|
4
|
1
|
40
|
my $self = shift; |
267
|
4
|
50
|
|
|
|
6
|
eval { $self->kdbx } or return FALSE; |
|
4
|
|
|
|
|
9
|
|
268
|
4
|
|
100
|
5
|
|
9
|
return !!($self->group && any { $_->is_recycle_bin } @{$self->lineage}); |
|
5
|
|
|
|
|
14
|
|
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
############################################################################## |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub tag_list { |
275
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
276
|
0
|
|
0
|
|
|
0
|
return grep { $_ ne '' } split(/[,\.:;]|\s+/, trim($self->tags) // ''); |
|
0
|
|
|
|
|
0
|
|
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub custom_icon { |
281
|
7
|
|
|
7
|
1
|
22
|
my $self = shift; |
282
|
7
|
|
|
|
|
16
|
my $kdbx = $self->kdbx; |
283
|
7
|
100
|
|
|
|
17
|
if (@_) { |
284
|
4
|
|
|
|
|
8
|
my $img = shift; |
285
|
4
|
100
|
|
|
|
17
|
my $uuid = defined $img ? $kdbx->add_custom_icon($img, @_) : undef; |
286
|
4
|
100
|
|
|
|
18
|
$self->icon_id(0) if $uuid; |
287
|
4
|
|
|
|
|
14
|
$self->custom_icon_uuid($uuid); |
288
|
4
|
|
|
|
|
13
|
return $img; |
289
|
|
|
|
|
|
|
} |
290
|
3
|
|
|
|
|
9
|
return $kdbx->custom_icon_data($self->custom_icon_uuid); |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
sub custom_data { |
295
|
526
|
|
|
526
|
1
|
857
|
my $self = shift; |
296
|
526
|
50
|
66
|
|
|
1083
|
$self->{custom_data} = shift if @_ == 1 && is_plain_hashref($_[0]); |
297
|
526
|
100
|
100
|
|
|
2570
|
return $self->{custom_data} //= {} if !@_; |
298
|
|
|
|
|
|
|
|
299
|
19
|
100
|
|
|
|
78
|
my %args = @_ == 2 ? (key => shift, value => shift) |
|
|
100
|
|
|
|
|
|
300
|
|
|
|
|
|
|
: @_ % 2 == 1 ? (key => shift, @_) : @_; |
301
|
|
|
|
|
|
|
|
302
|
19
|
50
|
66
|
|
|
48
|
if (!$args{key} && !$args{value}) { |
303
|
2
|
|
|
|
|
6
|
my %standard = (key => 1, value => 1, last_modification_time => 1); |
304
|
2
|
|
|
|
|
5
|
my @other_keys = grep { !$standard{$_} } keys %args; |
|
4
|
|
|
|
|
11
|
|
305
|
2
|
50
|
|
|
|
7
|
if (@other_keys == 1) { |
306
|
2
|
|
|
|
|
5
|
my $key = $args{key} = $other_keys[0]; |
307
|
2
|
|
|
|
|
5
|
$args{value} = delete $args{$key}; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
19
|
50
|
|
|
|
39
|
my $key = $args{key} or throw 'Must provide a custom_data key to access'; |
312
|
|
|
|
|
|
|
|
313
|
19
|
50
|
|
|
|
53
|
return $self->{custom_data}{$key} = $args{value} if is_plain_hashref($args{value}); |
314
|
|
|
|
|
|
|
|
315
|
19
|
|
|
|
|
61
|
while (my ($field, $value) = each %args) { |
316
|
34
|
|
|
|
|
110
|
$self->{custom_data}{$key}{$field} = $value; |
317
|
|
|
|
|
|
|
} |
318
|
19
|
|
|
|
|
51
|
return $self->{custom_data}{$key}; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
sub custom_data_value { |
323
|
6
|
|
|
6
|
1
|
53
|
my $self = shift; |
324
|
6
|
|
50
|
|
|
16
|
my $data = $self->custom_data(@_) // return undef; |
325
|
6
|
|
|
|
|
33
|
return $data->{value}; |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
############################################################################## |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub begin_work { |
332
|
35
|
|
|
35
|
1
|
1644
|
my $self = shift; |
333
|
|
|
|
|
|
|
|
334
|
35
|
100
|
|
|
|
77
|
if (defined wantarray) { |
335
|
16
|
|
|
|
|
874
|
require File::KDBX::Transaction; |
336
|
16
|
|
|
|
|
59
|
return File::KDBX::Transaction->new($self, @_); |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
19
|
|
|
|
|
33
|
my %args = @_; |
340
|
19
|
|
66
|
|
|
55
|
my $orig = $args{snapshot} // do { |
341
|
|
|
|
|
|
|
my $c = $self->clone( |
342
|
|
|
|
|
|
|
entries => $args{entries} // 0, |
343
|
|
|
|
|
|
|
groups => $args{groups} // 0, |
344
|
17
|
|
100
|
|
|
125
|
history => $args{history} // 0, |
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
345
|
|
|
|
|
|
|
); |
346
|
17
|
100
|
|
|
|
50
|
$c->{entries} = $self->{entries} if !$args{entries}; |
347
|
17
|
50
|
|
|
|
60
|
$c->{groups} = $self->{groups} if !$args{groups}; |
348
|
17
|
50
|
|
|
|
44
|
$c->{history} = $self->{history} if !$args{history}; |
349
|
17
|
|
|
|
|
43
|
$c; |
350
|
|
|
|
|
|
|
}; |
351
|
|
|
|
|
|
|
|
352
|
19
|
|
|
|
|
46
|
my $id = Hash::Util::FieldHash::id($orig); |
353
|
19
|
|
|
|
|
50
|
_save_references($id, $self, $orig); |
354
|
|
|
|
|
|
|
|
355
|
19
|
|
|
|
|
59
|
$self->_signal_begin_work; |
356
|
|
|
|
|
|
|
|
357
|
19
|
|
|
|
|
27
|
push @{$self->_txns}, $orig; |
|
19
|
|
|
|
|
40
|
|
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
sub commit { |
362
|
14
|
|
|
14
|
1
|
20
|
my $self = shift; |
363
|
14
|
50
|
|
|
|
20
|
my $orig = pop @{$self->_txns} or return $self; |
|
14
|
|
|
|
|
24
|
|
364
|
14
|
|
|
|
|
52
|
$self->_commit($orig); |
365
|
14
|
|
|
|
|
184
|
my $signals = $self->_signal_commit; |
366
|
14
|
100
|
|
|
|
31
|
$self->_signal_send($signals) if !$self->_in_txn; |
367
|
14
|
|
|
|
|
87
|
return $self; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sub rollback { |
372
|
5
|
|
|
5
|
1
|
9
|
my $self = shift; |
373
|
|
|
|
|
|
|
|
374
|
5
|
50
|
|
|
|
5
|
my $orig = pop @{$self->_txns} or return $self; |
|
5
|
|
|
|
|
10
|
|
375
|
|
|
|
|
|
|
|
376
|
5
|
|
|
|
|
13
|
my $id = Hash::Util::FieldHash::id($orig); |
377
|
5
|
|
|
|
|
11
|
_restore_references($id, $orig); |
378
|
|
|
|
|
|
|
|
379
|
5
|
|
|
|
|
17
|
$self->_signal_rollback; |
380
|
|
|
|
|
|
|
|
381
|
5
|
|
|
|
|
44
|
return $self; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# Get whether or not there is at least one pending transaction. |
385
|
83
|
|
|
83
|
|
101
|
sub _in_txn { scalar @{$_[0]->_txns} } |
|
83
|
|
|
|
|
164
|
|
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# Get an array ref of pending transactions. |
388
|
153
|
|
100
|
153
|
|
913
|
sub _txns { $TXNS{$_[0]} //= [] } |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
# The _commit hook notifies subclasses that a commit has occurred. |
391
|
0
|
|
|
0
|
|
0
|
sub _commit { die 'Not implemented' } |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Get a reference to an object that represents an object's committed state. If there is no pending |
394
|
|
|
|
|
|
|
# transaction, this is just $self. If there is a transaction, this is the snapshot taken immediately before |
395
|
|
|
|
|
|
|
# the transaction began. This method is private because it provides direct access to the actual snapshot. It |
396
|
|
|
|
|
|
|
# is important that the snapshot not be changed or a rollback would roll back to an altered state. |
397
|
|
|
|
|
|
|
# This is used by File::KDBX::Dumper::XML so as to not dump uncommitted changes. |
398
|
|
|
|
|
|
|
sub _committed { |
399
|
32
|
|
|
32
|
|
59
|
my $self = shift; |
400
|
32
|
|
|
|
|
51
|
my ($orig) = @{$self->_txns}; |
|
32
|
|
|
|
|
89
|
|
401
|
32
|
|
66
|
|
|
192
|
return $orig // $self; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# In addition to cloning an object when beginning work, we also keep track its hashrefs and arrayrefs |
405
|
|
|
|
|
|
|
# internally so that we can restore to the very same structures in the case of a rollback. |
406
|
|
|
|
|
|
|
sub _save_references { |
407
|
1406
|
|
|
1406
|
|
1552
|
my $id = shift; |
408
|
1406
|
|
|
|
|
1463
|
my $self = shift; |
409
|
1406
|
|
|
|
|
1392
|
my $orig = shift; |
410
|
|
|
|
|
|
|
|
411
|
1406
|
100
|
100
|
|
|
4248
|
if (is_plain_arrayref($orig)) { |
|
|
100
|
66
|
|
|
|
|
412
|
67
|
|
|
|
|
135
|
for (my $i = 0; $i < @$orig; ++$i) { |
413
|
20
|
|
|
|
|
35
|
_save_references($id, $self->[$i], $orig->[$i]); |
414
|
|
|
|
|
|
|
} |
415
|
67
|
|
|
|
|
199
|
$REFS{$id}{Hash::Util::FieldHash::id($orig)} = $self; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
elsif (is_plain_hashref($orig) || (blessed $orig && $orig->isa(__PACKAGE__))) { |
418
|
349
|
|
|
|
|
692
|
for my $key (keys %$orig) { |
419
|
1367
|
|
|
|
|
1951
|
_save_references($id, $self->{$key}, $orig->{$key}); |
420
|
|
|
|
|
|
|
} |
421
|
349
|
|
|
|
|
1095
|
$REFS{$id}{Hash::Util::FieldHash::id($orig)} = $self; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
# During a rollback, copy data from the snapshot back into the original internal structures. |
426
|
|
|
|
|
|
|
sub _restore_references { |
427
|
377
|
|
|
377
|
|
432
|
my $id = shift; |
428
|
377
|
|
100
|
|
|
583
|
my $orig = shift // return; |
429
|
329
|
|
50
|
|
|
1641
|
my $self = delete $REFS{$id}{Hash::Util::FieldHash::id($orig) // ''} // return $orig; |
|
|
|
100
|
|
|
|
|
430
|
|
|
|
|
|
|
|
431
|
108
|
100
|
33
|
|
|
268
|
if (is_plain_arrayref($orig)) { |
|
|
50
|
66
|
|
|
|
|
432
|
19
|
|
|
|
|
34
|
@$self = map { _restore_references($id, $_) } @$orig; |
|
6
|
|
|
|
|
10
|
|
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
elsif (is_plain_hashref($orig) || (blessed $orig && $orig->isa(__PACKAGE__))) { |
435
|
89
|
|
|
|
|
175
|
for my $key (keys %$orig) { |
436
|
|
|
|
|
|
|
# next if is_ref($orig->{$key}) && |
437
|
|
|
|
|
|
|
# (Hash::Util::FieldHash::id($self->{$key}) // 0) == Hash::Util::FieldHash::id($orig->{$key}); |
438
|
366
|
|
|
|
|
486
|
$self->{$key} = _restore_references($id, $orig->{$key}); |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
108
|
|
|
|
|
201
|
return $self; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
############################################################################## |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
sub _signal { |
448
|
69
|
|
|
69
|
|
95
|
my $self = shift; |
449
|
69
|
|
|
|
|
90
|
my $type = shift; |
450
|
|
|
|
|
|
|
|
451
|
69
|
50
|
|
|
|
157
|
if ($self->_in_txn) { |
452
|
0
|
|
|
|
|
0
|
my $stack = $self->_signal_stack; |
453
|
0
|
|
|
|
|
0
|
my $queue = $stack->[-1]; |
454
|
0
|
|
|
|
|
0
|
push @$queue, [$type, @_]; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
69
|
|
|
|
|
345
|
$self->_signal_send([[$type, @_]]); |
458
|
|
|
|
|
|
|
|
459
|
69
|
|
|
|
|
311
|
return $self; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
52
|
|
100
|
52
|
|
219
|
sub _signal_stack { $SIGNALS{$_[0]} //= [] } |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
sub _signal_begin_work { |
465
|
19
|
|
|
19
|
|
29
|
my $self = shift; |
466
|
19
|
|
|
|
|
24
|
push @{$self->_signal_stack}, []; |
|
19
|
|
|
|
|
43
|
|
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
sub _signal_commit { |
470
|
14
|
|
|
14
|
|
22
|
my $self = shift; |
471
|
14
|
|
|
|
|
19
|
my $signals = pop @{$self->_signal_stack}; |
|
14
|
|
|
|
|
30
|
|
472
|
14
|
|
100
|
|
|
27
|
my $previous = $self->_signal_stack->[-1] // []; |
473
|
14
|
|
|
|
|
32
|
push @$previous, @$signals; |
474
|
14
|
|
|
|
|
22
|
return $previous; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub _signal_rollback { |
478
|
5
|
|
|
5
|
|
7
|
my $self = shift; |
479
|
5
|
|
|
|
|
6
|
pop @{$self->_signal_stack}; |
|
5
|
|
|
|
|
10
|
|
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
sub _signal_send { |
483
|
81
|
|
|
81
|
|
108
|
my $self = shift; |
484
|
81
|
|
50
|
|
|
154
|
my $signals = shift // []; |
485
|
|
|
|
|
|
|
|
486
|
81
|
50
|
|
|
|
219
|
my $kdbx = $KDBX{$self} or return; |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# de-duplicate, keeping the most recent signal for each type |
489
|
81
|
|
|
|
|
104
|
my %seen; |
490
|
81
|
|
|
|
|
155
|
my @signals = grep { !$seen{$_->[0]}++ } reverse @$signals; |
|
69
|
|
|
|
|
280
|
|
491
|
|
|
|
|
|
|
|
492
|
81
|
|
|
|
|
161
|
for my $sig (reverse @signals) { |
493
|
69
|
|
|
|
|
217
|
$kdbx->_handle_signal($self, @$sig); |
494
|
|
|
|
|
|
|
} |
495
|
|
|
|
|
|
|
} |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
############################################################################## |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
sub _wrap_group { |
500
|
54
|
|
|
54
|
|
76
|
my $self = shift; |
501
|
54
|
|
|
|
|
69
|
my $group = shift; |
502
|
54
|
|
|
|
|
166
|
require File::KDBX::Group; |
503
|
54
|
|
|
|
|
148
|
return File::KDBX::Group->wrap($group, $KDBX{$self}); |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
sub _wrap_entry { |
507
|
60
|
|
|
60
|
|
99
|
my $self = shift; |
508
|
60
|
|
|
|
|
80
|
my $entry = shift; |
509
|
60
|
|
|
|
|
3438
|
require File::KDBX::Entry; |
510
|
60
|
|
|
|
|
287
|
return File::KDBX::Entry->wrap($entry, $KDBX{$self}); |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
0
|
|
|
0
|
0
|
|
sub TO_JSON { +{%{$_[0]}} } |
|
0
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
1; |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
__END__ |