KeepassX format implementation in pure dart.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

197 lines
7.4 KiB

import 'dart:convert';
import 'dart:typed_data';
import 'package:kdbx/kdbx.dart';
import 'package:test/test.dart';
import 'internal/test_utils.dart';
void expectBinary(KdbxEntry entry, String key, dynamic matcher) {
final binaries = entry.binaryEntries;
expect(binaries, hasLength(1));
final binary = binaries.first;
expect(binary.key.key, key);
expect(binary.value.value, matcher);
}
Future<void> _testAddNewAttachment(String filePath) async {
final saved = await (() async {
final f = await TestUtil().readKdbxFile(filePath);
final entry = KdbxEntry.create(f, f.body.rootGroup);
entry.label = 'addattachment';
f.body.rootGroup.addEntry(entry);
expect(entry.binaryEntries, hasLength(0));
entry.createBinary(
isProtected: false,
name: 'test.txt',
bytes: utf8.encode('Content1') as Uint8List);
entry.createBinary(
isProtected: false,
name: 'test.txt',
bytes: utf8.encode('Content2') as Uint8List);
return await f.save();
})();
{
final file = await TestUtil().readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries
.firstWhere((e) => e.label == 'addattachment');
final binaries = entry.binaryEntries.toList();
expect(entry.binaryEntries, hasLength(2));
expect(binaries[0].key.key, 'test.txt');
expect(binaries[0].value.value, IsUtf8String('Content1'));
// must have been renamed.
expect(binaries[1].key.key, 'test1.txt');
expect(binaries[1].value.value, IsUtf8String('Content2'));
}
}
void main() {
final testUtil = TestUtil();
group('kdbx3 attachment', () {
void expectKeepass2binariesContents(KdbxEntry entry) {
final binaries = entry.binaryEntries;
expect(binaries, hasLength(3));
for (final binary in binaries) {
switch (binary.key.key) {
case 'example1.txt':
expect(utf8.decode(binary.value.value), 'content1 example\n\n');
break;
case 'example2.txt':
expect(utf8.decode(binary.value.value), 'content2 example\n\n');
break;
case 'keepasslogo.jpeg':
expect(binary.value.value, hasLength(7092));
break;
default:
fail('invalid key. ${binary.key}');
}
}
}
test('read binary', () async {
final file = await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final entry = file.body.rootGroup.entries.first;
expectKeepass2binariesContents(entry);
});
test('read write read', () async {
final fileRead =
await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final saved = await fileRead.save();
final file = await testUtil.readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries.first;
expectKeepass2binariesContents(entry);
});
test('modify file with binary in history', () async {
final fileRead =
await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
void updateEntry(KdbxFile file) {
final entry = fileRead.body.rootGroup.entries.first;
entry.setString(KdbxKeyCommon.TITLE, PlainValue('example'));
}
updateEntry(fileRead);
final saved = await fileRead.save();
final file = await testUtil.readKdbxFileBytes(saved);
await file.save();
});
test('Add new attachment', () async {
await _testAddNewAttachment('test/keepass2binaries.kdbx');
});
test('Remove attachment', () async {
final saved = await (() async {
final file = await testUtil.readKdbxFile('test/keepass2binaries.kdbx');
final entry = file.body.rootGroup.entries.first;
expectKeepass2binariesContents(entry);
expect(file.ctx.binariesIterable, hasLength(3));
entry.removeBinary(KdbxKey('example1.txt'));
expect(file.ctx.binariesIterable, hasLength(3));
return await file.save();
})();
final file = await testUtil.readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries.first;
expect(entry.binaryEntries, hasLength(2));
expect(entry.binaryEntries.map((e) => (e.key.key)),
['example2.txt', 'keepasslogo.jpeg']);
// the file itself will contain 3 items, because it is still
// available in history.
expect(file.ctx.binariesIterable, hasLength(3));
expect(entry.history.last.binaryEntries, hasLength(3));
// make sure the file can still be saved.
await file.save();
});
test('keepassxc compatibility', () async {
// keepass has files in arbitrary sort order.
final file = await testUtil
.readKdbxFile('test/test_files/binarytest-keepassxc.kdbx');
final entry = file.body.rootGroup.entries.first;
for (final name in ['a', 'b', 'c', 'd', 'e']) {
expect(
utf8.decode(entry.getBinary(KdbxKey('$name.txt'))!.value).trim(),
name,
);
}
});
}, tags: ['kdbx3']);
group('kdbx4 attachment', () {
test('read binary', () async {
final file =
await testUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx');
expect(file.body.rootGroup.entries, hasLength(2));
expectBinary(file.body.rootGroup.entries.first, 'example2.txt',
IsUtf8String('content2 example\n\n'));
expectBinary(file.body.rootGroup.entries.last, 'keepasslogo.jpeg',
hasLength(7092));
});
test('read, write, read kdbx4', () async {
final fileRead =
await testUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx');
final saved = await fileRead.save();
final file = await testUtil.readKdbxFileBytes(saved);
expect(file.body.rootGroup.entries, hasLength(2));
expectBinary(file.body.rootGroup.entries.first, 'example2.txt',
IsUtf8String('content2 example\n\n'));
expectBinary(file.body.rootGroup.entries.last, 'keepasslogo.jpeg',
hasLength(7092));
});
test('remove attachment kdbx4', () async {
final saved = await (() async {
final file =
await testUtil.readKdbxFile('test/keepass2kdbx4binaries.kdbx');
final entry = file.body.rootGroup.entries.first;
expectBinary(file.body.rootGroup.entries.first, 'example2.txt',
IsUtf8String('content2 example\n\n'));
expectBinary(file.body.rootGroup.entries.last, 'keepasslogo.jpeg',
hasLength(7092));
expect(file.ctx.binariesIterable, hasLength(2));
entry.removeBinary(KdbxKey('example2.txt'));
// the binary remains in the file, since it is referenced in the history
expect(file.ctx.binariesIterable, hasLength(2));
expect(file.dirtyObjects, [entry]);
return await file.save();
})();
final file = await testUtil.readKdbxFileBytes(saved);
final entry = file.body.rootGroup.entries.first;
expect(entry.binaryEntries, hasLength(0));
expectBinary(file.body.rootGroup.entries.last, 'keepasslogo.jpeg',
hasLength(7092));
expect(file.ctx.binariesIterable, hasLength(2));
});
test('Add new attachment kdbx4', () async {
await _testAddNewAttachment('test/keepass2kdbx4binaries.kdbx');
});
}, tags: ['kdbx4']);
}
class IsUtf8String extends CustomMatcher {
IsUtf8String(dynamic matcher) : super('is utf8 string', 'utf8', matcher);
@override
Object? featureValueOf(dynamic actual) {
if (actual is Uint8List) {
return utf8.decode(actual);
}
return super.featureValueOf(actual);
}
}