From 2484f1a8e83e87f74e3700ffaa211ad06082eec2 Mon Sep 17 00:00:00 2001 From: Herbert Poul Date: Thu, 13 Aug 2020 13:06:02 +0200 Subject: [PATCH] support for custom icons for entries and groups. --- CHANGELOG.md | 4 +++ bin/kdbx.dart | 4 --- example/pubspec.lock | 2 +- lib/src/kdbx_meta.dart | 53 ++++++++++++++++++++++++++++ lib/src/kdbx_object.dart | 16 ++++++++- lib/src/kdbx_xml.dart | 10 ++++++ test/icon/icontest.kdbx | Bin 0 -> 2645 bytes test/icon/kdbx_customicon_test.dart | 13 +++++++ 8 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 test/icon/icontest.kdbx create mode 100644 test/icon/kdbx_customicon_test.dart 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 0000000000000000000000000000000000000000..845288ca175f8de48cdac0e85126d5b2b4233cc5 GIT binary patch literal 2645 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