]> Dogcows Code - chaz/p5-File-KDBX/blob - lib/File/KDBX/Entry.pm
Version 0.901
[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.901'; # 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} // 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.901
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 C<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-type 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 foreground_color
1239
1240 Text color represented as a string of the form C<#000000>.
1241
1242 =head2 background_color
1243
1244 Background color represented as a string of the form C<#FFFFFF>.
1245
1246 =head2 override_url
1247
1248 TODO
1249
1250 =head2 auto_type_enabled
1251
1252 Whether or not the entry is eligible to be matched for auto-typing.
1253
1254 =head2 auto_type_obfuscation
1255
1256 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1257
1258 =head2 auto_type_default_sequence
1259
1260 The default auto-type keystroke sequence.
1261
1262 =head2 auto_type_associations
1263
1264 An array of window title / keystroke sequence associations.
1265
1266 {
1267 window => 'Example Window Title',
1268 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1269 }
1270
1271 Keystroke sequences can have </Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1272
1273 =head2 quality_check
1274
1275 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1276
1277 =head2 strings
1278
1279 Hash with entry strings, including the standard strings as well as any custom ones.
1280
1281 {
1282 # Every entry has these five strings:
1283 Title => { value => 'Example Entry' },
1284 UserName => { value => 'jdoe' },
1285 Password => { value => 's3cr3t', protect => true },
1286 URL => { value => 'https://example.com' }
1287 Notes => { value => '' },
1288 # May also have custom strings:
1289 MySystem => { value => 'The mainframe' },
1290 }
1291
1292 There are methods available to provide more convenient access to strings, including L</string>,
1293 L</string_value>, L</expand_string_value> and L</string_peek>.
1294
1295 =head2 binaries
1296
1297 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1298 characters.
1299
1300 {
1301 'myfile.txt' => {
1302 value => '...',
1303 },
1304 'mysecrets.txt' => {
1305 value => '...',
1306 protect => true,
1307 },
1308 }
1309
1310 There are methods available to provide more convenient access to binaries, including L</binary> and
1311 L</binary_value>.
1312
1313 =head2 history
1314
1315 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
1316 same UUID with the current entry.
1317
1318 =head2 notes
1319
1320 Alias for the B<Notes> string value.
1321
1322 =head2 password
1323
1324 Alias for the B<Password> string value.
1325
1326 =head2 title
1327
1328 Alias for the B<Title> string value.
1329
1330 =head2 url
1331
1332 Alias for the B<URL> string value.
1333
1334 =head2 username
1335
1336 Aliases for the B<UserName> string value.
1337
1338 =head2 expand_notes
1339
1340 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
1341
1342 =head2 expand_password
1343
1344 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
1345
1346 =head2 expand_title
1347
1348 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
1349
1350 =head2 expand_url
1351
1352 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
1353
1354 =head2 expand_username
1355
1356 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
1357
1358 =head1 METHODS
1359
1360 =head2 string
1361
1362 \%string = $entry->string($string_key);
1363
1364 $entry->string($string_key, \%string);
1365 $entry->string($string_key, %attributes);
1366 $entry->string($string_key, $value); # same as: value => $value
1367
1368 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
1369 structure. For example:
1370
1371 $string = {
1372 value => 'Password',
1373 protect => true, # optional
1374 };
1375
1376 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1377 which might exist:
1378
1379 =over 4
1380
1381 =item *
1382
1383 C<protect> - Whether or not the string value should be memory-protected.
1384
1385 =back
1386
1387 =head2 string_value
1388
1389 $string = $entry->string_value($string_key);
1390
1391 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
1392 is not set or is currently memory-protected. This is just a shortcut for:
1393
1394 my $string = do {
1395 my $s = $entry->string(...);
1396 defined $s ? $s->{value} : undef;
1397 };
1398
1399 =head2 expand_string_value
1400
1401 $string = $entry->expand_string_value;
1402
1403 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
1404 do not expand to values are left as-is.
1405
1406 See L</Placeholders>.
1407
1408 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1409 error if it is not.
1410
1411 =head2 other_strings
1412
1413 $other = $entry->other_strings;
1414 $other = $entry->other_strings($delimiter);
1415
1416 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
1417 for executing queries to search for entities based on the contents of these other strings (if any).
1418
1419 =head2 string_peek
1420
1421 $string = $entry->string_peek($string_key);
1422
1423 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1424 protected.
1425
1426 =head2 add_auto_type_association
1427
1428 $entry->add_auto_type_association(\%association);
1429
1430 Add a new auto-type association to an entry.
1431
1432 =head2 expand_keystroke_sequence
1433
1434 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
1435 $string = $entry->expand_keystroke_sequence(\%association);
1436 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
1437
1438 Get a keystroke sequence after placeholder expansion.
1439
1440 =head2 binary
1441
1442 \%binary = $entry->binary($binary_key);
1443
1444 $entry->binary($binary_key, \%binary);
1445 $entry->binary($binary_key, %attributes);
1446 $entry->binary($binary_key, $value); # same as: value => $value
1447
1448 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
1449 structure. For example:
1450
1451 $binary = {
1452 value => '...',
1453 protect => true, # optional
1454 };
1455
1456 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1457 which might exist:
1458
1459 =over 4
1460
1461 =item *
1462
1463 C<protect> - Whether or not the binary value should be memory-protected.
1464
1465 =back
1466
1467 =head2 binary_value
1468
1469 $binary = $entry->binary_value($binary_key);
1470
1471 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
1472 is not set or is currently memory-protected. This is just a shortcut for:
1473
1474 my $binary = do {
1475 my $b = $entry->binary(...);
1476 defined $b ? $b->{value} : undef;
1477 };
1478
1479 =head2 hmac_otp
1480
1481 $otp = $entry->hmac_otp(%options);
1482
1483 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
1484 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1485
1486 =over 4
1487
1488 =item *
1489
1490 C<counter> - Specify the counter value
1491
1492 =back
1493
1494 To configure HOTP, see L</"One-time Passwords">.
1495
1496 =head2 time_otp
1497
1498 $otp = $entry->time_otp(%options);
1499
1500 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
1501 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1502
1503 =over 4
1504
1505 =item *
1506
1507 C<now> - Specify the value for determining the time-step counter
1508
1509 =back
1510
1511 To configure TOTP, see L</"One-time Passwords">.
1512
1513 =head2 hmac_otp_uri
1514
1515 =head2 time_otp_uri
1516
1517 $uri_string = $entry->hmac_otp_uri;
1518 $uri_string = $entry->time_otp_uri;
1519
1520 Get a HOTP or TOTP otpauth URI for the entry, if available.
1521
1522 To configure OTP, see L</"One-time Passwords">.
1523
1524 =head2 size
1525
1526 $size = $entry->size;
1527
1528 Get the size (in bytes) of an entry.
1529
1530 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
1531 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
1532
1533 =head2 history_size
1534
1535 $size = $entry->history_size;
1536
1537 Get the size (in bytes) of all historical entries combined.
1538
1539 =head2 prune_history
1540
1541 @removed_historical_entries = $entry->prune_history(%options);
1542
1543 Remove just as many older historical entries as necessary to get under the database limits. The limits are
1544 taken from the connected database (if any) or can be overridden with C<%options>:
1545
1546 =over 4
1547
1548 =item *
1549
1550 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1551
1552 =item *
1553
1554 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1555
1556 =item *
1557
1558 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1559
1560 =back
1561
1562 =head2 add_historical_entry
1563
1564 $entry->add_historical_entry($entry);
1565
1566 Add an entry to the history.
1567
1568 =head2 remove_historical_entry
1569
1570 $entry->remove_historical_entry($historical_entry);
1571
1572 Remove an entry from the history.
1573
1574 =head2 current_entry
1575
1576 $current_entry = $entry->current_entry;
1577
1578 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1579
1580 =head2 is_current
1581
1582 $bool = $entry->is_current;
1583
1584 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
1585 in the parent group's entry list.
1586
1587 =head2 is_historical
1588
1589 $bool = $entry->is_historical;
1590
1591 Get whether or not an entry is considered historical (i.e. not current).
1592
1593 This is just the inverse of L</is_current>.
1594
1595 =head2 remove
1596
1597 $entry = $entry->remove;
1598
1599 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1600 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1601
1602 =head2 searching_enabled
1603
1604 $bool = $entry->searching_enabled;
1605
1606 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1607 L<File::KDBX::Group/effective_enable_searching> value.
1608
1609 Throws if entry has no parent group or if the entry is not connected to a database.
1610
1611 =for Pod::Coverage auto_type times
1612
1613 =head1 BUGS
1614
1615 Please report any bugs or feature requests on the bugtracker website
1616 L<https://github.com/chazmcgarvey/File-KDBX/issues>
1617
1618 When submitting a bug or request, please include a test-file or a
1619 patch to an existing test-file that illustrates the bug or desired
1620 feature.
1621
1622 =head1 AUTHOR
1623
1624 Charles McGarvey <ccm@cpan.org>
1625
1626 =head1 COPYRIGHT AND LICENSE
1627
1628 This software is copyright (c) 2022 by Charles McGarvey.
1629
1630 This is free software; you can redistribute it and/or modify it under
1631 the same terms as the Perl 5 programming language system itself.
1632
1633 =cut
This page took 0.143949 seconds and 4 git commands to generate.