1 package File
::KDBX
::Entry
;
2 # ABSTRACT: A KDBX database entry
7 use Crypt
::Misc
0.029 qw(decode_b64 encode_b32r);
8 use Devel
::GlobalDestruction
;
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);
22 extends
'File::KDBX::Object';
24 our $VERSION = '0.901'; # VERSION
26 my $PLACEHOLDER_MAX_DEPTH = 10;
28 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
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;
40 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid && $self->is_current;
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
;
53 has previous_parent_group
=> undef, coerce
=> \
&to_uuid
;
54 has quality_check
=> true
, coerce
=> \
&to_bool
;
58 # has custom_data => {};
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
;
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';
78 username
=> 'UserName',
79 password
=> 'Password',
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, @_) };
89 my @ATTRS = qw(uuid custom_data history auto_type_enabled);
90 sub _set_nonlazy_attributes
{
92 $self->$_ for @ATTRS, keys %ATTRS_STRINGS, list_attributes
(ref $self);
99 while (my ($key, $val) = each %args) {
100 if (my $method = $self->can($key)) {
101 $self->$method($val);
104 $self->string($key => $val);
111 ##############################################################################
116 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
117 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
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};
128 my $key = delete $args{key
} or throw
'Must provide a string key to access';
130 return $self->{strings
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
132 while (my ($field, $value) = each %args) {
133 $self->{strings
}{$key}{$field} = $value;
136 # Auto-vivify the standard strings.
137 if ($STANDARD_STRINGS{$key}) {
138 return $self->{strings
}{$key} //= {value
=> '', $self->_protect($key) ? (protect
=> true
) : ()};
140 return $self->{strings
}{$key};
143 ### Get whether or not a standard string is configured to be protected
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;
152 return $key eq 'Password';
158 my $string = $self->string(@_) // return undef;
159 return $string->{value
};
163 sub _expand_placeholder
{
165 my $placeholder = shift;
170 my $placeholder_key = $placeholder;
172 $placeholder_key = $File::KDBX
::PLACEHOLDERS
{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
175 return if !defined $File::KDBX
::PLACEHOLDERS
{$placeholder_key};
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;
187 return $handler->($self, $arg, $placeholder);
194 my $expand = memoize
$self->can('_expand_placeholder'), $self;
196 # placeholders (including field references):
197 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
199 # environment variables (alt syntax):
200 my $vars = join('|', map { quotemeta($_) } keys %ENV);
201 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
206 sub expand_string_value
{
208 my $str = $self->string_peek(@_) // return undef;
209 my $cleanup = erase_scoped
$str;
210 return $self->_expand_string($str);
216 my $delim = shift // "\n";
218 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
219 return join($delim, @strings);
225 my $string = $self->string(@_);
226 return defined $string->{value
} ? $string->{value
} : $self->kdbx->peek($string);
229 ##############################################################################
232 sub add_auto_type_association
{
234 my $association = shift;
235 push @{$self->auto_type_associations}, $association;
239 sub expand_keystroke_sequence
{
241 my $association = shift;
245 $keys = is_hashref
($association) && exists $association->{keystroke_sequence
} ?
246 $association->{keystroke_sequence
} : defined $association ? $association : '';
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..
253 return $self->_expand_string($keys);
256 ##############################################################################
261 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
262 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
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};
273 my $key = delete $args{key
} or throw
'Must provide a binary key to access';
275 return $self->{binaries
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
277 assert
{ !defined $args{value
} || !utf8
::is_utf8
($args{value
}) };
278 while (my ($field, $value) = each %args) {
279 $self->{binaries
}{$key}{$field} = $value;
281 return $self->{binaries
}{$key};
287 my $binary = $self->binary(@_) // return undef;
288 return $binary->{value
};
291 ##############################################################################
296 load_optional
('Pass::OTP');
298 my %params = ($self->_hotp_params, @_);
299 return if !defined $params{type
} || !defined $params{secret
};
301 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
304 my $otp = eval {Pass
::OTP
::otp
(%params, @_) };
306 throw
'Unable to generate HOTP', error
=> $err;
309 $self->_hotp_increment_counter($params{counter
});
317 load_optional
('Pass::OTP');
319 my %params = ($self->_totp_params, @_);
320 return if !defined $params{type
} || !defined $params{secret
};
322 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
325 my $otp = eval {Pass
::OTP
::otp
(%params, @_) };
327 throw
'Unable to generate TOTP', error
=> $err;
334 sub hmac_otp_uri
{ $_[0]->_otp_uri($_[0]->_hotp_params) }
335 sub time_otp_uri
{ $_[0]->_otp_uri($_[0]->_totp_params) }
341 return if 4 != grep { defined } @params{qw(type secret issuer account)};
342 return if $params{type
} !~ /^[ht]otp$/i;
344 my $label = delete $params{label
};
345 $params{$_} = uri_escape_utf8
($params{$_}) for keys %params;
347 my $type = lc($params{type
});
348 my $issuer = $params{issuer
};
349 my $account = $params{account
};
351 $label //= "$issuer:$account";
353 my $secret = $params{secret
};
354 $secret = uc(encode_b32r
($secret)) if !$params{base32
};
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;
361 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
363 if (defined $params{encoder
}) {
364 $uri .= "&encoder=$params{encoder}";
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
};
380 issuer
=> $self->title || 'KDBX',
381 account
=> $self->username || 'none',
383 counter
=> $self->string_value('HmacOtp-Counter') // 0,
384 $self->_otp_secret_params('Hmac'),
386 return %params if $params{secret
};
388 my %otp_params = $self->_otp_params;
389 return () if !$otp_params{secret
} || $otp_params{type
} ne 'hotp';
391 # $otp_params{counter} = 0
393 return (%params, %otp_params);
400 'HMAC-SHA-1' => 'sha1',
401 'HMAC-SHA-256' => 'sha256',
402 'HMAC-SHA-512' => 'sha512',
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'),
413 return %params if $params{secret
};
415 my %otp_params = $self->_otp_params;
416 return () if !$otp_params{secret
} || $otp_params{type
} ne 'totp';
418 return (%params, %otp_params);
424 load_optional
('Pass::OTP::URI');
426 my $uri = $self->string_value('otp') || '';
428 %params = Pass
::OTP
::URI
::parse
($uri) if $uri =~ m!^otpauth://!;
429 return () if !$params{secret
} || !$params{type
};
431 if (($params{encoder
} // '') eq 'steam') {
433 $params{chars
} = '23456789BCDFGHJKMNPQRTVWXY';
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);
441 $params{algorithm
} = lc($params{algorithm
}) if $params{algorithm
};
442 $params{counter
} = $self->string_value('HmacOtp-Counter') if $params{type
} eq 'hotp';
447 sub _otp_secret_params
{
449 my $type = shift // return ();
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");
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;
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));
466 sub _hotp_increment_counter
{
468 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
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);
476 ##############################################################################
485 $size += length(encode
('UTF-8', $self->tags // ''));
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
} // ''));
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
} // ''));
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;
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
} // ''));
516 ##############################################################################
520 my $entries = $self->{history
} //= [];
521 if (@$entries && !blessed
($entries->[0])) {
522 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
524 assert
{ !any
{ !blessed
$_ } @$entries };
531 return sum0
map { $_->size } @{$self->history};
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
;
543 # history is ordered oldest to newest
544 my $history = $self->history;
548 if (0 <= $max_items && $max_items < @$history) {
549 push @removed, splice @$history, -$max_items;
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;
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;
569 @removed = sort { $a->last_modification_time <=> $b->last_modification_time } @removed;
574 sub add_historical_entry
{
576 delete $_->{history
} for @_;
577 push @{$self->{history
} //= []}, map { $self->_wrap_entry($_) } @_;
581 sub remove_historical_entry
{
584 my $history = $self->history;
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;
598 my $parent = $self->group;
601 my $id = $self->uuid;
602 my $entry = first
{ $id eq $_->uuid } @{$parent->entries};
603 return $entry if $entry;
612 my $current = $self->current_entry;
613 return Hash
::Util
::FieldHash
::id
($self) == Hash
::Util
::FieldHash
::id
($current);
617 sub is_historical
{ !$_[0]->is_current }
622 my $current = $self->current_entry;
623 return $self if $current->remove_historical_entry($self);
624 $self->SUPER::remove
(@_);
627 ##############################################################################
630 sub searching_enabled
{
632 my $parent = $self->group;
633 return $parent->effective_enable_searching if $parent;
637 sub auto_type_enabled
{
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;
648 ##############################################################################
653 return $self->SUPER::_signal
("entry.$type", @_);
659 $self->add_historical_entry($orig);
661 $self->last_modification_time($time);
662 $self->last_access_time($time);
665 sub label
{ shift-
>expand_title(@_) }
667 ### Name of the parent attribute expected to contain the object
668 sub _parent_container
{ 'entries' }
680 File::KDBX::Entry - A KDBX database entry
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:
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.
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.
723 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>.
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>.
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}>.
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.
739 =head3 Entry Placeholders
745 ☑ C<{TITLE}> - B<Title> string
749 ☑ C<{USERNAME}> - B<UserName> string
753 ☑ C<{PASSWORD}> - B<Password> string
757 ☑ C<{NOTES}> - B<Notes> string
761 ☑ C<{URL}> - B<URL> string
765 ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
769 ☑ C<{URL:USERINFO}>
773 ☑ C<{URL:USERNAME}>
777 ☑ C<{URL:PASSWORD}>
797 ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
801 ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
805 ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
809 ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
813 ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
817 ☑ C<{TIMEOTP}> - Generate a time-based one-time password
821 ☑ C<{GROUP_NOTES}> - Notes of the parent group
825 ☑ C<{GROUP_PATH}> - Full path of the parent group
829 ☑ C<{GROUP}> - Name of the parent group
833 =head3 Field References
839 ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
843 =head3 File path Placeholders
849 ☑ C<{APPDIR}> - Program directory path
853 ☑ C<{FIREFOX}> - Path to the Firefox browser executable
857 ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
861 ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
865 ☑ C<{OPERA}> - Path to the Opera browser executable
869 ☑ C<{SAFARI}> - Path to the Safari browser executable
873 ☒ C<{DB_PATH}> - Full file path of the database
877 ☒ C<{DB_DIR}> - Directory path of the database
881 ☒ C<{DB_NAME}> - File name (including extension) of the database
885 ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
889 ☒ C<{DB_EXT}> - File name extension
893 ☑ C<{ENV_DIRSEP}> - Directory separator
897 ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
901 =head3 Date and Time Placeholders
907 ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
911 ☑ C<{DT_YEAR}> - Year component of the current local date
915 ☑ C<{DT_MONTH}> - Month component of the current local date
919 ☑ C<{DT_DAY}> - Day component of the current local date
923 ☑ C<{DT_HOUR}> - Hour component of the current local time
927 ☑ C<{DT_MINUTE}> - Minute component of the current local time
931 ☑ C<{DT_SECOND}> - Second component of the current local time
935 ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
939 ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
943 ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
947 ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
951 ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
955 ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
959 ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
963 If the current date and time is C<2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
965 =head3 Special Key Placeholders
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:
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}>
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}>
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}>
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}>
983 =head3 Miscellaneous Placeholders
993 ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
997 ☒ C<{BASE:USERINFO}>
1001 ☒ C<{BASE:USERNAME}>
1005 ☒ C<{BASE:PASSWORD}>
1025 ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1029 ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1033 ☒ C<{CLIPBOARD-SET:/Text/}>
1041 ☒ C<{CMD:/CommandLine/Options/}>
1045 ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1049 ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1053 ☒ C<{GROUP_SEL_NOTES}>
1057 ☒ C<{GROUP_SEL_PATH}>
1065 ☒ C<{NEWPASSWORD}>
1069 ☒ C<{NEWPASSWORD:/Profile/}>
1073 ☒ C<{PASSWORD_ENC}>
1081 ☒ C<{PICKCHARS:Field:Options}>
1089 ☒ C<{T-CONV:/Text/Type/}>
1093 ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
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:
1102 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
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.
1111 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1112 my ($entry, $arg) = @_; # ^ Notice the colon here
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>.
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):
1124 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1125 (undef, my $arg) = @_;
1126 return defined $arg ? rand($arg) : rand;
1129 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1132 %File::KDBX::PLACEHOLDERS = ();
1134 =head2 One-time Passwords
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
1144 L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1152 B<NOTE:> To use this feature, you must install the suggested dependency:
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.
1165 To configure TOTP in the KeePass 2 style, set the following strings:
1171 C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and C<HMAC-SHA-512>
1175 C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1179 C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1183 C<TimeOtp-Secret> - Text string secret, OR
1187 C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1191 C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1195 C<TimeOtp-Secret-Base64> - Base64-encoded secret
1199 To configure HOTP in the KeePass 2 style, set the following strings:
1205 C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp> is called
1209 C<HmacOtp-Secret> - Text string secret, OR
1213 C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1217 C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1221 C<HmacOtp-Secret-Base64> - Base64-encoded secret
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.
1228 Here's a basic example:
1230 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1232 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1234 my $otp = $entry->time_otp;
1238 =head2 foreground_color
1240 Text color represented as a string of the form C<#000000>.
1242 =head2 background_color
1244 Background color represented as a string of the form C<#FFFFFF>.
1250 =head2 auto_type_enabled
1252 Whether or not the entry is eligible to be matched for auto-typing.
1254 =head2 auto_type_obfuscation
1256 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1258 =head2 auto_type_default_sequence
1260 The default auto-type keystroke sequence.
1262 =head2 auto_type_associations
1264 An array of window title / keystroke sequence associations.
1267 window => 'Example Window Title',
1268 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1271 Keystroke sequences can have </Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1273 =head2 quality_check
1275 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1279 Hash with entry strings, including the standard strings as well as any custom ones.
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' },
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>.
1297 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1304 'mysecrets.txt' => {
1310 There are methods available to provide more convenient access to binaries, including L</binary> and
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.
1320 Alias for the B<Notes> string value.
1324 Alias for the B<Password> string value.
1328 Alias for the B<Title> string value.
1332 Alias for the B<URL> string value.
1336 Aliases for the B<UserName> string value.
1340 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
1342 =head2 expand_password
1344 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
1348 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
1352 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
1354 =head2 expand_username
1356 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
1362 \%string = $entry->string($string_key);
1364 $entry->string($string_key, \%string);
1365 $entry->string($string_key, %attributes);
1366 $entry->string($string_key, $value); # same as: value => $value
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:
1372 value => 'Password',
1373 protect => true, # optional
1376 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1383 C<protect> - Whether or not the string value should be memory-protected.
1389 $string = $entry->string_value($string_key);
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:
1395 my $s = $entry->string(...);
1396 defined $s ? $s->{value} : undef;
1399 =head2 expand_string_value
1401 $string = $entry->expand_string_value;
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.
1406 See L</Placeholders>.
1408 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1411 =head2 other_strings
1413 $other = $entry->other_strings;
1414 $other = $entry->other_strings($delimiter);
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).
1421 $string = $entry->string_peek($string_key);
1423 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1426 =head2 add_auto_type_association
1428 $entry->add_auto_type_association(\%association);
1430 Add a new auto-type association to an entry.
1432 =head2 expand_keystroke_sequence
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
1438 Get a keystroke sequence after placeholder expansion.
1442 \%binary = $entry->binary($binary_key);
1444 $entry->binary($binary_key, \%binary);
1445 $entry->binary($binary_key, %attributes);
1446 $entry->binary($binary_key, $value); # same as: value => $value
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:
1453 protect => true, # optional
1456 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1463 C<protect> - Whether or not the binary value should be memory-protected.
1469 $binary = $entry->binary_value($binary_key);
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:
1475 my $b = $entry->binary(...);
1476 defined $b ? $b->{value} : undef;
1481 $otp = $entry->hmac_otp(%options);
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:
1490 C<counter> - Specify the counter value
1494 To configure HOTP, see L</"One-time Passwords">.
1498 $otp = $entry->time_otp(%options);
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:
1507 C<now> - Specify the value for determining the time-step counter
1511 To configure TOTP, see L</"One-time Passwords">.
1517 $uri_string = $entry->hmac_otp_uri;
1518 $uri_string = $entry->time_otp_uri;
1520 Get a HOTP or TOTP otpauth URI for the entry, if available.
1522 To configure OTP, see L</"One-time Passwords">.
1526 $size = $entry->size;
1528 Get the size (in bytes) of an entry.
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.
1535 $size = $entry->history_size;
1537 Get the size (in bytes) of all historical entries combined.
1539 =head2 prune_history
1541 @removed_historical_entries = $entry->prune_history(%options);
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>:
1550 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1554 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1558 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1562 =head2 add_historical_entry
1564 $entry->add_historical_entry($entry);
1566 Add an entry to the history.
1568 =head2 remove_historical_entry
1570 $entry->remove_historical_entry($historical_entry);
1572 Remove an entry from the history.
1574 =head2 current_entry
1576 $current_entry = $entry->current_entry;
1578 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1582 $bool = $entry->is_current;
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.
1587 =head2 is_historical
1589 $bool = $entry->is_historical;
1591 Get whether or not an entry is considered historical (i.e. not current).
1593 This is just the inverse of L</is_current>.
1597 $entry = $entry->remove;
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>.
1602 =head2 searching_enabled
1604 $bool = $entry->searching_enabled;
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.
1609 Throws if entry has no parent group or if the entry is not connected to a database.
1611 =for Pod::Coverage auto_type times
1615 Please report any bugs or feature requests on the bugtracker website
1616 L<https://github.com/chazmcgarvey/File-KDBX/issues>
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
1624 Charles McGarvey <ccm@cpan.org>
1626 =head1 COPYRIGHT AND LICENSE
1628 This software is copyright (c) 2022 by Charles McGarvey.
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.