Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
luckyrat committed Feb 9, 2024
1 parent f8f29da commit 4c280b3
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 54 deletions.
79 changes: 74 additions & 5 deletions lib/src/kdbx_custom_data.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_object.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:xml/xml.dart' as xml;
import 'package:collection/collection.dart';

//TODO: create KdbxFileCustomData with date too
class KdbxCustomData extends KdbxNode {
KdbxCustomData.create()
class KdbxObjectCustomData extends KdbxNode {
KdbxObjectCustomData.create()
: _data = {},
super.create(TAG_NAME);

KdbxCustomData.read(xml.XmlElement node)
KdbxObjectCustomData.read(xml.XmlElement node)
: _data = Map.fromEntries(
node.findElements(KdbxXml.NODE_CUSTOM_DATA_ITEM).map((el) {
final key = el.singleTextNode(KdbxXml.NODE_KEY);
Expand Down Expand Up @@ -46,7 +47,75 @@ class KdbxCustomData extends KdbxNode {
return el;
}

void overwriteFrom(KdbxCustomData other) {
void overwriteFrom(KdbxObjectCustomData other) {
_data.clear();
_data.addAll(other._data);
}
}

typedef KdbxMetaCustomDataItem = ({
String value,
DateTime? lastModified,
});

class KdbxMetaCustomData extends KdbxNode {
KdbxMetaCustomData.create()
: _data = {},
super.create(TAG_NAME);

KdbxMetaCustomData.read(xml.XmlElement node)
: _data = Map.fromEntries(
node.findElements(KdbxXml.NODE_CUSTOM_DATA_ITEM).map((el) {
final key = el.singleTextNode(KdbxXml.NODE_KEY);
final value = el.singleTextNode(KdbxXml.NODE_VALUE);
final lastModified =
el.singleElement(KdbxXml.NODE_LAST_MODIFICATION_TIME)?.innerText;
return MapEntry(key, (
value: value,
lastModified: lastModified != null
? DateTimeUtils.fromBase64(lastModified)
: null
));
})),
super.read(node);

static const String TAG_NAME = KdbxXml.NODE_CUSTOM_DATA;

final Map<String, KdbxMetaCustomDataItem> _data;

Iterable<MapEntry<String, KdbxMetaCustomDataItem>> get entries =>
_data.entries;

KdbxMetaCustomDataItem? operator [](String key) => _data[key];
void operator []=(String key, KdbxMetaCustomDataItem value) {
modify(() => _data[key] = value);
}

bool containsKey(String key) => _data.containsKey(key);
KdbxMetaCustomDataItem? remove(String key) => _data.remove(key);

@override
xml.XmlElement toXml() {
final el = super.toXml();
el.children.clear();
el.children.addAll(
_data.entries.map((e) {
final d = e.value.lastModified != null
? DateTimeUtils.toBase64(e.value.lastModified!)
: null;

return XmlUtils.createNode(KdbxXml.NODE_CUSTOM_DATA_ITEM, [
XmlUtils.createTextNode(KdbxXml.NODE_KEY, e.key),
XmlUtils.createTextNode(KdbxXml.NODE_VALUE, e.value.value),
if (d != null)
XmlUtils.createTextNode(KdbxXml.NODE_LAST_MODIFICATION_TIME, d),
]);
}),
);
return el;
}

void overwriteFrom(KdbxMetaCustomData other) {
_data.clear();
_data.addAll(other._data);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/kdbx_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ extension KdbxEntryInternal on KdbxEntry {
foregroundColor,
backgroundColor,
overrideURL,
tags,
qualityCheck,
];

void _overwriteFrom(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/kdbx_group.dart
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,13 @@ class KdbxGroup extends KdbxObject {
enableAutoType,
enableSearching,
lastTopVisibleEntry,
//TODO: More fields for 4.1?
];

void _overwriteFrom(OverwriteContext mergeContext, KdbxGroup other) {
overwriteSubNodesFrom(mergeContext, _overwriteNodes, other._overwriteNodes);
// we should probably check that [lastTopVisibleEntry] is still a
// valid reference?
customData.overwriteFrom(other.customData);
times.overwriteFrom(other.times);
}

Expand Down
19 changes: 11 additions & 8 deletions lib/src/kdbx_meta.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/kdbx_binary.dart';
Expand All @@ -24,7 +25,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
required String databaseName,
required this.ctx,
String? generator,
}) : customData = KdbxCustomData.create(),
}) : customData = KdbxMetaCustomData.create(),
binaries = [],
_customIcons = {},
super.create('Meta') {
Expand All @@ -42,8 +43,8 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
KdbxMeta.read(xml.XmlElement node, this.ctx)
: customData = node
.singleElement(KdbxXml.NODE_CUSTOM_DATA)
?.let((e) => KdbxCustomData.read(e)) ??
KdbxCustomData.create(),
?.let((e) => KdbxMetaCustomData.read(e)) ??
KdbxMetaCustomData.create(),
binaries = node
.singleElement(KdbxXml.NODE_BINARIES)
?.let((el) sync* {
Expand Down Expand Up @@ -86,7 +87,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
@override
final KdbxReadWriteContext ctx;

final KdbxCustomData customData;
final KdbxMetaCustomData customData;

/// only used in Kdbx 3
final List<KdbxBinary>? binaries;
Expand Down Expand Up @@ -162,7 +163,7 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
BrowserDbSettings? _browserSettings;
BrowserDbSettings get browserSettings {
if (_browserSettings == null) {
final tempJson = customData['KeePassRPC.Config'];
final tempJson = customData['KeePassRPC.Config']?.value;

if (tempJson != null) {
_browserSettings = BrowserDbSettings.fromJson(tempJson);
Expand All @@ -174,14 +175,15 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
}

set browserSettings(BrowserDbSettings settings) {
customData['KeePassRPC.Config'] = settings.toJson();
customData['KeePassRPC.Config'] =
(value: settings.toJson(), lastModified: clock.now().toUtc());
settingsChanged.setToNow();
}

KeeVaultEmbeddedConfig? _keeVaultSettings;
KeeVaultEmbeddedConfig get keeVaultSettings {
if (_keeVaultSettings == null) {
final tempJson = customData['KeeVault.Config'];
final tempJson = customData['KeeVault.Config']?.value;

if (tempJson != null) {
_keeVaultSettings = KeeVaultEmbeddedConfig.fromJson(tempJson);
Expand All @@ -193,7 +195,8 @@ class KdbxMeta extends KdbxNode implements KdbxNodeContext {
}

set keeVaultSettings(KeeVaultEmbeddedConfig settings) {
customData['KeeVault.Config'] = settings.toJson();
customData['KeeVault.Config'] =
(value: settings.toJson(), lastModified: clock.now().toUtc());
settingsChanged.setToNow();
}

Expand Down
16 changes: 7 additions & 9 deletions lib/src/kdbx_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,8 @@ extension UnmodifiableMapViewKdbxObject<K extends String, V extends KdbxObject>
}

extension KdbxObjectInternal on KdbxObject {
List<KdbxSubNode<dynamic>> get objectNodes => [
icon,
customIconUuid,
];
List<KdbxSubNode<dynamic>> get objectNodes =>
[icon, customIconUuid, previousParentGroup, tags];

/// should only be used in internal code, used to clone
/// from one kdbx file into another. (like merging).
Expand Down Expand Up @@ -180,7 +178,7 @@ abstract class KdbxObject extends KdbxNode {
this.file,
String nodeName,
KdbxGroup? parent,
) : customData = KdbxCustomData.create(),
) : customData = KdbxObjectCustomData.create(),
times = KdbxTimes.create(ctx),
_parent = parent,
super.create(nodeName) {
Expand All @@ -190,8 +188,8 @@ abstract class KdbxObject extends KdbxNode {
KdbxObject.read(this.ctx, KdbxGroup? parent, XmlElement node)
: customData = node
.singleElement(KdbxXml.NODE_CUSTOM_DATA)
?.let((e) => KdbxCustomData.read(e)) ??
KdbxCustomData.create(),
?.let((e) => KdbxObjectCustomData.read(e)) ??
KdbxObjectCustomData.create(),
times = KdbxTimes.read(node.findElements('Times').single, ctx),
_parent = parent,
super.read(node);
Expand Down Expand Up @@ -222,7 +220,7 @@ abstract class KdbxObject extends KdbxNode {
StringListNode get tags => StringListNode(this, KdbxXml.NODE_TAGS);

@protected
final KdbxCustomData customData;
final KdbxObjectCustomData customData;

String? getCustomData(String key) => customData[key];

Expand All @@ -232,7 +230,7 @@ abstract class KdbxObject extends KdbxNode {
return;
}
// We have to call modify from here to ensure the correct overload of
// onAfterModify gets called. Otherwise direct changes to a KdbxCustomData
// onAfterModify gets called. Otherwise direct changes to a KdbxObjectCustomData
// node will not affect the modification date of the entry that contains that node.
modify(() {
if (value == null) {
Expand Down
37 changes: 23 additions & 14 deletions lib/src/kdbx_xml.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class KdbxXml {
static const NODE_BACKGROUND_COLOR = 'BackgroundColor';
static const NODE_FOREGROUND_COLOR = 'ForegroundColor';
static const NODE_TAGS = 'Tags';
static const NODE_LAST_MODIFICATION_TIME = 'LastModificationTime';

/// CustomIcons >> Icon
static const NODE_ICON = 'Icon';
Expand Down Expand Up @@ -275,8 +276,6 @@ class NullableBooleanNode extends KdbxSubTextNode<bool?> {
class DateTimeUtcNode extends KdbxSubTextNode<DateTime?> {
DateTimeUtcNode(KdbxNodeContext node, String name) : super(node, name);

static const EpochSeconds = 62135596800;

KdbxReadWriteContext get _ctx => (node as KdbxNodeContext).ctx;
static final minDate = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);

Expand All @@ -298,13 +297,7 @@ class DateTimeUtcNode extends KdbxSubTextNode<DateTime?> {
return DateTime.parse(value);
}
// kdbx 4.x uses base64 encoded date.
final decoded = base64.decode(value);

final secondsFrom00 = ReaderHelper(decoded).readUint64();

return DateTime.fromMillisecondsSinceEpoch(
(secondsFrom00 - EpochSeconds) * 1000,
isUtc: true);
return DateTimeUtils.fromBase64(value);
} catch (e, stackTrace) {
_logger.severe(
'Error while parsing time for {$name}: {$value}', e, stackTrace);
Expand All @@ -317,11 +310,7 @@ class DateTimeUtcNode extends KdbxSubTextNode<DateTime?> {
assert(value!.isUtc);
if (_ctx.versionMajor >= 4) {
// for kdbx v4 we need to support binary/base64
final secondsFrom00 =
(value!.millisecondsSinceEpoch ~/ 1000) + EpochSeconds;
final encoded = base64.encode(
(WriterHelper()..writeUint64(secondsFrom00)).output.toBytes());
return encoded;
return DateTimeUtils.toBase64(value!);
}
return DateTimeUtils.toIso8601StringSeconds(value!);
}
Expand All @@ -344,6 +333,26 @@ class XmlUtils {
}

class DateTimeUtils {
static const EpochSeconds = 62135596800;
static String toBase64(DateTime dateTime) {
final secondsFrom00 =
(dateTime.millisecondsSinceEpoch ~/ 1000) + EpochSeconds;
final encoded = base64
.encode((WriterHelper()..writeUint64(secondsFrom00)).output.toBytes());

return encoded;
}

static DateTime fromBase64(String value) {
final decoded = base64.decode(value);

final secondsFrom00 = ReaderHelper(decoded).readUint64();

return DateTime.fromMillisecondsSinceEpoch(
(secondsFrom00 - EpochSeconds) * 1000,
isUtc: true);
}

static String toIso8601StringSeconds(DateTime dateTime) {
final y = _fourDigits(dateTime.year);
final m = _twoDigits(dateTime.month);
Expand Down
Loading

0 comments on commit 4c280b3

Please sign in to comment.