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(any first sum0);
15 use Ref
::Util
qw(is_coderef is_hashref is_plain_hashref);
16 use Scalar
::Util
qw(blessed looks_like_number);
17 use Storable
qw(dclone);
22 extends
'File::KDBX::Object';
24 our $VERSION = '0.906'; # 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 # Auto-vivify the standard strings.
133 if (!exists $self->{strings
}{$key} && $STANDARD_STRINGS{$key}) {
135 $args{protect
} //= true
if $self->_protect($key);
138 while (my ($field, $value) = each %args) {
139 $self->{strings
}{$key}{$field} = $value;
142 return $self->{strings
}{$key};
145 ### Get whether or not a standard string is configured to be protected
149 return false
if !$STANDARD_STRINGS{$key};
150 if (my $kdbx = eval { $self->kdbx }) {
151 my $protect = $kdbx->memory_protection($key);
152 return $protect if defined $protect;
154 return $key eq 'Password';
160 my $string = $self->string(@_) // return undef;
161 return $string->{value
};
165 sub _expand_placeholder
{
167 my $placeholder = shift;
172 my $placeholder_key = $placeholder;
174 $placeholder_key = $File::KDBX
::PLACEHOLDERS
{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
177 return if !defined $File::KDBX
::PLACEHOLDERS
{$placeholder_key};
179 my $local_key = join('/', Hash
::Util
::FieldHash
::id
($self), $placeholder_key);
180 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
181 my $handler = $File::KDBX
::PLACEHOLDERS
{$placeholder_key} or next;
182 memoize recurse_limit
($handler, $PLACEHOLDER_MAX_DEPTH, sub {
183 alert
"Detected deep recursion while expanding $placeholder placeholder",
184 placeholder
=> $placeholder;
189 return $handler->($self, $arg, $placeholder);
196 my $expand = memoize
$self->can('_expand_placeholder'), $self;
198 # placeholders (including field references):
199 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
201 # environment variables (alt syntax):
202 my $vars = join('|', map { quotemeta($_) } keys %ENV);
203 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
208 sub expand_string_value
{
210 my $str = $self->string_peek(@_) // return undef;
211 my $cleanup = erase_scoped
$str;
212 return $self->_expand_string($str);
218 my $delim = shift // "\n";
220 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
221 return join($delim, @strings);
227 my $string = $self->string(@_);
228 return defined $string->{value
} ? $string->{value
} : $self->kdbx->peek($string);
231 ##############################################################################
234 sub add_auto_type_association
{
236 my $association = shift;
237 push @{$self->auto_type_associations}, $association;
241 sub expand_keystroke_sequence
{
243 my $association = shift;
247 $keys = is_hashref
($association) && exists $association->{keystroke_sequence
} ?
248 $association->{keystroke_sequence
} : defined $association ? $association : '';
251 $keys = $self->auto_type_default_sequence if !$keys;
252 # TODO - Fall back to getting default sequence from parent group, which probably means we shouldn't be
253 # setting a default value in the entry..
255 return $self->_expand_string($keys);
258 ##############################################################################
263 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
264 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
266 if (!defined $args{key
} && !defined $args{value
}) {
267 my %standard = (value
=> 1, protect
=> 1);
268 my @other_keys = grep { !$standard{$_} } keys %args;
269 if (@other_keys == 1) {
270 my $key = $args{key
} = $other_keys[0];
271 $args{value
} = delete $args{$key};
275 my $key = delete $args{key
} or throw
'Must provide a binary key to access';
277 return $self->{binaries
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
279 assert
{ !defined $args{value
} || !utf8
::is_utf8
($args{value
}) };
280 while (my ($field, $value) = each %args) {
281 $self->{binaries
}{$key}{$field} = $value;
283 return $self->{binaries
}{$key};
289 my $binary = $self->binary(@_) // return undef;
290 return $binary->{value
};
293 ##############################################################################
298 load_optional
('Pass::OTP');
300 my %params = ($self->_hotp_params, @_);
301 return if !defined $params{type
} || !defined $params{secret
};
303 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
306 my $otp = eval { Pass
::OTP
::otp
(%params, @_) };
308 throw
'Unable to generate HOTP', error
=> $err;
311 $self->_hotp_increment_counter($params{counter
});
319 load_optional
('Pass::OTP');
321 my %params = ($self->_totp_params, @_);
322 return if !defined $params{type
} || !defined $params{secret
};
324 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
327 my $otp = eval { Pass
::OTP
::otp
(%params, @_) };
329 throw
'Unable to generate TOTP', error
=> $err;
336 sub hmac_otp_uri
{ $_[0]->_otp_uri($_[0]->_hotp_params) }
337 sub time_otp_uri
{ $_[0]->_otp_uri($_[0]->_totp_params) }
343 return if 4 != grep { defined } @params{qw(type secret issuer account)};
344 return if $params{type
} !~ /^[ht]otp$/i;
346 my $label = delete $params{label
};
347 $params{$_} = uri_escape_utf8
($params{$_}) for keys %params;
349 my $type = lc($params{type
});
350 my $issuer = $params{issuer
};
351 my $account = $params{account
};
353 $label //= "$issuer:$account";
355 my $secret = $params{secret
};
356 $secret = uc(encode_b32r
($secret)) if !$params{base32
};
358 delete $params{algorithm
} if defined $params{algorithm
} && $params{algorithm
} eq 'sha1';
359 delete $params{period
} if defined $params{period
} && $params{period
} == 30;
360 delete $params{digits
} if defined $params{digits
} && $params{digits
} == 6;
361 delete $params{counter
} if defined $params{counter
} && $params{counter
} == 0;
363 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
365 if (defined $params{encoder
}) {
366 $uri .= "&encoder=$params{encoder}";
369 $uri .= '&algorithm=' . uc($params{algorithm
}) if defined $params{algorithm
};
370 $uri .= "&digits=$params{digits}" if defined $params{digits
};
371 $uri .= "&counter=$params{counter}" if defined $params{counter
};
372 $uri .= "&period=$params{period}" if defined $params{period
};
382 issuer
=> $self->expand_title || 'KDBX',
383 account
=> $self->expand_username || 'none',
385 counter
=> $self->string_value('HmacOtp-Counter') // 0,
386 $self->_otp_secret_params('Hmac'),
388 return %params if $params{secret
};
390 my %otp_params = $self->_otp_params;
391 return () if !$otp_params{secret
} || $otp_params{type
} ne 'hotp';
393 # $otp_params{counter} = 0
395 return (%params, %otp_params);
402 'HMAC-SHA-1' => 'sha1',
403 'HMAC-SHA-256' => 'sha256',
404 'HMAC-SHA-512' => 'sha512',
408 issuer
=> $self->expand_title || 'KDBX',
409 account
=> $self->expand_username || 'none',
410 digits
=> $self->string_value('TimeOtp-Length') // 6,
411 algorithm
=> $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
412 period
=> $self->string_value('TimeOtp-Period') // 30,
413 $self->_otp_secret_params('Time'),
415 return %params if $params{secret
};
417 my %otp_params = $self->_otp_params;
418 return () if !$otp_params{secret
} || $otp_params{type
} ne 'totp';
420 return (%params, %otp_params);
426 load_optional
('Pass::OTP::URI');
428 my $uri = $self->string_value('otp') || '';
430 %params = Pass
::OTP
::URI
::parse
($uri) if $uri =~ m!^otpauth://!;
431 return () if !$params{secret
} || !$params{type
};
433 if (($params{encoder
} // '') eq 'steam') {
435 $params{chars
} = '23456789BCDFGHJKMNPQRTVWXY';
438 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
439 my ($issuer, $user) = split(':', $params{label
} // ':', 2);
440 $params{issuer
} //= uri_unescape_utf8
($issuer);
441 $params{account
} //= uri_unescape_utf8
($user);
443 $params{algorithm
} = lc($params{algorithm
}) if $params{algorithm
};
444 $params{counter
} = $self->string_value('HmacOtp-Counter') if $params{type
} eq 'hotp';
449 sub _otp_secret_params
{
451 my $type = shift // return ();
453 my $secret_txt = $self->string_value("${type}Otp-Secret");
454 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
455 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
456 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
458 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
459 return () if $count == 0;
460 alert
"Found multiple ${type}Otp-Secret strings", count
=> $count if 1 < $count;
462 return (secret
=> $secret_b32, base32
=> 1) if defined $secret_b32;
463 return (secret
=> decode_b64
($secret_b64)) if defined $secret_b64;
464 return (secret
=> pack('H*', $secret_hex)) if defined $secret_hex;
465 return (secret
=> encode
('UTF-8', $secret_txt));
468 sub _hotp_increment_counter
{
470 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
472 looks_like_number
($counter) or throw
'HmacOtp-Counter value must be a number', value
=> $counter;
473 my $next = $counter + 1;
474 $self->string('HmacOtp-Counter', $next);
478 ##############################################################################
487 $size += length(encode
('UTF-8', $self->tags // ''));
489 # attributes (strings)
490 while (my ($key, $string) = each %{$self->strings}) {
491 next if !defined $string->{value
};
492 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $string->{value
} // ''));
496 while (my ($key, $item) = each %{$self->custom_data}) {
497 next if !defined $item->{value
};
498 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $item->{value
} // ''));
502 while (my ($key, $binary) = each %{$self->binaries}) {
503 next if !defined $binary->{value
};
504 my $value_len = utf8
::is_utf8
($binary->{value
}) ? length(encode
('UTF-8', $binary->{value
}))
505 : length($binary->{value
});
506 $size += length(encode
('UTF-8', $key)) + $value_len;
509 # autotype associations
510 for my $association (@{$self->auto_type->{associations
} || []}) {
511 $size += length(encode
('UTF-8', $association->{window
}))
512 + length(encode
('UTF-8', $association->{keystroke_sequence
} // ''));
518 ##############################################################################
522 my $entries = $self->{history
} //= [];
523 if (@$entries && !blessed
($entries->[0])) {
524 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
526 assert
{ !any
{ !blessed
$_ } @$entries };
533 return sum0
map { $_->size } @{$self->history};
541 my $max_items = $args{max_items
} // eval { $self->kdbx->history_max_items } // HISTORY_DEFAULT_MAX_ITEMS
;
542 my $max_size = $args{max_size
} // eval { $self->kdbx->history_max_size } // HISTORY_DEFAULT_MAX_SIZE
;
543 my $max_age = $args{max_age
} // eval { $self->kdbx->maintenance_history_days } // HISTORY_DEFAULT_MAX_AGE
;
545 # history is ordered oldest to newest
546 my $history = $self->history;
550 if (0 <= $max_items && $max_items < @$history) {
551 push @removed, splice @$history, -$max_items;
554 if (0 <= $max_size) {
555 my $current_size = $self->history_size;
556 while ($max_size < $current_size) {
557 push @removed, my $entry = shift @$history;
558 $current_size -= $entry->size;
563 my $cutoff = gmtime - ($max_age * 86400);
564 for (my $i = @$history - 1; 0 <= $i; --$i) {
565 my $entry = $history->[$i];
566 next if $cutoff <= $entry->last_modification_time;
567 push @removed, splice @$history, $i, 1;
571 @removed = sort { $a->last_modification_time <=> $b->last_modification_time } @removed;
576 sub add_historical_entry
{
578 delete $_->{history
} for @_;
579 push @{$self->{history
} //= []}, map { $self->_wrap_entry($_) } @_;
583 sub remove_historical_entry
{
586 my $history = $self->history;
589 for (my $i = @$history - 1; 0 <= $i; --$i) {
590 my $item = $history->[$i];
591 next if Hash
::Util
::FieldHash
::id
($entry) != Hash
::Util
::FieldHash
::id
($item);
592 push @removed, splice @{$self->{history
}}, $i, 1;
600 my $parent = $self->group;
603 my $id = $self->uuid;
604 my $entry = first
{ $id eq $_->uuid } @{$parent->entries};
605 return $entry if $entry;
614 my $current = $self->current_entry;
615 return Hash
::Util
::FieldHash
::id
($self) == Hash
::Util
::FieldHash
::id
($current);
619 sub is_historical
{ !$_[0]->is_current }
624 my $current = $self->current_entry;
625 return $self if $current->remove_historical_entry($self);
626 $self->SUPER::remove
(@_);
629 ##############################################################################
632 sub searching_enabled
{
634 my $parent = $self->group;
635 return $parent->effective_enable_searching if $parent;
639 sub auto_type_enabled
{
641 $self->auto_type->{enabled
} = to_bool
(shift) if @_;
642 $self->auto_type->{enabled
} //= true
;
643 return false
if !$self->auto_type->{enabled
};
644 return true
if !$self->is_connected;
645 my $parent = $self->group;
646 return $parent->effective_enable_auto_type if $parent;
650 ##############################################################################
655 return $self->SUPER::_signal
("entry.$type", @_);
661 $self->add_historical_entry($orig);
663 $self->last_modification_time($time);
664 $self->last_access_time($time);
667 sub label
{ shift-
>expand_title(@_) }
669 ### Name of the parent attribute expected to contain the object
670 sub _parent_container
{ 'entries' }
682 File::KDBX::Entry - A KDBX database entry
690 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
691 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
692 that every entry has:
718 Beyond this, you can store any number of other strings and any number of binaries that you can use for
719 whatever purpose you want.
721 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
722 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
723 the attributes to see what's available.
725 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>. View its documentation to see other attributes
726 and methods available on entries.
730 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
731 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
732 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
733 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
734 C<http://example.com?user=batman>.
736 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
737 brace, like C<{PLACEHOLDER:ARGUMENT}>.
739 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
740 This software supports many (but not all) of the placeholders documented there.
742 =head3 Entry Placeholders
748 ☑ C<{TITLE}> - B<Title> string
752 ☑ C<{USERNAME}> - B<UserName> string
756 ☑ C<{PASSWORD}> - B<Password> string
760 ☑ C<{NOTES}> - B<Notes> string
764 ☑ C<{URL}> - B<URL> string
768 ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
772 ☑ C<{URL:USERINFO}>
776 ☑ C<{URL:USERNAME}>
780 ☑ C<{URL:PASSWORD}>
800 ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
804 ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
808 ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
812 ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
816 ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
820 ☑ C<{TIMEOTP}> - Generate a time-based one-time password
824 ☑ C<{GROUP_NOTES}> - Notes of the parent group
828 ☑ C<{GROUP_PATH}> - Full path of the parent group
832 ☑ C<{GROUP}> - Name of the parent group
836 =head3 Field References
842 ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
846 =head3 File path Placeholders
852 ☑ C<{APPDIR}> - Program directory path
856 ☑ C<{FIREFOX}> - Path to the Firefox browser executable
860 ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
864 ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
868 ☑ C<{OPERA}> - Path to the Opera browser executable
872 ☑ C<{SAFARI}> - Path to the Safari browser executable
876 ☒ C<{DB_PATH}> - Full file path of the database
880 ☒ C<{DB_DIR}> - Directory path of the database
884 ☒ C<{DB_NAME}> - File name (including extension) of the database
888 ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
892 ☒ C<{DB_EXT}> - File name extension
896 ☑ C<{ENV_DIRSEP}> - Directory separator
900 ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
904 =head3 Date and Time Placeholders
910 ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
914 ☑ C<{DT_YEAR}> - Year component of the current local date
918 ☑ C<{DT_MONTH}> - Month component of the current local date
922 ☑ C<{DT_DAY}> - Day component of the current local date
926 ☑ C<{DT_HOUR}> - Hour component of the current local time
930 ☑ C<{DT_MINUTE}> - Minute component of the current local time
934 ☑ C<{DT_SECOND}> - Second component of the current local time
938 ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
942 ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
946 ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
950 ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
954 ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
958 ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
962 ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
966 If the current date and time is C<2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
968 =head3 Special Key Placeholders
970 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
971 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
972 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
974 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
975 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
977 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
978 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
980 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
981 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
983 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
984 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
986 =head3 Miscellaneous Placeholders
996 ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
1000 ☒ C<{BASE:USERINFO}>
1004 ☒ C<{BASE:USERNAME}>
1008 ☒ C<{BASE:PASSWORD}>
1028 ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1032 ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1036 ☒ C<{CLIPBOARD-SET:/Text/}>
1044 ☒ C<{CMD:/CommandLine/Options/}>
1048 ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1052 ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1056 ☒ C<{GROUP_SEL_NOTES}>
1060 ☒ C<{GROUP_SEL_PATH}>
1068 ☒ C<{NEWPASSWORD}>
1072 ☒ C<{NEWPASSWORD:/Profile/}>
1076 ☒ C<{PASSWORD_ENC}>
1084 ☒ C<{PICKCHARS:Field:Options}>
1092 ☒ C<{T-CONV:/Text/Type/}>
1096 ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1100 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1101 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1102 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1103 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1105 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1110 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1111 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1112 strings or auto-type key sequences.
1114 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1115 my ($entry, $arg) = @_; # ^ Notice the colon here
1119 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1120 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1121 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1123 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1124 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1125 both with and without a colon (or they could be different subroutines):
1127 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1128 (undef, my $arg) = @_;
1129 return defined $arg ? rand($arg) : rand;
1132 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1135 %File::KDBX::PLACEHOLDERS = ();
1137 =head2 One-time Passwords
1139 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1140 configuration storage isn't completely standardized, but this module supports two predominant configuration
1147 L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1155 B<NOTE:> To use this feature, you must install the suggested dependency:
1165 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1166 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1168 To configure TOTP in the KeePass 2 style, set the following strings:
1174 C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and C<HMAC-SHA-512>
1178 C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1182 C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1186 C<TimeOtp-Secret> - Text string secret, OR
1190 C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1194 C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1198 C<TimeOtp-Secret-Base64> - Base64-encoded secret
1202 To configure HOTP in the KeePass 2 style, set the following strings:
1208 C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp> is called
1212 C<HmacOtp-Secret> - Text string secret, OR
1216 C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1220 C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1224 C<HmacOtp-Secret-Base64> - Base64-encoded secret
1228 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1229 these should actually be set or an error will be thrown.
1231 Here's a basic example:
1233 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1235 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1237 my $otp = $entry->time_otp;
1241 =head2 foreground_color
1243 Text color represented as a string of the form C<#000000>.
1245 =head2 background_color
1247 Background color represented as a string of the form C<#FFFFFF>.
1253 =head2 auto_type_enabled
1255 Whether or not the entry is eligible to be matched for auto-typing.
1257 =head2 auto_type_obfuscation
1259 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1261 =head2 auto_type_default_sequence
1263 The default auto-type keystroke sequence.
1265 =head2 auto_type_associations
1267 An array of window title / keystroke sequence associations.
1270 window => 'Example Window Title',
1271 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1274 Keystroke sequences can have L</Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1276 =head2 quality_check
1278 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1282 Hash with entry strings, including the standard strings as well as any custom ones.
1285 # Every entry has these five strings:
1286 Title => { value => 'Example Entry' },
1287 UserName => { value => 'jdoe' },
1288 Password => { value => 's3cr3t', protect => true },
1289 URL => { value => 'https://example.com' }
1290 Notes => { value => '' },
1291 # May also have custom strings:
1292 MySystem => { value => 'The mainframe' },
1295 There are methods available to provide more convenient access to strings, including L</string>,
1296 L</string_value>, L</expand_string_value> and L</string_peek>.
1300 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1307 'mysecrets.txt' => {
1313 There are methods available to provide more convenient access to binaries, including L</binary> and
1318 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
1319 same UUID with the current entry.
1323 Alias for the B<Notes> string value.
1327 Alias for the B<Password> string value.
1331 Alias for the B<Title> string value.
1335 Alias for the B<URL> string value.
1339 Aliases for the B<UserName> string value.
1345 \%string = $entry->string($string_key);
1347 $entry->string($string_key, \%string);
1348 $entry->string($string_key, %attributes);
1349 $entry->string($string_key, $value); # same as: value => $value
1351 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
1352 structure. For example:
1355 value => 'Password',
1356 protect => true, # optional
1359 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1366 C<protect> - Whether or not the string value should be memory-protected.
1372 $string = $entry->string_value($string_key);
1374 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
1375 is not set or is currently memory-protected. This is just a shortcut for:
1378 my $s = $entry->string(...);
1379 defined $s ? $s->{value} : undef;
1382 =head2 expand_string_value
1384 $string = $entry->expand_string_value($string_key);
1386 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
1387 do not expand to values are left as-is.
1389 See L</Placeholders>.
1391 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1396 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
1398 =head2 expand_password
1400 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
1404 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
1408 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
1410 =head2 expand_username
1412 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
1414 =head2 other_strings
1416 $other = $entry->other_strings;
1417 $other = $entry->other_strings($delimiter);
1419 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
1420 for executing queries to search for entities based on the contents of these other strings (if any).
1424 $string = $entry->string_peek($string_key);
1426 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1429 =head2 add_auto_type_association
1431 $entry->add_auto_type_association(\%association);
1433 Add a new auto-type association to an entry.
1435 =head2 expand_keystroke_sequence
1437 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
1438 $string = $entry->expand_keystroke_sequence(\%association);
1439 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
1441 Get a keystroke sequence after placeholder expansion.
1445 \%binary = $entry->binary($binary_key);
1447 $entry->binary($binary_key, \%binary);
1448 $entry->binary($binary_key, %attributes);
1449 $entry->binary($binary_key, $value); # same as: value => $value
1451 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
1452 structure. For example:
1456 protect => true, # optional
1459 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1466 C<protect> - Whether or not the binary value should be memory-protected.
1472 $binary = $entry->binary_value($binary_key);
1474 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
1475 is not set or is currently memory-protected. This is just a shortcut for:
1478 my $b = $entry->binary(...);
1479 defined $b ? $b->{value} : undef;
1484 $otp = $entry->hmac_otp(%options);
1486 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
1487 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1493 C<counter> - Specify the counter value
1497 To configure HOTP, see L</"One-time Passwords">.
1501 $otp = $entry->time_otp(%options);
1503 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
1504 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1510 C<now> - Specify the value for determining the time-step counter
1514 To configure TOTP, see L</"One-time Passwords">.
1520 $uri_string = $entry->hmac_otp_uri;
1521 $uri_string = $entry->time_otp_uri;
1523 Get a HOTP or TOTP otpauth URI for the entry, if available.
1525 To configure OTP, see L</"One-time Passwords">.
1529 $size = $entry->size;
1531 Get the size (in bytes) of an entry.
1533 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
1534 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
1538 $size = $entry->history_size;
1540 Get the size (in bytes) of all historical entries combined.
1542 =head2 prune_history
1544 @removed_historical_entries = $entry->prune_history(%options);
1546 Remove just as many older historical entries as necessary to get under the database limits. The limits are
1547 taken from the connected database (if any) or can be overridden with C<%options>:
1553 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1557 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1561 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1565 =head2 add_historical_entry
1567 $entry->add_historical_entry($entry);
1569 Add an entry to the history.
1571 =head2 remove_historical_entry
1573 $entry->remove_historical_entry($historical_entry);
1575 Remove an entry from the history.
1577 =head2 current_entry
1579 $current_entry = $entry->current_entry;
1581 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1585 $bool = $entry->is_current;
1587 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
1588 in the parent group's entry list.
1590 =head2 is_historical
1592 $bool = $entry->is_historical;
1594 Get whether or not an entry is considered historical (i.e. not current).
1596 This is just the inverse of L</is_current>.
1600 $entry = $entry->remove;
1602 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1603 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1605 =head2 searching_enabled
1607 $bool = $entry->searching_enabled;
1609 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1610 L<File::KDBX::Group/effective_enable_searching> value.
1612 Throws if entry has no parent group or if the entry is not connected to a database.
1614 =for Pod::Coverage auto_type times
1618 Please report any bugs or feature requests on the bugtracker website
1619 L<https://github.com/chazmcgarvey/File-KDBX/issues>
1621 When submitting a bug or request, please include a test-file or a
1622 patch to an existing test-file that illustrates the bug or desired
1627 Charles McGarvey <ccm@cpan.org>
1629 =head1 COPYRIGHT AND LICENSE
1631 This software is copyright (c) 2022 by Charles McGarvey.
1633 This is free software; you can redistribute it and/or modify it under
1634 the same terms as the Perl 5 programming language system itself.