1 package File
::KDBX
::Entry
;
2 # ABSTRACT: A KDBX database entry
7 use Crypt
::Misc
0.049 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.902'; # 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>. View its documentation to see other attributes
724 and methods available on entries.
728 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
729 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
730 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
731 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
732 C<http://example.com?user=batman>.
734 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
735 brace, like C<{PLACEHOLDER:ARGUMENT}>.
737 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
738 This software supports many (but not all) of the placeholders documented there.
740 =head3 Entry Placeholders
746 ☑ C<{TITLE}> - B<Title> string
750 ☑ C<{USERNAME}> - B<UserName> string
754 ☑ C<{PASSWORD}> - B<Password> string
758 ☑ C<{NOTES}> - B<Notes> string
762 ☑ C<{URL}> - B<URL> string
766 ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
770 ☑ C<{URL:USERINFO}>
774 ☑ C<{URL:USERNAME}>
778 ☑ C<{URL:PASSWORD}>
798 ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
802 ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
806 ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
810 ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
814 ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
818 ☑ C<{TIMEOTP}> - Generate a time-based one-time password
822 ☑ C<{GROUP_NOTES}> - Notes of the parent group
826 ☑ C<{GROUP_PATH}> - Full path of the parent group
830 ☑ C<{GROUP}> - Name of the parent group
834 =head3 Field References
840 ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
844 =head3 File path Placeholders
850 ☑ C<{APPDIR}> - Program directory path
854 ☑ C<{FIREFOX}> - Path to the Firefox browser executable
858 ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
862 ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
866 ☑ C<{OPERA}> - Path to the Opera browser executable
870 ☑ C<{SAFARI}> - Path to the Safari browser executable
874 ☒ C<{DB_PATH}> - Full file path of the database
878 ☒ C<{DB_DIR}> - Directory path of the database
882 ☒ C<{DB_NAME}> - File name (including extension) of the database
886 ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
890 ☒ C<{DB_EXT}> - File name extension
894 ☑ C<{ENV_DIRSEP}> - Directory separator
898 ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
902 =head3 Date and Time Placeholders
908 ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
912 ☑ C<{DT_YEAR}> - Year component of the current local date
916 ☑ C<{DT_MONTH}> - Month component of the current local date
920 ☑ C<{DT_DAY}> - Day component of the current local date
924 ☑ C<{DT_HOUR}> - Hour component of the current local time
928 ☑ C<{DT_MINUTE}> - Minute component of the current local time
932 ☑ C<{DT_SECOND}> - Second component of the current local time
936 ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
940 ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
944 ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
948 ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
952 ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
956 ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
960 ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
964 If the current date and time is C<2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
966 =head3 Special Key Placeholders
968 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
969 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
970 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
972 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
973 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
975 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
976 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
978 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
979 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
981 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
982 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
984 =head3 Miscellaneous Placeholders
994 ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
998 ☒ C<{BASE:USERINFO}>
1002 ☒ C<{BASE:USERNAME}>
1006 ☒ C<{BASE:PASSWORD}>
1026 ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1030 ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1034 ☒ C<{CLIPBOARD-SET:/Text/}>
1042 ☒ C<{CMD:/CommandLine/Options/}>
1046 ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1050 ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1054 ☒ C<{GROUP_SEL_NOTES}>
1058 ☒ C<{GROUP_SEL_PATH}>
1066 ☒ C<{NEWPASSWORD}>
1070 ☒ C<{NEWPASSWORD:/Profile/}>
1074 ☒ C<{PASSWORD_ENC}>
1082 ☒ C<{PICKCHARS:Field:Options}>
1090 ☒ C<{T-CONV:/Text/Type/}>
1094 ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1098 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1099 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1100 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1101 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1103 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1108 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1109 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1110 strings or auto-type key sequences.
1112 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1113 my ($entry, $arg) = @_; # ^ Notice the colon here
1117 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1118 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1119 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1121 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1122 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1123 both with and without a colon (or they could be different subroutines):
1125 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1126 (undef, my $arg) = @_;
1127 return defined $arg ? rand($arg) : rand;
1130 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1133 %File::KDBX::PLACEHOLDERS = ();
1135 =head2 One-time Passwords
1137 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1138 configuration storage isn't completely standardized, but this module supports two predominant configuration
1145 L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1153 B<NOTE:> To use this feature, you must install the suggested dependency:
1163 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1164 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1166 To configure TOTP in the KeePass 2 style, set the following strings:
1172 C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and C<HMAC-SHA-512>
1176 C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1180 C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1184 C<TimeOtp-Secret> - Text string secret, OR
1188 C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1192 C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1196 C<TimeOtp-Secret-Base64> - Base64-encoded secret
1200 To configure HOTP in the KeePass 2 style, set the following strings:
1206 C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp> is called
1210 C<HmacOtp-Secret> - Text string secret, OR
1214 C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1218 C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1222 C<HmacOtp-Secret-Base64> - Base64-encoded secret
1226 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1227 these should actually be set or an error will be thrown.
1229 Here's a basic example:
1231 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1233 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1235 my $otp = $entry->time_otp;
1239 =head2 foreground_color
1241 Text color represented as a string of the form C<#000000>.
1243 =head2 background_color
1245 Background color represented as a string of the form C<#FFFFFF>.
1251 =head2 auto_type_enabled
1253 Whether or not the entry is eligible to be matched for auto-typing.
1255 =head2 auto_type_obfuscation
1257 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1259 =head2 auto_type_default_sequence
1261 The default auto-type keystroke sequence.
1263 =head2 auto_type_associations
1265 An array of window title / keystroke sequence associations.
1268 window => 'Example Window Title',
1269 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1272 Keystroke sequences can have </Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1274 =head2 quality_check
1276 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1280 Hash with entry strings, including the standard strings as well as any custom ones.
1283 # Every entry has these five strings:
1284 Title => { value => 'Example Entry' },
1285 UserName => { value => 'jdoe' },
1286 Password => { value => 's3cr3t', protect => true },
1287 URL => { value => 'https://example.com' }
1288 Notes => { value => '' },
1289 # May also have custom strings:
1290 MySystem => { value => 'The mainframe' },
1293 There are methods available to provide more convenient access to strings, including L</string>,
1294 L</string_value>, L</expand_string_value> and L</string_peek>.
1298 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1305 'mysecrets.txt' => {
1311 There are methods available to provide more convenient access to binaries, including L</binary> and
1316 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
1317 same UUID with the current entry.
1321 Alias for the B<Notes> string value.
1325 Alias for the B<Password> string value.
1329 Alias for the B<Title> string value.
1333 Alias for the B<URL> string value.
1337 Aliases for the B<UserName> string value.
1341 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
1343 =head2 expand_password
1345 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
1349 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
1353 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
1355 =head2 expand_username
1357 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
1363 \%string = $entry->string($string_key);
1365 $entry->string($string_key, \%string);
1366 $entry->string($string_key, %attributes);
1367 $entry->string($string_key, $value); # same as: value => $value
1369 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
1370 structure. For example:
1373 value => 'Password',
1374 protect => true, # optional
1377 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1384 C<protect> - Whether or not the string value should be memory-protected.
1390 $string = $entry->string_value($string_key);
1392 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
1393 is not set or is currently memory-protected. This is just a shortcut for:
1396 my $s = $entry->string(...);
1397 defined $s ? $s->{value} : undef;
1400 =head2 expand_string_value
1402 $string = $entry->expand_string_value;
1404 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
1405 do not expand to values are left as-is.
1407 See L</Placeholders>.
1409 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1412 =head2 other_strings
1414 $other = $entry->other_strings;
1415 $other = $entry->other_strings($delimiter);
1417 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
1418 for executing queries to search for entities based on the contents of these other strings (if any).
1422 $string = $entry->string_peek($string_key);
1424 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1427 =head2 add_auto_type_association
1429 $entry->add_auto_type_association(\%association);
1431 Add a new auto-type association to an entry.
1433 =head2 expand_keystroke_sequence
1435 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
1436 $string = $entry->expand_keystroke_sequence(\%association);
1437 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
1439 Get a keystroke sequence after placeholder expansion.
1443 \%binary = $entry->binary($binary_key);
1445 $entry->binary($binary_key, \%binary);
1446 $entry->binary($binary_key, %attributes);
1447 $entry->binary($binary_key, $value); # same as: value => $value
1449 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
1450 structure. For example:
1454 protect => true, # optional
1457 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1464 C<protect> - Whether or not the binary value should be memory-protected.
1470 $binary = $entry->binary_value($binary_key);
1472 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
1473 is not set or is currently memory-protected. This is just a shortcut for:
1476 my $b = $entry->binary(...);
1477 defined $b ? $b->{value} : undef;
1482 $otp = $entry->hmac_otp(%options);
1484 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
1485 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1491 C<counter> - Specify the counter value
1495 To configure HOTP, see L</"One-time Passwords">.
1499 $otp = $entry->time_otp(%options);
1501 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
1502 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1508 C<now> - Specify the value for determining the time-step counter
1512 To configure TOTP, see L</"One-time Passwords">.
1518 $uri_string = $entry->hmac_otp_uri;
1519 $uri_string = $entry->time_otp_uri;
1521 Get a HOTP or TOTP otpauth URI for the entry, if available.
1523 To configure OTP, see L</"One-time Passwords">.
1527 $size = $entry->size;
1529 Get the size (in bytes) of an entry.
1531 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
1532 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
1536 $size = $entry->history_size;
1538 Get the size (in bytes) of all historical entries combined.
1540 =head2 prune_history
1542 @removed_historical_entries = $entry->prune_history(%options);
1544 Remove just as many older historical entries as necessary to get under the database limits. The limits are
1545 taken from the connected database (if any) or can be overridden with C<%options>:
1551 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1555 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1559 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1563 =head2 add_historical_entry
1565 $entry->add_historical_entry($entry);
1567 Add an entry to the history.
1569 =head2 remove_historical_entry
1571 $entry->remove_historical_entry($historical_entry);
1573 Remove an entry from the history.
1575 =head2 current_entry
1577 $current_entry = $entry->current_entry;
1579 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1583 $bool = $entry->is_current;
1585 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
1586 in the parent group's entry list.
1588 =head2 is_historical
1590 $bool = $entry->is_historical;
1592 Get whether or not an entry is considered historical (i.e. not current).
1594 This is just the inverse of L</is_current>.
1598 $entry = $entry->remove;
1600 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1601 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1603 =head2 searching_enabled
1605 $bool = $entry->searching_enabled;
1607 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1608 L<File::KDBX::Group/effective_enable_searching> value.
1610 Throws if entry has no parent group or if the entry is not connected to a database.
1612 =for Pod::Coverage auto_type times
1616 Please report any bugs or feature requests on the bugtracker website
1617 L<https://github.com/chazmcgarvey/File-KDBX/issues>
1619 When submitting a bug or request, please include a test-file or a
1620 patch to an existing test-file that illustrates the bug or desired
1625 Charles McGarvey <ccm@cpan.org>
1627 =head1 COPYRIGHT AND LICENSE
1629 This software is copyright (c) 2022 by Charles McGarvey.
1631 This is free software; you can redistribute it and/or modify it under
1632 the same terms as the Perl 5 programming language system itself.