Browse Source

support for custom icons for entries and groups.

pull/3/head
Herbert Poul 5 years ago
parent
commit
2484f1a8e8
  1. 4
      CHANGELOG.md
  2. 4
      bin/kdbx.dart
  3. 2
      example/pubspec.lock
  4. 53
      lib/src/kdbx_meta.dart
  5. 16
      lib/src/kdbx_object.dart
  6. 10
      lib/src/kdbx_xml.dart
  7. BIN
      test/icon/icontest.kdbx
  8. 13
      test/icon/kdbx_customicon_test.dart

4
CHANGELOG.md

@ -1,3 +1,7 @@
## Unreleased
- Implemented support for custom icons.
## 0.4.1 ## 0.4.1
- fix bug saving files with history entries which contain attachments. - fix bug saving files with history entries which contain attachments.

4
bin/kdbx.dart

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'package:argon2_ffi_base/argon2_ffi_base.dart'; import 'package:argon2_ffi_base/argon2_ffi_base.dart';
@ -17,9 +16,6 @@ final _logger = Logger('kdbx');
void main(List<String> arguments) { void main(List<String> arguments) {
exitCode = 0; exitCode = 0;
final path = Platform.script.resolve('../argon2_ffi_plugin.dll').toFilePath();
print('loading $path');
DynamicLibrary.open(path);
final runner = KdbxCommandRunner('kdbx', 'Kdbx Utility'); final runner = KdbxCommandRunner('kdbx', 'Kdbx Utility');
runner.run(arguments).catchError((dynamic error, StackTrace stackTrace) { runner.run(arguments).catchError((dynamic error, StackTrace stackTrace) {
if (error is! UsageException) { if (error is! UsageException) {

2
example/pubspec.lock

@ -112,7 +112,7 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.4.0+1" version: "0.4.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:

53
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/kdbx.dart';
import 'package:kdbx/src/internal/extension_utils.dart'; import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/kdbx_binary.dart'; import 'package:kdbx/src/kdbx_binary.dart';
@ -17,6 +21,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
String generator, String generator,
}) : customData = KdbxCustomData.create(), }) : customData = KdbxCustomData.create(),
binaries = [], binaries = [],
_customIcons = {},
super.create('Meta') { super.create('Meta') {
this.databaseName.set(databaseName); this.databaseName.set(databaseName);
this.generator.set(generator ?? 'kdbx.dart'); this.generator.set(generator ?? 'kdbx.dart');
@ -39,6 +44,20 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
yield KdbxBinary.readBinaryXml(binaryNode, isInline: false); yield KdbxBinary.readBinaryXml(binaryNode, isInline: false);
} }
})?.toList(), })?.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); super.read(node);
@override @override
@ -49,6 +68,18 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
/// only used in Kdbx 3 /// only used in Kdbx 3
final List<KdbxBinary> binaries; final List<KdbxBinary> binaries;
final Map<KdbxUuid, KdbxCustomIcon> _customIcons;
Map<KdbxUuid, KdbxCustomIcon> 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 generator => StringNode(this, 'Generator');
StringNode get databaseName => StringNode(this, 'DatabaseName'); StringNode get databaseName => StringNode(this, 'DatabaseName');
@ -62,6 +93,8 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
DateTimeUtcNode get recycleBinChanged => DateTimeUtcNode get recycleBinChanged =>
DateTimeUtcNode(this, 'RecycleBinChanged'); DateTimeUtcNode(this, 'RecycleBinChanged');
// void addCustomIcon
@override @override
xml.XmlElement toXml() { xml.XmlElement toXml() {
final ret = super.toXml()..replaceSingle(customData.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; 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;
}

16
lib/src/kdbx_object.dart

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:kdbx/kdbx.dart'; 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_file.dart';
import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_group.dart';
import 'package:kdbx/src/kdbx_times.dart'; import 'package:kdbx/src/kdbx_times.dart';
@ -126,14 +127,27 @@ abstract class KdbxObject extends KdbxNode {
KdbxUuid get uuid => _uuid.get(); 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'); IconNode get icon => IconNode(this, 'IconID');
UuidNode get customIconUuid => UuidNode(this, 'CustomIconUUID');
KdbxGroup get parent => _parent; KdbxGroup get parent => _parent;
KdbxGroup _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 @override
void onAfterModify() { void onAfterModify() {
super.onAfterModify(); super.onAfterModify();

10
lib/src/kdbx_xml.dart

@ -19,6 +19,16 @@ class KdbxXml {
static const ATTR_ID = 'ID'; static const ATTR_ID = 'ID';
static const NODE_BINARY = 'Binary'; static const NODE_BINARY = 'Binary';
static const ATTR_REF = 'Ref'; 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'; static const NODE_CUSTOM_DATA_ITEM = 'Item';

BIN
test/icon/icontest.kdbx

Binary file not shown.

13
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);
});
}
Loading…
Cancel
Save