]> Dogcows Code - chaz/p5-File-KDBX/blob - lib/File/KDBX/Entry.pm
Version 0.800
[chaz/p5-File-KDBX] / lib / File / KDBX / Entry.pm
1 package File::KDBX::Entry;
2 # ABSTRACT: A KDBX database entry
3
4 use warnings;
5 use strict;
6
7 use Crypt::Misc 0.029 qw(decode_b64 encode_b32r);
8 use Devel::GlobalDestruction;
9 use Encode qw(encode);
10 use File::KDBX::Constants qw(:history :icon);
11 use File::KDBX::Error;
12 use File::KDBX::Util qw(:assert :class :coercion :erase :function :uri generate_uuid load_optional);
13 use Hash::Util::FieldHash;
14 use List::Util qw(first sum0);
15 use Ref::Util qw(is_coderef is_hashref is_plain_hashref);
16 use Scalar::Util qw(blessed looks_like_number);
17 use Storable qw(dclone);
18 use Time::Piece;
19 use boolean;
20 use namespace::clean;
21
22 extends 'File::KDBX::Object';
23
24 our $VERSION = '0.800'; # VERSION
25
26 my $PLACEHOLDER_MAX_DEPTH = 10;
27 my %PLACEHOLDERS;
28 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
29
30
31 sub uuid {
32 my $self = shift;
33 if (@_ || !defined $self->{uuid}) {
34 my %args = @_ % 2 == 1 ? (uuid => shift, @_) : @_;
35 my $old_uuid = $self->{uuid};
36 my $uuid = $self->{uuid} = delete $args{uuid} // generate_uuid;
37 for my $entry (@{$self->history}) {
38 $entry->{uuid} = $uuid;
39 }
40 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid && $self->is_current;
41 }
42 $self->{uuid};
43 }
44
45 # has uuid => sub { generate_uuid(printable => 1) };
46 has icon_id => ICON_PASSWORD, coerce => \&to_icon_constant;
47 has custom_icon_uuid => undef, coerce => \&to_uuid;
48 has foreground_color => '', coerce => \&to_string;
49 has background_color => '', coerce => \&to_string;
50 has override_url => '', coerce => \&to_string;
51 has tags => '', coerce => \&to_string;
52 has auto_type => {};
53 has previous_parent_group => undef, coerce => \&to_uuid;
54 has quality_check => true, coerce => \&to_bool;
55 has strings => {};
56 has binaries => {};
57 has times => {};
58 # has custom_data => {};
59 # has history => [];
60
61 has last_modification_time => sub { gmtime }, store => 'times', coerce => \&to_time;
62 has creation_time => sub { gmtime }, store => 'times', coerce => \&to_time;
63 has last_access_time => sub { gmtime }, store => 'times', coerce => \&to_time;
64 has expiry_time => sub { gmtime }, store => 'times', coerce => \&to_time;
65 has expires => false, store => 'times', coerce => \&to_bool;
66 has usage_count => 0, store => 'times', coerce => \&to_number;
67 has location_changed => sub { gmtime }, store => 'times', coerce => \&to_time;
68
69 # has 'auto_type.auto_type_enabled' => true, coerce => \&to_bool;
70 has 'auto_type_obfuscation' => 0, path => 'auto_type.data_transfer_obfuscation',
71 coerce => \&to_number;
72 has 'auto_type_default_sequence' => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
73 path => 'auto_type.default_sequence', coerce => \&to_string;
74 has 'auto_type_associations' => [], path => 'auto_type.associations';
75
76 my %ATTRS_STRINGS = (
77 title => 'Title',
78 username => 'UserName',
79 password => 'Password',
80 url => 'URL',
81 notes => 'Notes',
82 );
83 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
84 no strict 'refs'; ## no critic (ProhibitNoStrict)
85 *{$attr} = sub { shift->string_value($string_key, @_) };
86 *{"expand_${attr}"} = sub { shift->expand_string_value($string_key, @_) };
87 }
88
89 my @ATTRS = qw(uuid custom_data history auto_type_enabled);
90 sub _set_nonlazy_attributes {
91 my $self = shift;
92 $self->$_ for @ATTRS, keys %ATTRS_STRINGS, list_attributes(ref $self);
93 }
94
95 sub init {
96 my $self = shift;
97 my %args = @_;
98
99 while (my ($key, $val) = each %args) {
100 if (my $method = $self->can($key)) {
101 $self->$method($val);
102 }
103 else {
104 $self->string($key => $val);
105 }
106 }
107
108 return $self;
109 }
110
111 ##############################################################################
112
113
114 sub string {
115 my $self = shift;
116 my %args = @_ == 2 ? (key => shift, value => shift)
117 : @_ % 2 == 1 ? (key => shift, @_) : @_;
118
119 if (!defined $args{key} && !defined $args{value}) {
120 my %standard = (value => 1, protect => 1);
121 my @other_keys = grep { !$standard{$_} } keys %args;
122 if (@other_keys == 1) {
123 my $key = $args{key} = $other_keys[0];
124 $args{value} = delete $args{$key};
125 }
126 }
127
128 my $key = delete $args{key} or throw 'Must provide a string key to access';
129
130 return $self->{strings}{$key} = $args{value} if is_plain_hashref($args{value});
131
132 while (my ($field, $value) = each %args) {
133 $self->{strings}{$key}{$field} = $value;
134 }
135
136 # Auto-vivify the standard strings.
137 if ($STANDARD_STRINGS{$key}) {
138 return $self->{strings}{$key} //= {value => '', $self->_protect($key) ? (protect => true) : ()};
139 }
140 return $self->{strings}{$key};
141 }
142
143 ### Get whether or not a standard string is configured to be protected
144 sub _protect {
145 my $self = shift;
146 my $key = shift;
147 return false if !$STANDARD_STRINGS{$key};
148 if (my $kdbx = eval { $self->kdbx }) {
149 my $protect = $kdbx->memory_protection($key);
150 return $protect if defined $protect;
151 }
152 return $key eq 'Password';
153 }
154
155
156 sub string_value {
157 my $self = shift;
158 my $string = $self->string(@_) // return undef;
159 return $string->{value};
160 }
161
162
163 sub _expand_placeholder {
164 my $self = shift;
165 my $placeholder = shift;
166 my $arg = shift;
167
168 require File::KDBX;
169
170 my $placeholder_key = $placeholder;
171 if (defined $arg) {
172 $placeholder_key = $File::KDBX::PLACEHOLDERS{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
173 : "${placeholder}:";
174 }
175 return if !defined $File::KDBX::PLACEHOLDERS{$placeholder_key};
176
177 my $local_key = join('/', Hash::Util::FieldHash::id($self), $placeholder_key);
178 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
179 my $handler = $File::KDBX::PLACEHOLDERS{$placeholder_key} or next;
180 memoize recurse_limit($handler, $PLACEHOLDER_MAX_DEPTH, sub {
181 alert "Detected deep recursion while expanding $placeholder placeholder",
182 placeholder => $placeholder;
183 return; # undef
184 });
185 };
186
187 return $handler->($self, $arg, $placeholder);
188 }
189
190 sub _expand_string {
191 my $self = shift;
192 my $str = shift;
193
194 my $expand = memoize $self->can('_expand_placeholder'), $self;
195
196 # placeholders (including field references):
197 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
198
199 # environment variables (alt syntax):
200 my $vars = join('|', map { quotemeta($_) } keys %ENV);
201 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
202
203 return $str;
204 }
205
206 sub expand_string_value {
207 my $self = shift;
208 my $str = $self->string_peek(@_) // return undef;
209 my $cleanup = erase_scoped $str;
210 return $self->_expand_string($str);
211 }
212
213
214 sub other_strings {
215 my $self = shift;
216 my $delim = shift // "\n";
217
218 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
219 return join($delim, @strings);
220 }
221
222
223 sub string_peek {
224 my $self = shift;
225 my $string = $self->string(@_);
226 return defined $string->{value} ? $string->{value} : $self->kdbx->peek($string);
227 }
228
229 ##############################################################################
230
231
232 sub add_auto_type_association {
233 my $self = shift;
234 my $association = shift;
235 push @{$self->auto_type_associations}, $association;
236 }
237
238
239 sub expand_keystroke_sequence {
240 my $self = shift;
241 my $association = shift;
242
243 my $keys;
244 if ($association) {
245 $keys = is_hashref($association) && exists $association->{keystroke_sequence} ?
246 $association->{keystroke_sequence} : defined $association ? $association : '';
247 }
248
249 $keys = $self->auto_type_default_sequence if !$keys;
250 # TODO - Fall back to getting default sequence from parent group, which probably means we shouldn't be
251 # setting a default value in the entry..
252
253 return $self->_expand_string($keys);
254 }
255
256 ##############################################################################
257
258
259 sub binary {
260 my $self = shift;
261 my %args = @_ == 2 ? (key => shift, value => shift)
262 : @_ % 2 == 1 ? (key => shift, @_) : @_;
263
264 if (!defined $args{key} && !defined $args{value}) {
265 my %standard = (value => 1, protect => 1);
266 my @other_keys = grep { !$standard{$_} } keys %args;
267 if (@other_keys == 1) {
268 my $key = $args{key} = $other_keys[0];
269 $args{value} = delete $args{$key};
270 }
271 }
272
273 my $key = delete $args{key} or throw 'Must provide a binary key to access';
274
275 return $self->{binaries}{$key} = $args{value} if is_plain_hashref($args{value});
276
277 assert { !defined $args{value} || !utf8::is_utf8($args{value}) };
278 while (my ($field, $value) = each %args) {
279 $self->{binaries}{$key}{$field} = $value;
280 }
281 return $self->{binaries}{$key};
282 }
283
284
285 sub binary_value {
286 my $self = shift;
287 my $binary = $self->binary(@_) // return undef;
288 return $binary->{value};
289 }
290
291 ##############################################################################
292
293
294 sub hmac_otp {
295 my $self = shift;
296 load_optional('Pass::OTP');
297
298 my %params = ($self->_hotp_params, @_);
299 return if !defined $params{type} || !defined $params{secret};
300
301 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
302 $params{base32} = 1;
303
304 my $otp = eval {Pass::OTP::otp(%params, @_) };
305 if (my $err = $@) {
306 throw 'Unable to generate HOTP', error => $err;
307 }
308
309 $self->_hotp_increment_counter($params{counter});
310
311 return $otp;
312 }
313
314
315 sub time_otp {
316 my $self = shift;
317 load_optional('Pass::OTP');
318
319 my %params = ($self->_totp_params, @_);
320 return if !defined $params{type} || !defined $params{secret};
321
322 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
323 $params{base32} = 1;
324
325 my $otp = eval {Pass::OTP::otp(%params, @_) };
326 if (my $err = $@) {
327 throw 'Unable to generate TOTP', error => $err;
328 }
329
330 return $otp;
331 }
332
333
334 sub hmac_otp_uri { $_[0]->_otp_uri($_[0]->_hotp_params) }
335 sub time_otp_uri { $_[0]->_otp_uri($_[0]->_totp_params) }
336
337 sub _otp_uri {
338 my $self = shift;
339 my %params = @_;
340
341 return if 4 != grep { defined } @params{qw(type secret issuer account)};
342 return if $params{type} !~ /^[ht]otp$/i;
343
344 my $label = delete $params{label};
345 $params{$_} = uri_escape_utf8($params{$_}) for keys %params;
346
347 my $type = lc($params{type});
348 my $issuer = $params{issuer};
349 my $account = $params{account};
350
351 $label //= "$issuer:$account";
352
353 my $secret = $params{secret};
354 $secret = uc(encode_b32r($secret)) if !$params{base32};
355
356 delete $params{algorithm} if defined $params{algorithm} && $params{algorithm} eq 'sha1';
357 delete $params{period} if defined $params{period} && $params{period} == 30;
358 delete $params{digits} if defined $params{digits} && $params{digits} == 6;
359 delete $params{counter} if defined $params{counter} && $params{counter} == 0;
360
361 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
362
363 if (defined $params{encoder}) {
364 $uri .= "&encoder=$params{encoder}";
365 return $uri;
366 }
367 $uri .= '&algorithm=' . uc($params{algorithm}) if defined $params{algorithm};
368 $uri .= "&digits=$params{digits}" if defined $params{digits};
369 $uri .= "&counter=$params{counter}" if defined $params{counter};
370 $uri .= "&period=$params{period}" if defined $params{period};
371
372 return $uri;
373 }
374
375 sub _hotp_params {
376 my $self = shift;
377
378 my %params = (
379 type => 'hotp',
380 issuer => $self->title || 'KDBX',
381 account => $self->username || 'none',
382 digits => 6,
383 counter => $self->string_value('HmacOtp-Counter') // 0,
384 $self->_otp_secret_params('Hmac'),
385 );
386 return %params if $params{secret};
387
388 my %otp_params = $self->_otp_params;
389 return () if !$otp_params{secret} || $otp_params{type} ne 'hotp';
390
391 # $otp_params{counter} = 0
392
393 return (%params, %otp_params);
394 }
395
396 sub _totp_params {
397 my $self = shift;
398
399 my %algorithms = (
400 'HMAC-SHA-1' => 'sha1',
401 'HMAC-SHA-256' => 'sha256',
402 'HMAC-SHA-512' => 'sha512',
403 );
404 my %params = (
405 type => 'totp',
406 issuer => $self->title || 'KDBX',
407 account => $self->username || 'none',
408 digits => $self->string_value('TimeOtp-Length') // 6,
409 algorithm => $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
410 period => $self->string_value('TimeOtp-Period') // 30,
411 $self->_otp_secret_params('Time'),
412 );
413 return %params if $params{secret};
414
415 my %otp_params = $self->_otp_params;
416 return () if !$otp_params{secret} || $otp_params{type} ne 'totp';
417
418 return (%params, %otp_params);
419 }
420
421 # KeePassXC style
422 sub _otp_params {
423 my $self = shift;
424 load_optional('Pass::OTP::URI');
425
426 my $uri = $self->string_value('otp') || '';
427 my %params;
428 %params = Pass::OTP::URI::parse($uri) if $uri =~ m!^otpauth://!;
429 return () if !$params{secret} || !$params{type};
430
431 if (($params{encoder} // '') eq 'steam') {
432 $params{digits} = 5;
433 $params{chars} = '23456789BCDFGHJKMNPQRTVWXY';
434 }
435
436 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
437 my ($issuer, $user) = split(':', $params{label} // ':', 2);
438 $params{issuer} //= uri_unescape_utf8($issuer);
439 $params{account} //= uri_unescape_utf8($user);
440
441 $params{algorithm} = lc($params{algorithm}) if $params{algorithm};
442 $params{counter} = $self->string_value('HmacOtp-Counter') if $params{type} eq 'hotp';
443
444 return %params;
445 }
446
447 sub _otp_secret_params {
448 my $self = shift;
449 my $type = shift // return ();
450
451 my $secret_txt = $self->string_value("${type}Otp-Secret");
452 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
453 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
454 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
455
456 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
457 return () if $count == 0;
458 alert "Found multiple ${type}Otp-Secret strings", count => $count if 1 < $count;
459
460 return (secret => $secret_b32, base32 => 1) if defined $secret_b32;
461 return (secret => decode_b64($secret_b64)) if defined $secret_b64;
462 return (secret => pack('H*', $secret_hex)) if defined $secret_hex;
463 return (secret => encode('UTF-8', $secret_txt));
464 }
465
466 sub _hotp_increment_counter {
467 my $self = shift;
468 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
469
470 looks_like_number($counter) or throw 'HmacOtp-Counter value must be a number', value => $counter;
471 my $next = $counter + 1;
472 $self->string('HmacOtp-Counter', $next);
473 return $next;
474 }
475
476 ##############################################################################
477
478
479 sub size {
480 my $self = shift;
481
482 my $size = 0;
483
484 # tags
485 $size += length(encode('UTF-8', $self->tags // ''));
486
487 # attributes (strings)
488 while (my ($key, $string) = each %{$self->strings}) {
489 next if !defined $string->{value};
490 $size += length(encode('UTF-8', $key)) + length(encode('UTF-8', $string->{value} // ''));
491 }
492
493 # custom data
494 while (my ($key, $item) = each %{$self->custom_data}) {
495 next if !defined $item->{value};
496 $size += length(encode('UTF-8', $key)) + length(encode('UTF-8', $item->{value} // ''));
497 }
498
499 # binaries
500 while (my ($key, $binary) = each %{$self->binaries}) {
501 next if !defined $binary->{value};
502 my $value_len = utf8::is_utf8($binary->{value}) ? length(encode('UTF-8', $binary->{value}))
503 : length($binary->{value});
504 $size += length(encode('UTF-8', $key)) + $value_len;
505 }
506
507 # autotype associations
508 for my $association (@{$self->auto_type->{associations} || []}) {
509 $size += length(encode('UTF-8', $association->{window}))
510 + length(encode('UTF-8', $association->{keystroke_sequence} // ''));
511 }
512
513 return $size;
514 }
515
516 ##############################################################################
517
518 sub history {
519 my $self = shift;
520 my $entries = $self->{history} //= [];
521 if (@$entries && !blessed($entries->[0])) {
522 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
523 }
524 assert { !any { !blessed $_ } @$entries };
525 return $entries;
526 }
527
528
529 sub history_size {
530 my $self = shift;
531 return sum0 map { $_->size } @{$self->history};
532 }
533
534
535 sub prune_history {
536 my $self = shift;
537 my %args = @_;
538
539 my $max_items = $args{max_items} // eval { $self->kdbx->history_max_items } // HISTORY_DEFAULT_MAX_ITEMS;
540 my $max_size = $args{max_size} // eval { $self->kdbx->history_max_size } // HISTORY_DEFAULT_MAX_SIZE;
541 my $max_age = $args{max_age} // HISTORY_DEFAULT_MAX_AGE;
542
543 # history is ordered oldest to newest
544 my $history = $self->history;
545
546 my @removed;
547
548 if (0 <= $max_items && $max_items < @$history) {
549 push @removed, splice @$history, -$max_items;
550 }
551
552 if (0 <= $max_size) {
553 my $current_size = $self->history_size;
554 while ($max_size < $current_size) {
555 push @removed, my $entry = shift @$history;
556 $current_size -= $entry->size;
557 }
558 }
559
560 if (0 <= $max_age) {
561 my $cutoff = gmtime - ($max_age * 86400);
562 for (my $i = @$history - 1; 0 <= $i; --$i) {
563 my $entry = $history->[$i];
564 next if $cutoff <= $entry->last_modification_time;
565 push @removed, splice @$history, $i, 1;
566 }
567 }
568
569 @removed = sort { $a->last_modification_time <=> $b->last_modification_time } @removed;
570 return @removed;
571 }
572
573
574 sub add_historical_entry {
575 my $self = shift;
576 delete $_->{history} for @_;
577 push @{$self->{history} //= []}, map { $self->_wrap_entry($_) } @_;
578 }
579
580
581 sub remove_historical_entry {
582 my $self = shift;
583 my $entry = shift;
584 my $history = $self->history;
585
586 my @removed;
587 for (my $i = @$history - 1; 0 <= $i; --$i) {
588 my $item = $history->[$i];
589 next if Hash::Util::FieldHash::id($entry) != Hash::Util::FieldHash::id($item);
590 push @removed, splice @{$self->{history}}, $i, 1;
591 }
592 return @removed;
593 }
594
595
596 sub current_entry {
597 my $self = shift;
598 my $parent = $self->group;
599
600 if ($parent) {
601 my $id = $self->uuid;
602 my $entry = first { $id eq $_->uuid } @{$parent->entries};
603 return $entry if $entry;
604 }
605
606 return $self;
607 }
608
609
610 sub is_current {
611 my $self = shift;
612 my $current = $self->current_entry;
613 return Hash::Util::FieldHash::id($self) == Hash::Util::FieldHash::id($current);
614 }
615
616
617 sub is_historical { !$_[0]->is_current }
618
619
620 sub remove {
621 my $self = shift;
622 my $current = $self->current_entry;
623 return $self if $current->remove_historical_entry($self);
624 $self->SUPER::remove(@_);
625 }
626
627 ##############################################################################
628
629
630 sub searching_enabled {
631 my $self = shift;
632 my $parent = $self->group;
633 return $parent->effective_enable_searching if $parent;
634 return true;
635 }
636
637 sub auto_type_enabled {
638 my $self = shift;
639 $self->auto_type->{enabled} = to_bool(shift) if @_;
640 $self->auto_type->{enabled} //= true;
641 return false if !$self->auto_type->{enabled};
642 return true if !$self->is_connected;
643 my $parent = $self->group;
644 return $parent->effective_enable_auto_type if $parent;
645 return true;
646 }
647
648 ##############################################################################
649
650 sub _signal {
651 my $self = shift;
652 my $type = shift;
653 return $self->SUPER::_signal("entry.$type", @_);
654 }
655
656 sub _commit {
657 my $self = shift;
658 my $orig = shift;
659 $self->add_historical_entry($orig);
660 my $time = gmtime;
661 $self->last_modification_time($time);
662 $self->last_access_time($time);
663 }
664
665 sub label { shift->expand_title(@_) }
666
667 ### Name of the parent attribute expected to contain the object
668 sub _parent_container { 'entries' }
669
670 1;
671
672 __END__
673
674 =pod
675
676 =encoding UTF-8
677
678 =head1 NAME
679
680 File::KDBX::Entry - A KDBX database entry
681
682 =head1 VERSION
683
684 version 0.800
685
686 =head1 DESCRIPTION
687
688 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
689 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
690 that every entry has:
691
692 =over 4
693
694 =item *
695
696 B<Title>
697
698 =item *
699
700 B<UserName>
701
702 =item *
703
704 B<Password>
705
706 =item *
707
708 B<URL>
709
710 =item *
711
712 B<Notes>
713
714 =back
715
716 Beyond this, you can store any number of other strings and any number of binaries that you can use for
717 whatever purpose you want.
718
719 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
720 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
721 the attributes to see what's available.
722
723 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>.
724
725 =head2 Placeholders
726
727 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
728 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
729 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
730 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
731 C<http://example.com?user=batman>.
732
733 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
734 brace, like C<{PLACEHOLDER:ARGUMENT}>.
735
736 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
737 This software supports many (but not all) of the placeholders documented there.
738
739 =head3 Entry Placeholders
740
741 =over 4
742
743 =item *
744
745 ☑ C<{TITLE}> - B<Title> string
746
747 =item *
748
749 ☑ C<{USERNAME}> - B<UserName> string
750
751 =item *
752
753 ☑ C<{PASSWORD}> - B<Password> string
754
755 =item *
756
757 ☑ C<{NOTES}> - B<Notes> string
758
759 =item *
760
761 ☑ C<{URL}> - B<URL> string
762
763 =item *
764
765 ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
766
767 =item *
768
769 ☑ C<{URL:USERINFO}>
770
771 =item *
772
773 ☑ C<{URL:USERNAME}>
774
775 =item *
776
777 ☑ C<{URL:PASSWORD}>
778
779 =item *
780
781 ☑ C<{URL:HOST}>
782
783 =item *
784
785 ☑ C<{URL:PORT}>
786
787 =item *
788
789 ☑ C<{URL:PATH}>
790
791 =item *
792
793 ☑ C<{URL:QUERY}>
794
795 =item *
796
797 ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
798
799 =item *
800
801 ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
802
803 =item *
804
805 ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
806
807 =item *
808
809 ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
810
811 =item *
812
813 ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
814
815 =item *
816
817 ☑ C<{TIMEOTP}> - Generate a time-based one-time password
818
819 =item *
820
821 ☑ C<{GROUP_NOTES}> - Notes of the parent group
822
823 =item *
824
825 ☑ C<{GROUP_PATH}> - Full path of the parent group
826
827 =item *
828
829 ☑ C<{GROUP}> - Name of the parent group
830
831 =back
832
833 =head3 Field References
834
835 =over 4
836
837 =item *
838
839 ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
840
841 =back
842
843 =head3 File path Placeholders
844
845 =over 4
846
847 =item *
848
849 ☑ C<{APPDIR}> - Program directory path
850
851 =item *
852
853 ☑ C<{FIREFOX}> - Path to the Firefox browser executable
854
855 =item *
856
857 ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
858
859 =item *
860
861 ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
862
863 =item *
864
865 ☑ C<{OPERA}> - Path to the Opera browser executable
866
867 =item *
868
869 ☑ C<{SAFARI}> - Path to the Safari browser executable
870
871 =item *
872
873 ☒ C<{DB_PATH}> - Full file path of the database
874
875 =item *
876
877 ☒ C<{DB_DIR}> - Directory path of the database
878
879 =item *
880
881 ☒ C<{DB_NAME}> - File name (including extension) of the database
882
883 =item *
884
885 ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
886
887 =item *
888
889 ☒ C<{DB_EXT}> - File name extension
890
891 =item *
892
893 ☑ C<{ENV_DIRSEP}> - Directory separator
894
895 =item *
896
897 ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
898
899 =back
900
901 =head3 Date and Time Placeholders
902
903 =over 4
904
905 =item *
906
907 ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
908
909 =item *
910
911 ☑ C<{DT_YEAR}> - Year component of the current local date
912
913 =item *
914
915 ☑ C<{DT_MONTH}> - Month component of the current local date
916
917 =item *
918
919 ☑ C<{DT_DAY}> - Day component of the current local date
920
921 =item *
922
923 ☑ C<{DT_HOUR}> - Hour component of the current local time
924
925 =item *
926
927 ☑ C<{DT_MINUTE}> - Minute component of the current local time
928
929 =item *
930
931 ☑ C<{DT_SECOND}> - Second component of the current local time
932
933 =item *
934
935 ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
936
937 =item *
938
939 ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
940
941 =item *
942
943 ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
944
945 =item *
946
947 ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
948
949 =item *
950
951 ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
952
953 =item *
954
955 ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
956
957 =item *
958
959 ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
960
961 =back
962
963 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
964
965 =head3 Special Key Placeholders
966
967 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
968 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
969 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
970
971 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
972 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
973
974 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
975 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
976
977 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
978 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
979
980 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
981 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
982
983 =head3 Miscellaneous Placeholders
984
985 =over 4
986
987 =item *
988
989 ☒ C<{BASE}>
990
991 =item *
992
993 ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
994
995 =item *
996
997 ☒ C<{BASE:USERINFO}>
998
999 =item *
1000
1001 ☒ C<{BASE:USERNAME}>
1002
1003 =item *
1004
1005 ☒ C<{BASE:PASSWORD}>
1006
1007 =item *
1008
1009 ☒ C<{BASE:HOST}>
1010
1011 =item *
1012
1013 ☒ C<{BASE:PORT}>
1014
1015 =item *
1016
1017 ☒ C<{BASE:PATH}>
1018
1019 =item *
1020
1021 ☒ C<{BASE:QUERY}>
1022
1023 =item *
1024
1025 ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1026
1027 =item *
1028
1029 ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1030
1031 =item *
1032
1033 ☒ C<{CLIPBOARD-SET:/Text/}>
1034
1035 =item *
1036
1037 ☒ C<{CLIPBOARD}>
1038
1039 =item *
1040
1041 ☒ C<{CMD:/CommandLine/Options/}>
1042
1043 =item *
1044
1045 ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1046
1047 =item *
1048
1049 ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1050
1051 =item *
1052
1053 ☒ C<{GROUP_SEL_NOTES}>
1054
1055 =item *
1056
1057 ☒ C<{GROUP_SEL_PATH}>
1058
1059 =item *
1060
1061 ☒ C<{GROUP_SEL}>
1062
1063 =item *
1064
1065 ☒ C<{NEWPASSWORD}>
1066
1067 =item *
1068
1069 ☒ C<{NEWPASSWORD:/Profile/}>
1070
1071 =item *
1072
1073 ☒ C<{PASSWORD_ENC}>
1074
1075 =item *
1076
1077 ☒ C<{PICKCHARS}>
1078
1079 =item *
1080
1081 ☒ C<{PICKCHARS:Field:Options}>
1082
1083 =item *
1084
1085 ☒ C<{PICKFIELD}>
1086
1087 =item *
1088
1089 ☒ C<{T-CONV:/Text/Type/}>
1090
1091 =item *
1092
1093 ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1094
1095 =back
1096
1097 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1098 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1099 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1100 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1101
1102 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1103 my ($entry) = @_;
1104 ...;
1105 };
1106
1107 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1108 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1109 strings or auto-complete key sequences.
1110
1111 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1112 my ($entry, $arg) = @_; # ^ Notice the colon here
1113 ...;
1114 };
1115
1116 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1117 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1118 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1119
1120 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1121 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1122 both with and without a colon (or they could be different subroutines):
1123
1124 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1125 (undef, my $arg) = @_;
1126 return defined $arg ? rand($arg) : rand;
1127 };
1128
1129 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1130 all the handlers:
1131
1132 %File::KDBX::PLACEHOLDERS = ();
1133
1134 =head2 One-time Passwords
1135
1136 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1137 configuration storage isn't completely standardized, but this module supports two predominant configuration
1138 styles:
1139
1140 =over 4
1141
1142 =item *
1143
1144 L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1145
1146 =item *
1147
1148 KeePassXC
1149
1150 =back
1151
1152 B<NOTE:> To use this feature, you must install the suggested dependency:
1153
1154 =over 4
1155
1156 =item *
1157
1158 L<Pass::OTP>
1159
1160 =back
1161
1162 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1163 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1164
1165 To configure TOTP in the KeePass 2 style, set the following strings:
1166
1167 =over 4
1168
1169 =item *
1170
1171 C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and C<HMAC-SHA-512>
1172
1173 =item *
1174
1175 C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1176
1177 =item *
1178
1179 C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1180
1181 =item *
1182
1183 C<TimeOtp-Secret> - Text string secret, OR
1184
1185 =item *
1186
1187 C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1188
1189 =item *
1190
1191 C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1192
1193 =item *
1194
1195 C<TimeOtp-Secret-Base64> - Base64-encoded secret
1196
1197 =back
1198
1199 To configure HOTP in the KeePass 2 style, set the following strings:
1200
1201 =over 4
1202
1203 =item *
1204
1205 C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp> is called
1206
1207 =item *
1208
1209 C<HmacOtp-Secret> - Text string secret, OR
1210
1211 =item *
1212
1213 C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1214
1215 =item *
1216
1217 C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1218
1219 =item *
1220
1221 C<HmacOtp-Secret-Base64> - Base64-encoded secret
1222
1223 =back
1224
1225 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1226 these should actually be set or an error will be thrown.
1227
1228 Here's a basic example:
1229
1230 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1231 # OR
1232 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1233
1234 my $otp = $entry->time_otp;
1235
1236 =head1 ATTRIBUTES
1237
1238 =head2 uuid
1239
1240 128-bit UUID identifying the entry within the database.
1241
1242 =head2 icon_id
1243
1244 Integer representing a default icon. See L<File::KDBX::Constants/":icon"> for valid values.
1245
1246 =head2 custom_icon_uuid
1247
1248 128-bit UUID identifying a custom icon within the database.
1249
1250 =head2 foreground_color
1251
1252 Text color represented as a string of the form C<#000000>.
1253
1254 =head2 background_color
1255
1256 Background color represented as a string of the form C<#FFFFFF>.
1257
1258 =head2 override_url
1259
1260 TODO
1261
1262 =head2 tags
1263
1264 Text string with arbitrary tags which can be used to build a taxonomy.
1265
1266 =head2 auto_type_enabled
1267
1268 Whether or not the entry is eligible to be matched for auto-typing.
1269
1270 =head2 auto_type_obfuscation
1271
1272 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1273
1274 =head2 auto_type_default_sequence
1275
1276 The default auto-type keystroke sequence.
1277
1278 =head2 auto_type_associations
1279
1280 An array of window title / keystroke sequence associations.
1281
1282 {
1283 window => 'Example Window Title',
1284 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1285 }
1286
1287 Keystroke sequences can have </Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1288
1289 =head2 previous_parent_group
1290
1291 128-bit UUID identifying a group within the database.
1292
1293 =head2 quality_check
1294
1295 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1296
1297 =head2 strings
1298
1299 Hash with entry strings, including the standard strings as well as any custom ones.
1300
1301 {
1302 # Every entry has these five strings:
1303 Title => { value => 'Example Entry' },
1304 UserName => { value => 'jdoe' },
1305 Password => { value => 's3cr3t', protect => true },
1306 URL => { value => 'https://example.com' }
1307 Notes => { value => '' },
1308 # May also have custom strings:
1309 MySystem => { value => 'The mainframe' },
1310 }
1311
1312 There are methods available to provide more convenient access to strings, including L</string>,
1313 L</string_value>, L</expand_string_value> and L</string_peek>.
1314
1315 =head2 binaries
1316
1317 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1318 characters.
1319
1320 {
1321 'myfile.txt' => {
1322 value => '...',
1323 },
1324 'mysecrets.txt' => {
1325 value => '...',
1326 protect => true,
1327 },
1328 }
1329
1330 There are methods available to provide more convenient access to binaries, including L</binary> and
1331 L</binary_value>.
1332
1333 =head2 custom_data
1334
1335 A set of key-value pairs used to store arbitrary data, usually used by software to keep track of state rather
1336 than by end users (who typically work with the strings and binaries).
1337
1338 =head2 history
1339
1340 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
1341 same UUID with the current entry.
1342
1343 =head2 last_modification_time
1344
1345 Date and time when the entry was last modified.
1346
1347 =head2 creation_time
1348
1349 Date and time when the entry was created.
1350
1351 =head2 last_access_time
1352
1353 Date and time when the entry was last accessed.
1354
1355 =head2 expiry_time
1356
1357 Date and time when the entry expired or will expire.
1358
1359 =head2 expires
1360
1361 Boolean value indicating whether or not an entry is expired.
1362
1363 =head2 usage_count
1364
1365 The number of times an entry has been used, which typically means how many times the B<Password> string has
1366 been accessed.
1367
1368 =head2 location_changed
1369
1370 Date and time when the entry was last moved to a different parent group.
1371
1372 =head2 notes
1373
1374 Alias for the B<Notes> string value.
1375
1376 =head2 password
1377
1378 Alias for the B<Password> string value.
1379
1380 =head2 title
1381
1382 Alias for the B<Title> string value.
1383
1384 =head2 url
1385
1386 Alias for the B<URL> string value.
1387
1388 =head2 username
1389
1390 Aliases for the B<UserName> string value.
1391
1392 =head2 expand_notes
1393
1394 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
1395
1396 =head2 expand_password
1397
1398 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
1399
1400 =head2 expand_title
1401
1402 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
1403
1404 =head2 expand_url
1405
1406 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
1407
1408 =head2 expand_username
1409
1410 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
1411
1412 =head1 METHODS
1413
1414 =head2 string
1415
1416 \%string = $entry->string($string_key);
1417
1418 $entry->string($string_key, \%string);
1419 $entry->string($string_key, %attributes);
1420 $entry->string($string_key, $value); # same as: value => $value
1421
1422 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
1423 structure. For example:
1424
1425 $string = {
1426 value => 'Password',
1427 protect => true, # optional
1428 };
1429
1430 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1431 which might exist:
1432
1433 =over 4
1434
1435 =item *
1436
1437 C<protect> - Whether or not the string value should be memory-protected.
1438
1439 =back
1440
1441 =head2 string_value
1442
1443 $string = $entry->string_value($string_key);
1444
1445 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
1446 is not set or is currently memory-protected. This is just a shortcut for:
1447
1448 my $string = do {
1449 my $s = $entry->string(...);
1450 defined $s ? $s->{value} : undef;
1451 };
1452
1453 =head2 expand_string_value
1454
1455 $string = $entry->expand_string_value;
1456
1457 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
1458 do not expand to values are left as-is.
1459
1460 See L</Placeholders>.
1461
1462 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1463 error if it is not.
1464
1465 =head2 other_strings
1466
1467 $other = $entry->other_strings;
1468 $other = $entry->other_strings($delimiter);
1469
1470 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
1471 for executing queries to search for entities based on the contents of these other strings (if any).
1472
1473 =head2 string_peek
1474
1475 $string = $entry->string_peek($string_key);
1476
1477 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1478 protected.
1479
1480 =head2 add_auto_type_association
1481
1482 $entry->add_auto_type_association(\%association);
1483
1484 Add a new auto-type association to an entry.
1485
1486 =head2 expand_keystroke_sequence
1487
1488 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
1489 $string = $entry->expand_keystroke_sequence(\%association);
1490 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
1491
1492 Get a keystroke sequence after placeholder expansion.
1493
1494 =head2 binary
1495
1496 \%binary = $entry->binary($binary_key);
1497
1498 $entry->binary($binary_key, \%binary);
1499 $entry->binary($binary_key, %attributes);
1500 $entry->binary($binary_key, $value); # same as: value => $value
1501
1502 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
1503 structure. For example:
1504
1505 $binary = {
1506 value => '...',
1507 protect => true, # optional
1508 };
1509
1510 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1511 which might exist:
1512
1513 =over 4
1514
1515 =item *
1516
1517 C<protect> - Whether or not the binary value should be memory-protected.
1518
1519 =back
1520
1521 =head2 binary_value
1522
1523 $binary = $entry->binary_value($binary_key);
1524
1525 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
1526 is not set or is currently memory-protected. This is just a shortcut for:
1527
1528 my $binary = do {
1529 my $b = $entry->binary(...);
1530 defined $b ? $b->{value} : undef;
1531 };
1532
1533 =head2 hmac_otp
1534
1535 $otp = $entry->hmac_otp(%options);
1536
1537 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
1538 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1539
1540 =over 4
1541
1542 =item *
1543
1544 C<counter> - Specify the counter value
1545
1546 =back
1547
1548 To configure HOTP, see L</"One-time Passwords">.
1549
1550 =head2 time_otp
1551
1552 $otp = $entry->time_otp(%options);
1553
1554 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
1555 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1556
1557 =over 4
1558
1559 =item *
1560
1561 C<now> - Specify the value for determining the time-step counter
1562
1563 =back
1564
1565 To configure TOTP, see L</"One-time Passwords">.
1566
1567 =head2 hmac_otp_uri
1568
1569 =head2 time_otp_uri
1570
1571 $uri_string = $entry->hmac_otp_uri;
1572 $uri_string = $entry->time_otp_uri;
1573
1574 Get a HOTP or TOTP otpauth URI for the entry, if available.
1575
1576 To configure OTP, see L</"One-time Passwords">.
1577
1578 =head2 size
1579
1580 $size = $entry->size;
1581
1582 Get the size (in bytes) of an entry.
1583
1584 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
1585 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
1586
1587 =head2 history_size
1588
1589 $size = $entry->history_size;
1590
1591 Get the size (in bytes) of all historical entries combined.
1592
1593 =head2 prune_history
1594
1595 @removed_historical_entries = $entry->prune_history(%options);
1596
1597 Remove just as many older historical entries as necessary to get under the database limits. The limits are
1598 taken from the connected database (if any) or can be overridden with C<%options>:
1599
1600 =over 4
1601
1602 =item *
1603
1604 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1605
1606 =item *
1607
1608 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1609
1610 =item *
1611
1612 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1613
1614 =back
1615
1616 =head2 add_historical_entry
1617
1618 $entry->add_historical_entry($entry);
1619
1620 Add an entry to the history.
1621
1622 =head2 remove_historical_entry
1623
1624 $entry->remove_historical_entry($historical_entry);
1625
1626 Remove an entry from the history.
1627
1628 =head2 current_entry
1629
1630 $current_entry = $entry->current_entry;
1631
1632 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1633
1634 =head2 is_current
1635
1636 $bool = $entry->is_current;
1637
1638 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
1639 in the parent group's entry list.
1640
1641 =head2 is_historical
1642
1643 $bool = $entry->is_historical;
1644
1645 Get whether or not an entry is considered historical (i.e. not current).
1646
1647 This is just the inverse of L</is_current>.
1648
1649 =head2 remove
1650
1651 $entry = $entry->remove;
1652
1653 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1654 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1655
1656 =head2 searching_enabled
1657
1658 $bool = $entry->searching_enabled;
1659
1660 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1661 L<File::KDBX::Group/effective_enable_searching> value.
1662
1663 Throws if entry has no parent group or if the entry is not connected to a database.
1664
1665 =for Pod::Coverage auto_type times
1666
1667 =head1 BUGS
1668
1669 Please report any bugs or feature requests on the bugtracker website
1670 L<https://github.com/chazmcgarvey/File-KDBX/issues>
1671
1672 When submitting a bug or request, please include a test-file or a
1673 patch to an existing test-file that illustrates the bug or desired
1674 feature.
1675
1676 =head1 AUTHOR
1677
1678 Charles McGarvey <ccm@cpan.org>
1679
1680 =head1 COPYRIGHT AND LICENSE
1681
1682 This software is copyright (c) 2022 by Charles McGarvey.
1683
1684 This is free software; you can redistribute it and/or modify it under
1685 the same terms as the Perl 5 programming language system itself.
1686
1687 =cut
This page took 0.129288 seconds and 4 git commands to generate.