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