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.800'; # 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
} // 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 <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-complete 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;
1240 128-bit UUID identifying the entry within the database.
1244 Integer representing a default icon. See L<File::KDBX::Constants/":icon"> for valid values.
1246 =head2 custom_icon_uuid
1248 128-bit UUID identifying a custom icon within the database.
1250 =head2 foreground_color
1252 Text color represented as a string of the form C<#000000>.
1254 =head2 background_color
1256 Background color represented as a string of the form C<#FFFFFF>.
1264 Text string with arbitrary tags which can be used to build a taxonomy.
1266 =head2 auto_type_enabled
1268 Whether or not the entry is eligible to be matched for auto-typing.
1270 =head2 auto_type_obfuscation
1272 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
1274 =head2 auto_type_default_sequence
1276 The default auto-type keystroke sequence.
1278 =head2 auto_type_associations
1280 An array of window title / keystroke sequence associations.
1283 window => 'Example Window Title',
1284 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
1287 Keystroke sequences can have </Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
1289 =head2 previous_parent_group
1291 128-bit UUID identifying a group within the database.
1293 =head2 quality_check
1295 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
1299 Hash with entry strings, including the standard strings as well as any custom ones.
1302 # Every entry has these five strings:
1303 Title => { value => 'Example Entry' },
1304 UserName => { value => 'jdoe' },
1305 Password => { value => 's3cr3t', protect => true },
1306 URL => { value => 'https://example.com' }
1307 Notes => { value => '' },
1308 # May also have custom strings:
1309 MySystem => { value => 'The mainframe' },
1312 There are methods available to provide more convenient access to strings, including L</string>,
1313 L</string_value>, L</expand_string_value> and L</string_peek>.
1317 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
1324 'mysecrets.txt' => {
1330 There are methods available to provide more convenient access to binaries, including L</binary> and
1335 A set of key-value pairs used to store arbitrary data, usually used by software to keep track of state rather
1336 than by end users (who typically work with the strings and binaries).
1340 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
1341 same UUID with the current entry.
1343 =head2 last_modification_time
1345 Date and time when the entry was last modified.
1347 =head2 creation_time
1349 Date and time when the entry was created.
1351 =head2 last_access_time
1353 Date and time when the entry was last accessed.
1357 Date and time when the entry expired or will expire.
1361 Boolean value indicating whether or not an entry is expired.
1365 The number of times an entry has been used, which typically means how many times the B<Password> string has
1368 =head2 location_changed
1370 Date and time when the entry was last moved to a different parent group.
1374 Alias for the B<Notes> string value.
1378 Alias for the B<Password> string value.
1382 Alias for the B<Title> string value.
1386 Alias for the B<URL> string value.
1390 Aliases for the B<UserName> string value.
1394 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
1396 =head2 expand_password
1398 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
1402 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
1406 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
1408 =head2 expand_username
1410 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
1416 \%string = $entry->string($string_key);
1418 $entry->string($string_key, \%string);
1419 $entry->string($string_key, %attributes);
1420 $entry->string($string_key, $value); # same as: value => $value
1422 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
1423 structure. For example:
1426 value => 'Password',
1427 protect => true, # optional
1430 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
1437 C<protect> - Whether or not the string value should be memory-protected.
1443 $string = $entry->string_value($string_key);
1445 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
1446 is not set or is currently memory-protected. This is just a shortcut for:
1449 my $s = $entry->string(...);
1450 defined $s ? $s->{value} : undef;
1453 =head2 expand_string_value
1455 $string = $entry->expand_string_value;
1457 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
1458 do not expand to values are left as-is.
1460 See L</Placeholders>.
1462 Some placeholders (notably field references) require the entry be connected to a database and will throw an
1465 =head2 other_strings
1467 $other = $entry->other_strings;
1468 $other = $entry->other_strings($delimiter);
1470 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
1471 for executing queries to search for entities based on the contents of these other strings (if any).
1475 $string = $entry->string_peek($string_key);
1477 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
1480 =head2 add_auto_type_association
1482 $entry->add_auto_type_association(\%association);
1484 Add a new auto-type association to an entry.
1486 =head2 expand_keystroke_sequence
1488 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
1489 $string = $entry->expand_keystroke_sequence(\%association);
1490 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
1492 Get a keystroke sequence after placeholder expansion.
1496 \%binary = $entry->binary($binary_key);
1498 $entry->binary($binary_key, \%binary);
1499 $entry->binary($binary_key, %attributes);
1500 $entry->binary($binary_key, $value); # same as: value => $value
1502 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
1503 structure. For example:
1507 protect => true, # optional
1510 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
1517 C<protect> - Whether or not the binary value should be memory-protected.
1523 $binary = $entry->binary_value($binary_key);
1525 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
1526 is not set or is currently memory-protected. This is just a shortcut for:
1529 my $b = $entry->binary(...);
1530 defined $b ? $b->{value} : undef;
1535 $otp = $entry->hmac_otp(%options);
1537 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
1538 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1544 C<counter> - Specify the counter value
1548 To configure HOTP, see L</"One-time Passwords">.
1552 $otp = $entry->time_otp(%options);
1554 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
1555 strings generally must first be unprotected, just like when accessing the password. Valid options are:
1561 C<now> - Specify the value for determining the time-step counter
1565 To configure TOTP, see L</"One-time Passwords">.
1571 $uri_string = $entry->hmac_otp_uri;
1572 $uri_string = $entry->time_otp_uri;
1574 Get a HOTP or TOTP otpauth URI for the entry, if available.
1576 To configure OTP, see L</"One-time Passwords">.
1580 $size = $entry->size;
1582 Get the size (in bytes) of an entry.
1584 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
1585 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
1589 $size = $entry->history_size;
1591 Get the size (in bytes) of all historical entries combined.
1593 =head2 prune_history
1595 @removed_historical_entries = $entry->prune_history(%options);
1597 Remove just as many older historical entries as necessary to get under the database limits. The limits are
1598 taken from the connected database (if any) or can be overridden with C<%options>:
1604 C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
1608 C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
1612 C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
1616 =head2 add_historical_entry
1618 $entry->add_historical_entry($entry);
1620 Add an entry to the history.
1622 =head2 remove_historical_entry
1624 $entry->remove_historical_entry($historical_entry);
1626 Remove an entry from the history.
1628 =head2 current_entry
1630 $current_entry = $entry->current_entry;
1632 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
1636 $bool = $entry->is_current;
1638 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
1639 in the parent group's entry list.
1641 =head2 is_historical
1643 $bool = $entry->is_historical;
1645 Get whether or not an entry is considered historical (i.e. not current).
1647 This is just the inverse of L</is_current>.
1651 $entry = $entry->remove;
1653 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1654 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1656 =head2 searching_enabled
1658 $bool = $entry->searching_enabled;
1660 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1661 L<File::KDBX::Group/effective_enable_searching> value.
1663 Throws if entry has no parent group or if the entry is not connected to a database.
1665 =for Pod::Coverage auto_type times
1669 Please report any bugs or feature requests on the bugtracker website
1670 L<https://github.com/chazmcgarvey/File-KDBX/issues>
1672 When submitting a bug or request, please include a test-file or a
1673 patch to an existing test-file that illustrates the bug or desired
1678 Charles McGarvey <ccm@cpan.org>
1680 =head1 COPYRIGHT AND LICENSE
1682 This software is copyright (c) 2022 by Charles McGarvey.
1684 This is free software; you can redistribute it and/or modify it under
1685 the same terms as the Perl 5 programming language system itself.