From a41901198d3a23f9bae6b6cdc47f225dd3f1d747 Mon Sep 17 00:00:00 2001 From: Herbert Poul Date: Fri, 23 Aug 2019 14:44:18 +0200 Subject: [PATCH] parse kdbx file without futures. --- bin/kdbx.dart | 2 +- lib/src/kdbx_entry.dart | 8 ++++++-- lib/src/kdbx_format.dart | 4 ++-- lib/src/kdbx_header.dart | 35 +++++++++++++++++++++++------------ pubspec.yaml | 2 ++ 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/bin/kdbx.dart b/bin/kdbx.dart index 96b6c61..a7d119a 100644 --- a/bin/kdbx.dart +++ b/bin/kdbx.dart @@ -74,7 +74,7 @@ abstract class KdbxFileCommand extends Command { final bytes = await File(inputFile).readAsBytes() as Uint8List; final password = prompts.get('Password for $inputFile', conceal: true, validate: (str) => str.isNotEmpty); - final file = await KdbxFormat.read( + final file = KdbxFormat.read( bytes, Credentials(ProtectedValue.fromString(password))); return runWithFile(file); } diff --git a/lib/src/kdbx_entry.dart b/lib/src/kdbx_entry.dart index 6ed264e..24e2507 100644 --- a/lib/src/kdbx_entry.dart +++ b/lib/src/kdbx_entry.dart @@ -1,12 +1,15 @@ +import 'package:collection/collection.dart'; import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/kdbx_format.dart'; import 'package:kdbx/src/kdbx_group.dart'; import 'package:kdbx/src/kdbx_object.dart'; import 'package:xml/xml.dart'; +String _canonicalizeKey(String key) => key?.toLowerCase(); + class KdbxEntry extends KdbxObject { KdbxEntry.read(this.parent, XmlElement node) : super.read(node) { - strings = Map.fromEntries(node.findElements('String').map((el) { + strings.addEntries(node.findElements('String').map((el) { final key = el.findElements('Key').single.text; final valueNode = el.findElements('Value').single; if (valueNode.getAttribute('Protected')?.toLowerCase() == 'true') { @@ -18,7 +21,8 @@ class KdbxEntry extends KdbxObject { } KdbxGroup parent; - Map strings; + Map strings = + CanonicalizedMap(_canonicalizeKey); String _plainValue(String key) { final value = strings[key]; diff --git a/lib/src/kdbx_format.dart b/lib/src/kdbx_format.dart index 440e545..b17b471 100644 --- a/lib/src/kdbx_format.dart +++ b/lib/src/kdbx_format.dart @@ -61,9 +61,9 @@ class KdbxMeta extends KdbxNode { } class KdbxFormat { - static Future read(Uint8List input, Credentials credentials) async { + static KdbxFile read(Uint8List input, Credentials credentials) { final reader = ReaderHelper(input); - final header = await KdbxHeader.read(reader); + final header = KdbxHeader.read(reader); return _loadV3(header, reader, credentials); } diff --git a/lib/src/kdbx_header.dart b/lib/src/kdbx_header.dart index bb0dcb2..352493e 100644 --- a/lib/src/kdbx_header.dart +++ b/lib/src/kdbx_header.dart @@ -1,8 +1,6 @@ import 'dart:typed_data'; -import 'package:convert/convert.dart' as convert; import 'package:crypto/crypto.dart' as crypto; -import 'package:kdbx/src/crypto/protected_value.dart'; import 'package:kdbx/src/internal/byte_utils.dart'; import 'package:logging/logging.dart'; @@ -51,14 +49,21 @@ class HeaderField { } class KdbxHeader { - KdbxHeader({this.sig1, this.sig2, this.versionMinor, this.versionMajor, this.fields}); - - static Future read(ReaderHelper reader) async { + KdbxHeader( + {this.sig1, + this.sig2, + this.versionMinor, + this.versionMajor, + this.fields}); + + static KdbxHeader read(ReaderHelper reader) { // reading signature final sig1 = reader.readUint32(); final sig2 = reader.readUint32(); if (!(sig1 == Consts.FileMagic && sig2 == Consts.Sig2Kdbx)) { - throw UnsupportedError('Unsupported file structure. ${ByteUtils.toHex(sig1)}, ${ByteUtils.toHex(sig2)}'); + throw UnsupportedError( + 'Unsupported file structure. ${ByteUtils.toHex(sig1)}, ' + '${ByteUtils.toHex(sig2)}'); } // reading version @@ -66,7 +71,8 @@ class KdbxHeader { final versionMajor = reader.readUint16(); _logger.finer('Reading version: $versionMajor.$versionMinor'); - final headerFields = Map.fromEntries(readField(reader, versionMajor).map((field) => MapEntry(field.field, field))); + final headerFields = Map.fromEntries(readField(reader, versionMajor) + .map((field) => MapEntry(field.field, field))); return KdbxHeader( sig1: sig1, sig2: sig2, @@ -76,10 +82,12 @@ class KdbxHeader { ); } - static Iterable readField(ReaderHelper reader, int versionMajor) sync* { + static Iterable readField( + ReaderHelper reader, int versionMajor) sync* { while (true) { final headerId = reader.readUint8(); - final int bodySize = versionMajor >= 4 ? reader.readUint32() : reader.readUint16(); + final int bodySize = + versionMajor >= 4 ? reader.readUint32() : reader.readUint16(); _logger.finer('Read header ${HeaderFields.values[headerId]}'); final bodyBytes = bodySize > 0 ? reader.readBytes(bodySize) : null; if (headerId > 0) { @@ -108,7 +116,8 @@ class KdbxHeader { } PotectedValueEncryption get innerRandomStreamEncryption => - PotectedValueEncryption.values[fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single]; + PotectedValueEncryption.values[ + fields[HeaderFields.InnerRandomStreamID].bytes.asUint32List().single]; } class KdbxException implements Exception {} @@ -134,7 +143,8 @@ class HashedBlockReader { final blockSize = reader.readUint32(); if (blockSize > 0) { final blockData = reader.readBytes(blockSize).asUint8List(); - if (!ByteUtils.eq(crypto.sha256.convert(blockData).bytes as Uint8List, blockHash.asUint8List())) { + if (!ByteUtils.eq(crypto.sha256.convert(blockData).bytes as Uint8List, + blockHash.asUint8List())) { throw KdbxCorruptedFileException(); } yield blockData; @@ -151,7 +161,8 @@ class ReaderHelper { final Uint8List data; int pos = 0; - ByteBuffer _nextByteBuffer(int byteCount) => (data.sublist(pos, pos += byteCount) as Uint8List).buffer; + ByteBuffer _nextByteBuffer(int byteCount) => + (data.sublist(pos, pos += byteCount) as Uint8List).buffer; int readUint32() => _nextByteBuffer(4).asUint32List().first; diff --git a/pubspec.yaml b/pubspec.yaml index ee04939..1e27f9d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,8 @@ dependencies: uuid: '>=2.0.0 <3.0.0' meta: '>=1.0.0 <2.0.0' + collection: '>=1.14.0 <2.0.0' + # required for bin/ args: '>1.5.0 <2.0.0' prompts: '>=1.3.0 <2.0.0'