diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f042a9..74c0b48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + +- Implemented support for custom icons. + ## 0.4.1 - fix bug saving files with history entries which contain attachments. diff --git a/bin/kdbx.dart b/bin/kdbx.dart index 8668e72..e5037f8 100644 --- a/bin/kdbx.dart +++ b/bin/kdbx.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:ffi'; import 'dart:io'; import 'package:argon2_ffi_base/argon2_ffi_base.dart'; @@ -17,9 +16,6 @@ final _logger = Logger('kdbx'); void main(List arguments) { exitCode = 0; - final path = Platform.script.resolve('../argon2_ffi_plugin.dll').toFilePath(); - print('loading $path'); - DynamicLibrary.open(path); final runner = KdbxCommandRunner('kdbx', 'Kdbx Utility'); runner.run(arguments).catchError((dynamic error, StackTrace stackTrace) { if (error is! UsageException) { diff --git a/example/pubspec.lock b/example/pubspec.lock index 62dc4bb..3048441 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -112,7 +112,7 @@ packages: path: ".." relative: true source: path - version: "0.4.0+1" + version: "0.4.1" logging: dependency: transitive description: diff --git a/lib/src/kdbx_meta.dart b/lib/src/kdbx_meta.dart index be7a85f..44f6863 100644 --- a/lib/src/kdbx_meta.dart +++ b/lib/src/kdbx_meta.dart @@ -1,3 +1,7 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; import 'package:kdbx/kdbx.dart'; import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_binary.dart'; @@ -17,6 +21,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { String generator, }) : customData = KdbxCustomData.create(), binaries = [], + _customIcons = {}, super.create('Meta') { this.databaseName.set(databaseName); this.generator.set(generator ?? 'kdbx.dart'); @@ -39,6 +44,20 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { yield KdbxBinary.readBinaryXml(binaryNode, isInline: false); } })?.toList(), + _customIcons = node + .singleElement(KdbxXml.NODE_CUSTOM_ICONS) + ?.let((el) sync* { + for (final iconNode in el.findElements(KdbxXml.NODE_ICON)) { + yield KdbxCustomIcon( + uuid: KdbxUuid( + iconNode.singleTextNode(KdbxXml.NODE_UUID)), + data: base64.decode( + iconNode.singleTextNode(KdbxXml.NODE_DATA))); + } + }) + ?.map((e) => MapEntry(e.uuid, e)) + ?.let((that) => Map.fromEntries(that)) ?? + {}, super.read(node); @override @@ -49,6 +68,18 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { /// only used in Kdbx 3 final List binaries; + final Map _customIcons; + + Map get customIcons => + UnmodifiableMapView(_customIcons); + + void addCustomIcon(KdbxCustomIcon customIcon) { + if (_customIcons.containsKey(customIcon.uuid)) { + return; + } + modify(() => _customIcons[customIcon.uuid] = customIcon); + } + StringNode get generator => StringNode(this, 'Generator'); StringNode get databaseName => StringNode(this, 'DatabaseName'); @@ -62,6 +93,8 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { DateTimeUtcNode get recycleBinChanged => DateTimeUtcNode(this, 'RecycleBinChanged'); +// void addCustomIcon + @override xml.XmlElement toXml() { final ret = super.toXml()..replaceSingle(customData.toXml()); @@ -80,6 +113,26 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext { ), ); } + XmlUtils.removeChildrenByName(ret, KdbxXml.NODE_CUSTOM_ICONS); + ret.children.add( + XmlElement(XmlName(KdbxXml.NODE_CUSTOM_ICONS)) + ..children.addAll(customIcons.values.map( + (e) => XmlUtils.createNode(KdbxXml.NODE_ICON, [ + XmlUtils.createTextNode(KdbxXml.NODE_UUID, e.uuid.uuid), + XmlUtils.createTextNode(KdbxXml.NODE_DATA, base64.encode(e.data)) + ]), + )), + ); return ret; } } + +class KdbxCustomIcon { + KdbxCustomIcon({this.uuid, this.data}); + + /// uuid of the icon, must be unique within each file. + final KdbxUuid uuid; + + /// Encoded png data of the image. will be base64 encoded into the kdbx file. + final Uint8List data; +} diff --git a/lib/src/kdbx_object.dart b/lib/src/kdbx_object.dart index 5aacad0..f77d319 100644 --- a/lib/src/kdbx_object.dart +++ b/lib/src/kdbx_object.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:kdbx/kdbx.dart'; +import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/kdbx_file.dart'; import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_times.dart'; @@ -126,14 +127,27 @@ abstract class KdbxObject extends KdbxNode { KdbxUuid get uuid => _uuid.get(); - UuidNode get _uuid => UuidNode(this, 'UUID'); + UuidNode get _uuid => UuidNode(this, KdbxXml.NODE_UUID); IconNode get icon => IconNode(this, 'IconID'); + UuidNode get customIconUuid => UuidNode(this, 'CustomIconUUID'); + KdbxGroup get parent => _parent; KdbxGroup _parent; + KdbxCustomIcon get customIcon => + customIconUuid.get()?.let((uuid) => file.body.meta.customIcons[uuid]); + + set customIcon(KdbxCustomIcon icon) { + if (icon != null) { + file.body.meta.addCustomIcon(icon); + customIconUuid.set(icon.uuid); + } + customIconUuid.set(null); + } + @override void onAfterModify() { super.onAfterModify(); diff --git a/lib/src/kdbx_xml.dart b/lib/src/kdbx_xml.dart index e93645b..8a6b191 100644 --- a/lib/src/kdbx_xml.dart +++ b/lib/src/kdbx_xml.dart @@ -19,6 +19,16 @@ class KdbxXml { static const ATTR_ID = 'ID'; static const NODE_BINARY = 'Binary'; static const ATTR_REF = 'Ref'; + static const NODE_CUSTOM_ICONS = 'CustomIcons'; + + /// CustomIcons >> Icon + static const NODE_ICON = 'Icon'; + + /// CustomIcons >> Icon >> Data + static const NODE_DATA = 'Data'; + + /// Used for objects UUID and CustomIcons + static const NODE_UUID = 'UUID'; static const NODE_CUSTOM_DATA_ITEM = 'Item'; diff --git a/test/icon/icontest.kdbx b/test/icon/icontest.kdbx new file mode 100644 index 0000000..845288c Binary files /dev/null and b/test/icon/icontest.kdbx differ diff --git a/test/icon/kdbx_customicon_test.dart b/test/icon/kdbx_customicon_test.dart new file mode 100644 index 0000000..d6462f2 --- /dev/null +++ b/test/icon/kdbx_customicon_test.dart @@ -0,0 +1,13 @@ +import 'package:logging_appenders/logging_appenders.dart'; +import 'package:test/test.dart'; + +import '../internal/test_utils.dart'; + +void main() { + PrintAppender.setupLogging(); + test('load custom icons from file', () async { + final file = await TestUtil.readKdbxFile('test/icon/icontest.kdbx'); + final entry = file.body.rootGroup.entries.first; + expect(entry.customIcon.data, isNotNull); + }); +}