Revision history for File-KDBX.
+0.901 2022-05-02 01:18:13-0600
+
+ * Fixed a bug where peeking at memory-protected strings and binaries does
+ not work without unlocking the database at least once.
+ * Added an option for writing files non-atomically.
+ * Fixed broken tests on Windows.
+
0.900 2022-05-01 12:55:59-0600
* Removed the min_version methods from dumper and loader because it was
* Now use the database maintenance_history_days value as the default
"max_age" value in prune_history method.
* Fixed distribution prereq issues.
- * Clean up a lot of pod typos and other inaccuracies.
+ * Cleaned up a lot of pod typos and other inaccuracies.
0.800 2022-04-30 21:14:30-0600
"provides" : {
"File::KDBX" : {
"file" : "lib/File/KDBX.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Cipher" : {
"file" : "lib/File/KDBX/Cipher.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Cipher::CBC" : {
"file" : "lib/File/KDBX/Cipher/CBC.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Cipher::Stream" : {
"file" : "lib/File/KDBX/Cipher/Stream.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Constants" : {
"file" : "lib/File/KDBX/Constants.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Dumper" : {
"file" : "lib/File/KDBX/Dumper.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Dumper::KDB" : {
"file" : "lib/File/KDBX/Dumper/KDB.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Dumper::Raw" : {
"file" : "lib/File/KDBX/Dumper/Raw.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Dumper::V3" : {
"file" : "lib/File/KDBX/Dumper/V3.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Dumper::V4" : {
"file" : "lib/File/KDBX/Dumper/V4.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Dumper::XML" : {
"file" : "lib/File/KDBX/Dumper/XML.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Entry" : {
"file" : "lib/File/KDBX/Entry.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Error" : {
"file" : "lib/File/KDBX/Error.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Group" : {
"file" : "lib/File/KDBX/Group.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::IO" : {
"file" : "lib/File/KDBX/IO.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::IO::Crypt" : {
"file" : "lib/File/KDBX/IO/Crypt.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::IO::HashBlock" : {
"file" : "lib/File/KDBX/IO/HashBlock.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::IO::HmacBlock" : {
"file" : "lib/File/KDBX/IO/HmacBlock.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Iterator" : {
"file" : "lib/File/KDBX/Iterator.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::KDF" : {
"file" : "lib/File/KDBX/KDF.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::KDF::AES" : {
"file" : "lib/File/KDBX/KDF/AES.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::KDF::Argon2" : {
"file" : "lib/File/KDBX/KDF/Argon2.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Key" : {
"file" : "lib/File/KDBX/Key.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Key::ChallengeResponse" : {
"file" : "lib/File/KDBX/Key/ChallengeResponse.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Key::Composite" : {
"file" : "lib/File/KDBX/Key/Composite.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Key::File" : {
"file" : "lib/File/KDBX/Key/File.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Key::Password" : {
"file" : "lib/File/KDBX/Key/Password.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Key::YubiKey" : {
"file" : "lib/File/KDBX/Key/YubiKey.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Loader" : {
"file" : "lib/File/KDBX/Loader.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Loader::KDB" : {
"file" : "lib/File/KDBX/Loader/KDB.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Loader::Raw" : {
"file" : "lib/File/KDBX/Loader/Raw.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Loader::V3" : {
"file" : "lib/File/KDBX/Loader/V3.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Loader::V4" : {
"file" : "lib/File/KDBX/Loader/V4.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Loader::XML" : {
"file" : "lib/File/KDBX/Loader/XML.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Object" : {
"file" : "lib/File/KDBX/Object.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Safe" : {
"file" : "lib/File/KDBX/Safe.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Transaction" : {
"file" : "lib/File/KDBX/Transaction.pm",
- "version" : "0.900"
+ "version" : "0.901"
},
"File::KDBX::Util" : {
"file" : "lib/File/KDBX/Util.pm",
- "version" : "0.900"
+ "version" : "0.901"
}
},
"release_status" : "stable",
"web" : "https://github.com/chazmcgarvey/File-KDBX"
}
},
- "version" : "0.900",
+ "version" : "0.901",
"x_authority" : "cpan:CCM",
"x_generated_by_perl" : "v5.34.1",
"x_serialization_backend" : "Cpanel::JSON::XS version 4.27",
provides:
File::KDBX:
file: lib/File/KDBX.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Cipher:
file: lib/File/KDBX/Cipher.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Cipher::CBC:
file: lib/File/KDBX/Cipher/CBC.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Cipher::Stream:
file: lib/File/KDBX/Cipher/Stream.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Constants:
file: lib/File/KDBX/Constants.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Dumper:
file: lib/File/KDBX/Dumper.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Dumper::KDB:
file: lib/File/KDBX/Dumper/KDB.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Dumper::Raw:
file: lib/File/KDBX/Dumper/Raw.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Dumper::V3:
file: lib/File/KDBX/Dumper/V3.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Dumper::V4:
file: lib/File/KDBX/Dumper/V4.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Dumper::XML:
file: lib/File/KDBX/Dumper/XML.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Entry:
file: lib/File/KDBX/Entry.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Error:
file: lib/File/KDBX/Error.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Group:
file: lib/File/KDBX/Group.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::IO:
file: lib/File/KDBX/IO.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::IO::Crypt:
file: lib/File/KDBX/IO/Crypt.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::IO::HashBlock:
file: lib/File/KDBX/IO/HashBlock.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::IO::HmacBlock:
file: lib/File/KDBX/IO/HmacBlock.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Iterator:
file: lib/File/KDBX/Iterator.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::KDF:
file: lib/File/KDBX/KDF.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::KDF::AES:
file: lib/File/KDBX/KDF/AES.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::KDF::Argon2:
file: lib/File/KDBX/KDF/Argon2.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Key:
file: lib/File/KDBX/Key.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Key::ChallengeResponse:
file: lib/File/KDBX/Key/ChallengeResponse.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Key::Composite:
file: lib/File/KDBX/Key/Composite.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Key::File:
file: lib/File/KDBX/Key/File.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Key::Password:
file: lib/File/KDBX/Key/Password.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Key::YubiKey:
file: lib/File/KDBX/Key/YubiKey.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Loader:
file: lib/File/KDBX/Loader.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Loader::KDB:
file: lib/File/KDBX/Loader/KDB.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Loader::Raw:
file: lib/File/KDBX/Loader/Raw.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Loader::V3:
file: lib/File/KDBX/Loader/V3.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Loader::V4:
file: lib/File/KDBX/Loader/V4.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Loader::XML:
file: lib/File/KDBX/Loader/XML.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Object:
file: lib/File/KDBX/Object.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Safe:
file: lib/File/KDBX/Safe.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Transaction:
file: lib/File/KDBX/Transaction.pm
- version: '0.900'
+ version: '0.901'
File::KDBX::Util:
file: lib/File/KDBX/Util.pm
- version: '0.900'
+ version: '0.901'
recommends:
Compress::Raw::Zlib: '0'
File::KDBX::XS: '0'
bugtracker: https://github.com/chazmcgarvey/File-KDBX/issues
homepage: https://github.com/chazmcgarvey/File-KDBX
repository: https://github.com/chazmcgarvey/File-KDBX.git
-version: '0.900'
+version: '0.901'
x_authority: cpan:CCM
x_generated_by_perl: v5.34.1
x_serialization_backend: 'YAML::Tiny version 1.73'
"lib" => 0,
"utf8" => 0
},
- "VERSION" => "0.900",
+ "VERSION" => "0.901",
"test" => {
"TESTS" => "t/*.t"
}
VERSION
- version 0.900
+ version 0.901
SYNOPSIS
$kdbx->lock;
- Encrypt all protected binaries strings in a database. The encrypted
- strings are stored in a File::KDBX::Safe associated with the database
- and the actual strings will be replaced with undef to indicate their
+ Encrypt all protected strings and binaries in a database. The encrypted
+ data is stored in a File::KDBX::Safe associated with the database and
+ the actual values will be replaced with undef to indicate their
protected state. Returns itself to allow method chaining.
- You can call code on an already-locked database to memory-protect any
+ You can call lock on an already-locked database to memory-protect any
unprotected strings and binaries added after the last time the database
was locked.
$kdbx->unlock;
- Decrypt all protected strings in a database, replacing undef
- placeholders with unprotected values. Returns itself to allow method
- chaining.
+ Decrypt all protected strings and binaries in a database, replacing
+ undef value placeholders with their actual, unprotected values. Returns
+ itself to allow method chaining.
unlock_scoped
See "lock" and "unlock".
+ Example:
+
+ {
+ my $guard = $kdbx->unlock_scoped;
+ ...;
+ }
+ # $kdbx is now memory-locked
+
peek
$string = $kdbx->peek(\%string);
$bool = $kdbx->is_locked;
- Get whether or not a database's strings are memory-protected. If this
- is true, then some or all of the protected strings within the database
- will be unavailable (literally have undef values) until "unlock" is
- called.
+ Get whether or not a database's contents are in a locked (i.e.
+ memory-protected) state. If this is true, then some or all of the
+ protected strings and binaries within the database will be unavailable
+ (literally have undef values) until "unlock" is called.
remove_empty_groups
Get or set a File::KDBX::Key. This is the master key (e.g. a password
or a key file that can decrypt a database). You can also pass a
- primitive that can be cast to a Key. See "new" in File::KDBX::Key for
- an explanation of what the primitive can be.
+ primitive castable to a Key. See "new" in File::KDBX::Key for an
+ explanation of what the primitive can be.
You generally don't need to call this directly because you can provide
the key directly to the loader or dumper when loading or dumping a KDBX
Errors in this package are constructed as File::KDBX::Error objects and
propagated using perl's built-in mechanisms. Fatal errors are
- propagated using "die" in functions and non-fatal errors (a.k.a.
- warnings) are propagated using "warn" in functions while adhering to
- perl's warnings system. If you're already familiar with these
+ propagated using "die LIST" in perlfunc and non-fatal errors (a.k.a.
+ warnings) are propagated using "warn LIST" in perlfunc while adhering
+ to perl's warnings system. If you're already familiar with these
mechanisms, you can skip this section.
- You can catch fatal errors using "eval" in functions (or something like
- Try::Tiny) and non-fatal errors using $SIG{__WARN__} (see "%SIG" in
- variables). Examples:
+ You can catch fatal errors using "eval BLOCK" in perlfunc (or something
+ like Try::Tiny) and non-fatal errors using $SIG{__WARN__} (see "%SIG"
+ in perlvar). Examples:
use File::KDBX::Error qw(error);
use boolean;
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
our $WARNINGS = 1;
fieldhashes \my (%SAFE, %KEYS);
}
-sub is_locked { $_[0]->_safe ? 1 : 0 }
+sub is_locked { !!$_[0]->_safe }
##############################################################################
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
$kdbx->lock;
-Encrypt all protected binaries strings in a database. The encrypted strings are stored in
-a L<File::KDBX::Safe> associated with the database and the actual strings will be replaced with C<undef> to
+Encrypt all protected strings and binaries in a database. The encrypted data is stored in
+a L<File::KDBX::Safe> associated with the database and the actual values will be replaced with C<undef> to
indicate their protected state. Returns itself to allow method chaining.
-You can call C<code> on an already-locked database to memory-protect any unprotected strings and binaries
+You can call C<lock> on an already-locked database to memory-protect any unprotected strings and binaries
added after the last time the database was locked.
=head2 unlock
$kdbx->unlock;
-Decrypt all protected strings in a database, replacing C<undef> placeholders with unprotected values. Returns
-itself to allow method chaining.
+Decrypt all protected strings and binaries in a database, replacing C<undef> value placeholders with their
+actual, unprotected values. Returns itself to allow method chaining.
=head2 unlock_scoped
See L</lock> and L</unlock>.
+Example:
+
+ {
+ my $guard = $kdbx->unlock_scoped;
+ ...;
+ }
+ # $kdbx is now memory-locked
+
=head2 peek
$string = $kdbx->peek(\%string);
$bool = $kdbx->is_locked;
-Get whether or not a database's strings are memory-protected. If this is true, then some or all of the
-protected strings within the database will be unavailable (literally have C<undef> values) until L</unlock> is
-called.
+Get whether or not a database's contents are in a locked (i.e. memory-protected) state. If this is true, then
+some or all of the protected strings and binaries within the database will be unavailable (literally have
+C<undef> values) until L</unlock> is called.
=head2 remove_empty_groups
$key = $kdbx->key($primitive);
Get or set a L<File::KDBX::Key>. This is the master key (e.g. a password or a key file that can decrypt
-a database). You can also pass a primitive that can be cast to a B<Key>. See L<File::KDBX::Key/new> for an
-explanation of what the primitive can be.
+a database). You can also pass a primitive castable to a B<Key>. See L<File::KDBX::Key/new> for an explanation
+of what the primitive can be.
You generally don't need to call this directly because you can provide the key directly to the loader or
dumper when loading or dumping a KDBX file.
=head1 ERRORS
Errors in this package are constructed as L<File::KDBX::Error> objects and propagated using perl's built-in
-mechanisms. Fatal errors are propagated using L<functions/die> and non-fatal errors (a.k.a. warnings) are
-propagated using L<functions/warn> while adhering to perl's L<warnings> system. If you're already familiar
-with these mechanisms, you can skip this section.
+mechanisms. Fatal errors are propagated using L<perlfunc/"die LIST"> and non-fatal errors (a.k.a. warnings)
+are propagated using L<perlfunc/"warn LIST"> while adhering to perl's L<warnings> system. If you're already
+familiar with these mechanisms, you can skip this section.
-You can catch fatal errors using L<functions/eval> (or something like L<Try::Tiny>) and non-fatal errors using
-C<$SIG{__WARN__}> (see L<variables/%SIG>). Examples:
+You can catch fatal errors using L<perlfunc/"eval BLOCK"> (or something like L<Try::Tiny>) and non-fatal
+errors using C<$SIG{__WARN__}> (see L<perlvar/%SIG>). Examples:
use File::KDBX::Error qw(error);
use Scalar::Util qw(looks_like_number);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
my %CIPHERS;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Cipher';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
has key_size => 32;
sub iv_size { 16 }
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Cipher';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
has 'counter', is => 'ro', default => 0;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
use Scalar::Util qw(dualvar);
use namespace::clean -except => 'import';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
BEGIN {
my %CONSTANTS = (
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
use Scalar::Util qw(looks_like_number openhandle);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub new {
my $filepath = shift;
my %args = @_ % 2 == 0 ? @_ : (key => shift, @_);
- my $key = delete $args{key};
+ my $key = delete $args{key};
+ my $mode = delete $args{mode};
+ my $uid = delete $args{uid};
+ my $gid = delete $args{gid};
+ my $atomic = delete $args{atomic} // 1;
+
$args{kdbx} //= $self->kdbx;
- require File::Temp;
- my ($fh, $filepath_temp) = eval { File::Temp::tempfile("${filepath}-XXXXXX", CLEANUP => 1) };
- if (!$fh or my $err = $@) {
- $err //= 'Unknown error';
- throw sprintf('Open file failed (%s): %s', $filepath_temp, $err),
- error => $err,
- filepath => $filepath_temp;
+ my ($fh, $filepath_temp);
+ if ($atomic) {
+ require File::Temp;
+ ($fh, $filepath_temp) = eval { File::Temp::tempfile("${filepath}-XXXXXX", UNLINK => 1) };
+ if (!$fh or my $err = $@) {
+ $err //= 'Unknown error';
+ throw sprintf('Open file failed (%s): %s', $filepath_temp, $err),
+ error => $err,
+ filepath => $filepath_temp;
+ }
+ }
+ else {
+ open($fh, '>:raw', $filepath) or throw "Open file failed ($filepath): $!", filepath => $filepath;
}
$fh->autoflush(1);
my ($file_mode, $file_uid, $file_gid) = (stat($filepath))[2, 4, 5];
- my $mode = $args{mode} // $file_mode // do { my $m = umask; defined $m ? oct(666) &~ $m : undef };
- my $uid = $args{uid} // $file_uid // -1;
- my $gid = $args{gid} // $file_gid // -1;
- chmod($mode, $filepath_temp) if defined $mode;
- chown($uid, $gid, $filepath_temp);
- rename($filepath_temp, $filepath) or throw "Failed to write file ($filepath): $!", filepath => $filepath;
+ if ($filepath_temp) {
+ $mode //= $file_mode // do { my $m = umask; defined $m ? oct(666) &~ $m : undef };
+ $uid //= $file_uid // -1;
+ $gid //= $file_gid // -1;
+ chmod($mode, $filepath_temp) if defined $mode;
+ chown($uid, $gid, $filepath_temp);
+ rename($filepath_temp, $filepath) or throw "Failed to write file ($filepath): $!",
+ filepath => $filepath;
+ }
return $self;
}
=head1 VERSION
-version 0.900
+version 0.901
=head1 ATTRIBUTES
=head2 dump
- $dumper->dump(\$string, $key);
- $dumper->dump(*IO, $key);
- $dumper->dump($filepath, $key);
+ $dumper->dump(\$string, %options);
+ $dumper->dump(\$string, $key, %options);
+ $dumper->dump(*IO, %options);
+ $dumper->dump(*IO, $key, %options);
+ $dumper->dump($filepath, %options);
+ $dumper->dump($filepath, $key, %options);
Dump a KDBX file.
-The C<$key> is either a L<File::KDBX::Key> or a primitive that can be cast to a Key object.
+The C<$key> is either a L<File::KDBX::Key> or a primitive castable to a Key object. Available options:
+
+=over 4
+
+=item *
+
+C<kdbx> - Database to dump (default: value of L</kdbx>)
+
+=item *
+
+C<key> - Alternative way to specify C<$key> (default: value of L</File::KDBX/key>)
+
+=back
+
+Other options are supported depending on the first argument. See L</dump_string>, L</dump_file> and
+L</dump_handle>.
=head2 dump_string
- $dumper->dump_string(\$string, $key);
- \$string = $dumper->dump_string($key);
+ $dumper->dump_string(\$string, %options);
+ $dumper->dump_string(\$string, $key, %options);
+ \$string = $dumper->dump_string(%options);
+ \$string = $dumper->dump_string($key, %options);
+
+Dump a KDBX file to a string / memory buffer. Available options:
+
+=over 4
+
+=item *
-Dump a KDBX file to a string / memory buffer.
+C<kdbx> - Database to dump (default: value of L</kdbx>)
+
+=item *
+
+C<key> - Alternative way to specify C<$key> (default: value of L</File::KDBX/key>)
+
+=back
=head2 dump_file
- $dumper->dump_file($filepath, $key);
+ $dumper->dump_file($filepath, %options);
+ $dumper->dump_file($filepath, $key, %options);
-Dump a KDBX file to a filesystem.
+Dump a KDBX file to a filesystem. Available options:
+
+=over 4
+
+=item *
+
+C<kdbx> - Database to dump (default: value of L</kdbx>)
+
+=item *
+
+C<key> - Alternative way to specify C<$key> (default: value of L</File::KDBX/key>)
+
+=item *
+
+C<mode> - File mode / permissions (see L<perlfunc/"chmod LIST">
+
+=item *
+
+C<uid> - User ID (see L<perlfunc/"chown LIST">)
+
+=item *
+
+C<gid> - Group ID (see L<perlfunc/"chown LIST">)
+
+=item *
+
+C<atomic> - Write to the filepath atomically (default: true)
+
+=back
=head2 dump_handle
- $dumper->dump_handle($fh, $key);
- $dumper->dump_handle(*IO, $key);
+ $dumper->dump_handle($fh, %options);
+ $dumper->dump_handle(*IO, $key, %options);
+ $dumper->dump_handle($fh, %options);
+ $dumper->dump_handle(*IO, $key, %options);
+
+Dump a KDBX file to an output stream / file handle. Available options:
+
+=over 4
+
+=item *
+
+C<kdbx> - Database to dump (default: value of L</kdbx>)
-Dump a KDBX file to an output stream / file handle.
+=item *
+
+C<key> - Alternative way to specify C<$key> (default: value of L</File::KDBX/key>)
+
+=back
=head1 BUGS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _write_magic_numbers { '' }
sub _write_headers { '' }
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::Dumper';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _dump {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _write_headers {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 BUGS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
has _binaries_written => {}, is => 'ro';
=head1 VERSION
-version 0.900
+version 0.901
=head1 BUGS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
has allow_protection => 1;
=head1 VERSION
-version 0.900
+version 0.901
=head1 ATTRIBUTES
extends 'File::KDBX::Object';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
my $PLACEHOLDER_MAX_DEPTH = 10;
my %PLACEHOLDERS;
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
use Scalar::Util qw(blessed looks_like_number);
use namespace::clean -except => 'import';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
our @EXPORT = qw(alert error throw);
=head1 VERSION
-version 0.900
+version 0.901
=head1 ATTRIBUTES
extends 'File::KDBX::Object';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
# has uuid => sub { generate_uuid(printable => 1) };
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'IO::Handle';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _croak { require Carp; goto &Carp::croak }
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::IO';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
our $BUFFER_SIZE = 16384;
our $ERROR;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::IO';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
our $ALGORITHM = 'SHA256';
our $BLOCK_SIZE = 1048576; # 1MiB
our $ERROR;
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::IO';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
our $BLOCK_SIZE = 1048576; # 1MiB
our $ERROR;
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
BEGIN { mark_as_loaded('Iterator::Simple::Iterator') }
extends 'Iterator::Simple::Iterator';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub new {
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
use Scalar::Util qw(blessed);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
my %KDFS;
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::KDF';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
# Rounds higher than this are eligible for forking:
my $FORK_OPTIMIZATION_THRESHOLD = 100_000;
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::KDF';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub salt { $_[0]->{+KDF_PARAM_ARGON2_SALT} or throw 'Salt is not set' }
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
use Scalar::Util qw(blessed openhandle);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
fieldhashes \my %SAFE;
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::Key';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub init {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Key';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub init {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Key';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
has 'type', is => 'ro';
my $version = $args{version} // $self->version // 2;
my $filepath = $args{filepath} // $self->filepath;
my $fh = $args{fh};
+ my $atomic = $args{atomic} // 1;
my $filepath_temp;
if (!openhandle($fh)) {
$filepath or throw 'Must specify where to safe the key file to';
- require File::Temp;
- ($fh, $filepath_temp) = eval { File::Temp::tempfile("${filepath}-XXXXXX", CLEANUP => 1) };
- if (!$fh or my $err = $@) {
- $err //= 'Unknown error';
- throw sprintf('Open file failed (%s): %s', $filepath_temp, $err),
- error => $err,
- filepath => $filepath_temp;
+ if ($atomic) {
+ require File::Temp;
+ ($fh, $filepath_temp) = eval { File::Temp::tempfile("${filepath}-XXXXXX", UNLINK => 1) };
+ if (!$fh or my $err = $@) {
+ $err //= 'Unknown error';
+ throw sprintf('Open file failed (%s): %s', $filepath_temp, $err),
+ error => $err,
+ filepath => $filepath_temp;
+ }
+ }
+ else {
+ open($fh, '>:raw', $filepath) or throw "Open file failed ($filepath): $!", filepath => $filepath;
}
}
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
C<raw_key> - Raw key (default: value of L</raw_key>)
+=item *
+
+C<atomic> - Write to the filepath atomically (default: true)
+
=back
=head1 BUGS
extends 'File::KDBX::Key';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub init {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Key::ChallengeResponse';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
# It can take some time for the USB device to be ready again, so we can retry a few times.
our $RETRY_COUNT = 5;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
use Scalar::Util qw(looks_like_number openhandle);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub new {
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
=head2 load
+ $kdbx = File::KDBX::Loader->load(\$string, %options);
$kdbx = File::KDBX::Loader->load(\$string, $key);
+ $kdbx = File::KDBX::Loader->load(*IO, %options);
$kdbx = File::KDBX::Loader->load(*IO, $key);
+ $kdbx = File::KDBX::Loader->load($filepath, %options);
$kdbx = File::KDBX::Loader->load($filepath, $key);
- $kdbx = $loader->load(...); # also instance method
-Load a KDBX file.
+Load a KDBX file. This works as an instance or a class method. The C<$key> is either
+a L<File::KDBX::Key> or a primitive castable to a Key object. Available options:
-The C<$key> is either a L<File::KDBX::Key> or a primitive that can be cast to a Key object.
+=over 4
+
+=item *
+
+C<key> - Alternative way to specify C<$key>
+
+=back
=head2 load_string
+ $kdbx = File::KDBX::Loader->load_string($string, %options);
$kdbx = File::KDBX::Loader->load_string($string, $key);
+ $kdbx = File::KDBX::Loader->load_string(\$string, %options);
$kdbx = File::KDBX::Loader->load_string(\$string, $key);
- $kdbx = $loader->load_string(...); # also instance method
-Load a KDBX file from a string / memory buffer.
+Load a KDBX file from a string / memory buffer. This works as an instance or class method. Available options:
+
+=over 4
+
+=item *
+
+C<key> - Alternative way to specify C<$key>
+
+=back
=head2 load_file
+ $kdbx = File::KDBX::Loader->load_file($filepath, %options);
$kdbx = File::KDBX::Loader->load_file($filepath, $key);
- $kdbx = $loader->load_file(...); # also instance method
-Read a KDBX file from a filesystem.
+Read a KDBX file from a filesystem. This works as an instance or class method. Available options:
+
+=over 4
+
+=item *
+
+C<key> - Alternative way to specify C<$key>
+
+=back
=head2 load_handle
+ $kdbx = File::KDBX::Loader->load_handle($fh, %options);
$kdbx = File::KDBX::Loader->load_handle($fh, $key);
+ $kdbx = File::KDBX::Loader->load_handle(*IO, %options);
$kdbx = File::KDBX::Loader->load_handle(*IO, $key);
- $kdbx->load_handle(...); # also instance method
-Read a KDBX file from an input stream / file handle.
+Read a KDBX file from an input stream / file handle. This works as an instance or class method. Available
+options:
+
+=over 4
+
+=item *
+
+C<key> - Alternative way to specify C<$key>
+
+=back
=head2 read_magic_numbers
extends 'File::KDBX::Loader';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
my $DEFAULT_EXPIRATION = Time::Piece->new(32503677839); # 2999-12-31 23:59:59
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
extends 'File::KDBX::Loader';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _read {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
extends 'File::KDBX::Loader';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _read_header {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 BUGS
extends 'File::KDBX::Loader';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub _read_header {
my $self = shift;
=head1 VERSION
-version 0.900
+version 0.901
=head1 BUGS
extends 'File::KDBX::Loader';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
has '_reader', is => 'ro';
has '_safe', is => 'ro', default => sub { File::KDBX::Safe->new(cipher => $_[0]->kdbx->random_stream) };
=head1 VERSION
-version 0.900
+version 0.901
=head1 BUGS
use Scalar::Util qw(blessed weaken);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
fieldhashes \my (%KDBX, %PARENT, %TXNS, %REFS, %SIGNALS);
=head1 VERSION
-version 0.900
+version 0.901
=head1 DESCRIPTION
use Scalar::Util qw(refaddr);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub new {
@strings or throw 'Must provide strings to lock';
for my $string (@strings) {
- my $item = {str => $string};
+ my $item = {str => $string, off => $self->{counter}};
$item->{filter} = $filter if defined $filter;
if (is_scalarref($string)) {
next if !defined $$string;
=head1 VERSION
-version 0.900
+version 0.901
=head1 SYNOPSIS
use File::KDBX::Util qw(:class);
use namespace::clean;
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
sub new {
=head1 VERSION
-version 0.900
+version 0.901
=head1 ATTRIBUTES
use boolean;
use namespace::clean -except => 'import';
-our $VERSION = '0.900'; # VERSION
+our $VERSION = '0.901'; # VERSION
our %EXPORT_TAGS = (
assert => [qw(DEBUG assert assert_64bit)],
=head1 VERSION
-version 0.900
+version 0.901
=head1 FUNCTIONS
$size = read_all($fh, my $buffer, $size);
$size = read_all($fh, my $buffer, $size, $offset);
-Like L<functions/read> but returns C<undef> if not all C<$size> bytes are read. This is considered an error,
-distinguishable from other errors by C<$!> not being set.
+Like L<perlfunc/"read FILEHANDLE,SCALAR,LENGTH,OFFSET"> but returns C<undef> if not all C<$size> bytes are
+read. This is considered an error, distinguishable from other errors by C<$!> not being set.
=head2 recurse_limit
use TestCommon;
use File::KDBX;
+use File::Temp qw(tempfile);
use Test::Deep;
use Test::More;
use Time::Piece;
is $entry->custom_icon_uuid, $icon_uuid, 'Uses of removed icon change';
};
+subtest 'Dumping to filesystem' => sub {
+ my $kdbx = File::KDBX->new;
+ $kdbx->add_entry(title => 'Foo', password => 'whatever');
+
+ my ($fh, $filepath) = tempfile('kdbx-XXXXXX', TMPDIR => 1, UNLINK => 1);
+ close($fh);
+
+ $kdbx->dump($filepath, 'a');
+
+ my $kdbx2 = File::KDBX->load($filepath, 'a');
+ my $entry = $kdbx2->entries->map(sub { $_->title.'/'.$_->expand_password })->next;
+ is $entry, 'Foo/whatever', 'Dump and load an entry';
+
+ $kdbx->dump($filepath, key => 'a', atomic => 0);
+
+ $kdbx2 = File::KDBX->load($filepath, 'a');
+ $entry = $kdbx2->entries->map(sub { $_->title.'/'.$_->expand_password })->next;
+ is $entry, 'Foo/whatever', 'Dump and load an entry (non-atomic)';
+};
+
done_testing;
$write = File::KDBX::IO::HashBlock->new($write);
print $write $expected_plaintext;
close($write) or die "close failed: $!";
- # exit;
- require POSIX;
- POSIX::_exit(0);
+ exit;
+ # require POSIX;
+ # POSIX::_exit(0);
}
$read = File::KDBX::IO::HashBlock->new($read);
$write = File::KDBX::IO::HmacBlock->new($write, key => $KEY);
print $write $expected_plaintext;
close($write) or die "close failed: $!";
- # exit;
- require POSIX;
- POSIX::_exit(0);
+ exit;
+ # require POSIX;
+ # POSIX::_exit(0);
}
$read = File::KDBX::IO::HmacBlock->new($read, key => $KEY);
is $kdbx->meta->{database_name}, 'Protected Strings Test', 'Extract database name from meta';
- $kdbx->unlock;
-
my $entry = $kdbx->entries->next;
is $entry->title, 'Sample Entry', 'Get entry title';
+
+ is $entry->string_peek('Password'), 'ProtectedPassword', 'Peek at password from entry';
+ is $entry->string_peek('TestProtected'), 'ABC', 'Peek at protected string from entry';
+ $kdbx->unlock;
is $entry->username, 'Protected User Name', 'Get protected username from entry';
is $entry->password, 'ProtectedPassword', 'Get protected password from entry';
is $entry->string_value('TestProtected'), 'ABC', 'Get ABC string from entry';
subtest "Save $type key file" => sub {
my ($type, $filename, $expected_key, $version) = @_;
- my ($fh, $filepath) = tempfile('keyfile-XXXXXX', TMPDIR => 1, UNLINK => 1, SUFFIX => '.key');
+ my ($fh, $filepath) = tempfile('keyfile-XXXXXX', TMPDIR => 1, UNLINK => 1);
+ close($fh);
note $filepath;
my $key = File::KDBX::Key::File->new(
filepath => $filepath,
);
my $e = exception { $key->save };
- close($fh);
if ($type == KEY_FILE_TYPE_HASHED) {
like $e, qr/invalid type/i, "Cannot save $type file";
'Can calculate raw key from file handle' or diag encode_b64($key->raw_key);
is $key->type, 'hashed', 'file type is detected as hashed';
- my ($fh_save, $filepath) = tempfile('keyfile-XXXXXX', TMPDIR => 1, UNLINK => 1, SUFFIX => '.key');
+ my ($fh_save, $filepath) = tempfile('keyfile-XXXXXX', TMPDIR => 1, UNLINK => 1);
is exception { $key->save(fh => $fh_save, type => KEY_FILE_TYPE_XML) }, undef,
'Save key file using IO handle';
close($fh_save);