]> Dogcows Code - chaz/p5-File-KDBX/blob - lib/File/KDBX/Entry.pm
6ca2051a0ab72d382bfa194a374f31be4b49fdbf
[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.049 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(any 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 1.33;
19 use boolean;
20 use namespace::clean;
21
22 extends 'File::KDBX::Object';
23
24 our $VERSION = '0.905'; # 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->expand_title || 'KDBX',
381 account => $self->expand_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->expand_title || 'KDBX',
407 account => $self->expand_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} // eval { $self->kdbx->maintenance_history_days } // 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.905
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>. View its documentation to see other attributes
724 and methods available on entries.
725
726 =head2 Placeholders
727
728 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
729 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
730 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
731 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
732 C<http://example.com?user=batman>.
733
734 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
735 brace, like C<{PLACEHOLDER:ARGUMENT}>.
736
737 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
738 This software supports many (but not all) of the placeholders documented there.
739
740 =head3 Entry Placeholders
741
742 =over 4
743
744 =item *
745
746 ☑ C<{TITLE}> - B<Title> string
747
748 =item *
749
750 ☑ C<{USERNAME}> - B<UserName> string
751
752 =item *
753
754 ☑ C<{PASSWORD}> - B<Password> string
755
756 =item *
757
758 ☑ C<{NOTES}> - B<Notes> string
759
760 =item *
761
762 ☑ C<{URL}> - B<URL> string
763
764 =item *
765
766 ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
767
768 =item *
769
770 ☑ C<{URL:USERINFO}>
771
772 =item *
773
774 ☑ C<{URL:USERNAME}>
775
776 =item *
777
778 ☑ C<{URL:PASSWORD}>
779
780 =item *
781
782 ☑ C<{URL:HOST}>
783
784 =item *
785
786 ☑ C<{URL:PORT}>
787
788 =item *
789
790 ☑ C<{URL:PATH}>
791
792 =item *
793
794 ☑ C<{URL:QUERY}>
795
796 =item *
797
798 ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
799
800 =item *
801
802 ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
803
804 =item *
805
806 ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
807
808 =item *
809
810 ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
811
812 =item *
813
814 ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
815
816 =item *
817
818 ☑ C<{TIMEOTP}> - Generate a time-based one-time password
819
820 =item *
821
822 ☑ C<{GROUP_NOTES}> - Notes of the parent group
823
824 =item *
825
826 ☑ C<{GROUP_PATH}> - Full path of the parent group
827
828 =item *
829
830 ☑ C<{GROUP}> - Name of the parent group
831
832 =back
833
834 =head3 Field References
835
836 =over 4
837
838 =item *
839
840 ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
841
842 =back
843
844 =head3 File path Placeholders
845
846 =over 4
847
848 =item *
849
850 ☑ C<{APPDIR}> - Program directory path
851
852 =item *
853
854 ☑ C<{FIREFOX}> - Path to the Firefox browser executable
855
856 =item *
857
858 ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
859
860 =item *
861
862 ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
863
864 =item *
865
866 ☑ C<{OPERA}> - Path to the Opera browser executable
867
868 =item *
869
870 ☑ C<{SAFARI}> - Path to the Safari browser executable
871
872 =item *
873
874 ☒ C<{DB_PATH}> - Full file path of the database
875
876 =item *
877
878 ☒ C<{DB_DIR}> - Directory path of the database
879
880 =item *
881
882 ☒ C<{DB_NAME}> - File name (including extension) of the database
883
884 =item *
885
886 ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
887
888 =item *
889
890 ☒ C<{DB_EXT}> - File name extension
891
892 =item *
893
894 ☑ C<{ENV_DIRSEP}> - Directory separator
895
896 =item *
897
898 ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
899
900 =back
901
902 =head3 Date and Time Placeholders
903
904 =over 4
905
906 =item *
907
908 ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
909
910 =item *
911
912 ☑ C<{DT_YEAR}> - Year component of the current local date
913
914 =item *
915
916 ☑ C<{DT_MONTH}> - Month component of the current local date
917
918 =item *
919
920 ☑ C<{DT_DAY}> - Day component of the current local date
921
922 =item *
923
924 ☑ C<{DT_HOUR}> - Hour component of the current local time
925
926 =item *
927
928 ☑ C<{DT_MINUTE}> - Minute component of the current local time
929
930 =item *
931
932 ☑ C<{DT_SECOND}> - Second component of the current local time
933
934 =item *
935
936 ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
937
938 =item *
939
940 ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
941
942 =item *
943
944 ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
945
946 =item *
947
948 ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
949
950 =item *
951
952 ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
953
954 =item *
955
956 ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
957
958 =item *
959
960 ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
961
962 =back
963
964 If the current date and time is C<2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
965
966 =head3 Special Key Placeholders
967
968 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
969 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
970 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
971
972 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
973 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
974
975 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
976 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
977
978 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
979 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
980
981 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
982 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
983
984 =head3 Miscellaneous Placeholders
985
986 =over 4
987
988 =item *
989
990 ☒ C<{BASE}>
991
992 =item *
993
994 ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
995
996 =item *
997
998 ☒ C<{BASE:USERINFO}>
999
1000 =item *
1001
1002 ☒ C<{BASE:USERNAME}>
1003
1004 =item *
1005
1006 ☒ C<{BASE:PASSWORD}>
1007
1008 =item *
1009
1010 ☒ C<{BASE:HOST}>
1011
1012 =item *
1013
1014 ☒ C<{BASE:PORT}>
1015
1016 =item *
1017
1018 ☒ C<{BASE:PATH}>
1019
1020 =item *
1021
1022 ☒ C<{BASE:QUERY}>
1023
1024 =item *
1025
1026 ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1027
1028 =item *
1029
1030 ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1031
1032 =item *
1033
1034 ☒ C<{CLIPBOARD-SET:/Text/}>
1035
1036 =item *
1037
1038 ☒ C<{CLIPBOARD}>
1039
1040 =item *
1041
1042 ☒ C<{CMD:/CommandLine/Options/}>
1043
1044 =item *
1045
1046 ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1047
1048 =item *
1049
1050 ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1051
1052 =item *
1053
1054 ☒ C<{GROUP_SEL_NOTES}>
1055
1056 =item *
1057
1058 ☒ C<{GROUP_SEL_PATH}>
1059
1060 =item *
1061
1062 ☒ C<{GROUP_SEL}>
1063
1064 =item *
1065
1066 ☒ C<{NEWPASSWORD}>
1067
1068 =item *
1069
1070 ☒ C<{NEWPASSWORD:/Profile/}>
1071
1072 =item *
1073
1074 ☒ C<{PASSWORD_ENC}>
1075
1076 =item *
1077
1078 ☒ C<{PICKCHARS}>
1079
1080 =item *
1081
1082 ☒ C<{PICKCHARS:Field:Options}>
1083
1084 =item *
1085
1086 ☒ C<{PICKFIELD}>
1087
1088 =item *
1089
1090 ☒ C<{T-CONV:/Text/Type/}>
1091
1092 =item *
1093
1094 ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1095
1096 =back
1097
1098 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1099 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1100 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1101 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1102
1103 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1104 my ($entry) = @_;
1105 ...;
1106 };
1107
1108 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1109 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1110 strings or auto-type key sequences.
1111
1112 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1113 my ($entry, $arg) = @_; # ^ Notice the colon here
1114 ...;
1115 };
1116
1117 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1118 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1119 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1120
1121 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1122 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1123 both with and without a colon (or they could be different subroutines):
1124
1125 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1126 (undef, my $arg) = @_;
1127 return defined $arg ? rand($arg) : rand;
1128 };
1129
1130 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1131 all the handlers:
1132
1133 %File::KDBX::PLACEHOLDERS = ();
1134
1135 =head2 One-time Passwords
1136
1137 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1138 configuration storage isn't completely standardized, but this module supports two predominant configuration
1139 styles:
1140
1141 =over 4
1142
1143 =item *
1144
1145 L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1146
1147 =item *
1148
1149 KeePassXC
1150
1151 =back
1152
1153 B<NOTE:> To use this feature, you must install the suggested dependency:
1154
1155 =over 4
1156
1157 =item *
1158
1159 L<Pass::OTP>
1160
1161 =back
1162
1163 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1164 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1165
1166 To configure TOTP in the KeePass 2 style, set the following strings:
1167
1168 =over 4
1169
1170 =item *
1171
1172 C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and C<HMAC-SHA-512>
1173
1174 =item *
1175
1176 C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1177
1178 =item *
1179
1180 C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1181
1182 =item *
1183
1184 C<TimeOtp-Secret> - Text string secret, OR
1185
1186 =item *
1187
1188 C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1189
1190 =item *
1191
1192 C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1193
1194 =item *
1195
1196 C<TimeOtp-Secret-Base64> - Base64-encoded secret
1197
1198 =back
1199
1200 To configure HOTP in the KeePass 2 style, set the following strings:
1201
1202 =over 4
1203
1204 =item *
1205
1206 C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp> is called
1207
1208 =item *
1209
1210 C<HmacOtp-Secret> - Text string secret, OR
1211
1212 =item *
1213
1214 C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1215
1216 =item *
1217
1218 C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1219
1220 =item *
1221
1222 C<HmacOtp-Secret-Base64> - Base64-encoded secret
1223
1224 =back
1225
1226 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1227 these should actually be set or an error will be thrown.
1228
1229 Here's a basic example:
1230
1231 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1232 # OR
1233 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1234
1235 my $otp = $entry->time_otp;
1236
1237 =head1 ATTRIBUTES
1238
1239 =head2 foreground_color
1240
1241 Text color represented as a string of the form C<#000000>.
1242
1243 =head2 background_color
1244
1245 Background color represented as a string of the form C<#FFFFFF>.
1246
1247 =head2 override_url
1248
1249 TODO
1250
1251 =head2 auto_type_enabled
1252
1253 Whether or not the entry is eligible to be matched for auto-typing.
1254
1255 =head2 auto_type_obfuscation
1256
1257 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1258
1259 =head2 auto_type_default_sequence
1260
1261 The default auto-type keystroke sequence.
1262
1263 =head2 auto_type_associations
1264
1265 An array of window title / keystroke sequence associations.
1266
1267 {
1268 window => 'Example Window Title',
1269 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1270 }
1271
1272 Keystroke sequences can have L</Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1273
1274 =head2 quality_check
1275
1276 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1277
1278 =head2 strings
1279
1280 Hash with entry strings, including the standard strings as well as any custom ones.
1281
1282 {
1283 # Every entry has these five strings:
1284 Title => { value => 'Example Entry' },
1285 UserName => { value => 'jdoe' },
1286 Password => { value => 's3cr3t', protect => true },
1287 URL => { value => 'https://example.com' }
1288 Notes => { value => '' },
1289 # May also have custom strings:
1290 MySystem => { value => 'The mainframe' },
1291 }
1292
1293 There are methods available to provide more convenient access to strings, including L</string>,
1294 L</string_value>, L</expand_string_value> and L</string_peek>.
1295
1296 =head2 binaries
1297
1298 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1299 characters.
1300
1301 {
1302 'myfile.txt' => {
1303 value => '...',
1304 },
1305 'mysecrets.txt' => {
1306 value => '...',
1307 protect => true,
1308 },
1309 }
1310
1311 There are methods available to provide more convenient access to binaries, including L</binary> and
1312 L</binary_value>.
1313
1314 =head2 history
1315
1316 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
1317 same UUID with the current entry.
1318
1319 =head2 notes
1320
1321 Alias for the B<Notes> string value.
1322
1323 =head2 password
1324
1325 Alias for the B<Password> string value.
1326
1327 =head2 title
1328
1329 Alias for the B<Title> string value.
1330
1331 =head2 url
1332
1333 Alias for the B<URL> string value.
1334
1335 =head2 username
1336
1337 Aliases for the B<UserName> string value.
1338
1339 =head1 METHODS
1340
1341 =head2 string
1342
1343 \%string = $entry->string($string_key);
1344
1345 $entry->string($string_key, \%string);
1346 $entry->string($string_key, %attributes);
1347 $entry->string($string_key, $value); # same as: value => $value
1348
1349 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
1350 structure. For example:
1351
1352 $string = {
1353 value => 'Password',
1354 protect => true, # optional
1355 };
1356
1357 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1358 which might exist:
1359
1360 =over 4
1361
1362 =item *
1363
1364 C<protect> - Whether or not the string value should be memory-protected.
1365
1366 =back
1367
1368 =head2 string_value
1369
1370 $string = $entry->string_value($string_key);
1371
1372 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
1373 is not set or is currently memory-protected. This is just a shortcut for:
1374
1375 my $string = do {
1376 my $s = $entry->string(...);
1377 defined $s ? $s->{value} : undef;
1378 };
1379
1380 =head2 expand_string_value
1381
1382 $string = $entry->expand_string_value($string_key);
1383
1384 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
1385 do not expand to values are left as-is.
1386
1387 See L</Placeholders>.
1388
1389 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1390 error if it is not.
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 =head2 other_strings
1413
1414 $other = $entry->other_strings;
1415 $other = $entry->other_strings($delimiter);
1416
1417 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
1418 for executing queries to search for entities based on the contents of these other strings (if any).
1419
1420 =head2 string_peek
1421
1422 $string = $entry->string_peek($string_key);
1423
1424 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1425 protected.
1426
1427 =head2 add_auto_type_association
1428
1429 $entry->add_auto_type_association(\%association);
1430
1431 Add a new auto-type association to an entry.
1432
1433 =head2 expand_keystroke_sequence
1434
1435 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
1436 $string = $entry->expand_keystroke_sequence(\%association);
1437 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
1438
1439 Get a keystroke sequence after placeholder expansion.
1440
1441 =head2 binary
1442
1443 \%binary = $entry->binary($binary_key);
1444
1445 $entry->binary($binary_key, \%binary);
1446 $entry->binary($binary_key, %attributes);
1447 $entry->binary($binary_key, $value); # same as: value => $value
1448
1449 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
1450 structure. For example:
1451
1452 $binary = {
1453 value => '...',
1454 protect => true, # optional
1455 };
1456
1457 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1458 which might exist:
1459
1460 =over 4
1461
1462 =item *
1463
1464 C<protect> - Whether or not the binary value should be memory-protected.
1465
1466 =back
1467
1468 =head2 binary_value
1469
1470 $binary = $entry->binary_value($binary_key);
1471
1472 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
1473 is not set or is currently memory-protected. This is just a shortcut for:
1474
1475 my $binary = do {
1476 my $b = $entry->binary(...);
1477 defined $b ? $b->{value} : undef;
1478 };
1479
1480 =head2 hmac_otp
1481
1482 $otp = $entry->hmac_otp(%options);
1483
1484 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
1485 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1486
1487 =over 4
1488
1489 =item *
1490
1491 C<counter> - Specify the counter value
1492
1493 =back
1494
1495 To configure HOTP, see L</"One-time Passwords">.
1496
1497 =head2 time_otp
1498
1499 $otp = $entry->time_otp(%options);
1500
1501 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
1502 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1503
1504 =over 4
1505
1506 =item *
1507
1508 C<now> - Specify the value for determining the time-step counter
1509
1510 =back
1511
1512 To configure TOTP, see L</"One-time Passwords">.
1513
1514 =head2 hmac_otp_uri
1515
1516 =head2 time_otp_uri
1517
1518 $uri_string = $entry->hmac_otp_uri;
1519 $uri_string = $entry->time_otp_uri;
1520
1521 Get a HOTP or TOTP otpauth URI for the entry, if available.
1522
1523 To configure OTP, see L</"One-time Passwords">.
1524
1525 =head2 size
1526
1527 $size = $entry->size;
1528
1529 Get the size (in bytes) of an entry.
1530
1531 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
1532 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
1533
1534 =head2 history_size
1535
1536 $size = $entry->history_size;
1537
1538 Get the size (in bytes) of all historical entries combined.
1539
1540 =head2 prune_history
1541
1542 @removed_historical_entries = $entry->prune_history(%options);
1543
1544 Remove just as many older historical entries as necessary to get under the database limits. The limits are
1545 taken from the connected database (if any) or can be overridden with C<%options>:
1546
1547 =over 4
1548
1549 =item *
1550
1551 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1552
1553 =item *
1554
1555 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1556
1557 =item *
1558
1559 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1560
1561 =back
1562
1563 =head2 add_historical_entry
1564
1565 $entry->add_historical_entry($entry);
1566
1567 Add an entry to the history.
1568
1569 =head2 remove_historical_entry
1570
1571 $entry->remove_historical_entry($historical_entry);
1572
1573 Remove an entry from the history.
1574
1575 =head2 current_entry
1576
1577 $current_entry = $entry->current_entry;
1578
1579 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1580
1581 =head2 is_current
1582
1583 $bool = $entry->is_current;
1584
1585 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
1586 in the parent group's entry list.
1587
1588 =head2 is_historical
1589
1590 $bool = $entry->is_historical;
1591
1592 Get whether or not an entry is considered historical (i.e. not current).
1593
1594 This is just the inverse of L</is_current>.
1595
1596 =head2 remove
1597
1598 $entry = $entry->remove;
1599
1600 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1601 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1602
1603 =head2 searching_enabled
1604
1605 $bool = $entry->searching_enabled;
1606
1607 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1608 L<File::KDBX::Group/effective_enable_searching> value.
1609
1610 Throws if entry has no parent group or if the entry is not connected to a database.
1611
1612 =for Pod::Coverage auto_type times
1613
1614 =head1 BUGS
1615
1616 Please report any bugs or feature requests on the bugtracker website
1617 L<https://github.com/chazmcgarvey/File-KDBX/issues>
1618
1619 When submitting a bug or request, please include a test-file or a
1620 patch to an existing test-file that illustrates the bug or desired
1621 feature.
1622
1623 =head1 AUTHOR
1624
1625 Charles McGarvey <ccm@cpan.org>
1626
1627 =head1 COPYRIGHT AND LICENSE
1628
1629 This software is copyright (c) 2022 by Charles McGarvey.
1630
1631 This is free software; you can redistribute it and/or modify it under
1632 the same terms as the Perl 5 programming language system itself.
1633
1634 =cut
This page took 0.134762 seconds and 3 git commands to generate.