Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
luckyrat committed Feb 16, 2024
1 parent 77820dc commit 35e1f27
Show file tree
Hide file tree
Showing 14 changed files with 905 additions and 324 deletions.
6 changes: 5 additions & 1 deletion lib/kdbx.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
/// dart library for reading keepass file format (kdbx).
library kdbx;

import 'package:kdbx/src/kee_vault_model/enums.dart';
import 'package:kdbx/src/kee_vault_model/form_field_type.dart';

export 'src/credentials/credentials.dart'
show Credentials, CredentialsPart, HashCredentials, PasswordCredentials;
export 'src/credentials/keyfile.dart' show KeyFileComposite, KeyFileCredentials;
export 'src/crypto/key_encrypter_kdf.dart'
show KeyEncrypterKdf, KdfType, KdfField;
export 'src/crypto/protected_value.dart'
show ProtectedValue, StringValue, PlainValue;
export 'src/field.dart' show BrowserFieldModel, FormFieldType, FieldStorage;
export 'src/kee_vault_model/kee_vault_model.dart'
show BrowserFieldModel, FormFieldType, FieldStorage;
export 'src/internal/kdf_cache.dart' show KdfCache;
export 'src/kdbx_binary.dart' show KdbxBinary;
export 'src/kdbx_consts.dart';
Expand Down
310 changes: 8 additions & 302 deletions lib/src/kdbx_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:collection';
import 'dart:convert';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:kdbx/src/kee_vault_model/browser_entry_settings_v1.dart';
import 'package:kdbx/src/kee_vault_model/kee_vault_model.dart';
import 'package:kdbx/src/internal/extension_utils.dart';
import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_object.dart';
Expand Down Expand Up @@ -72,302 +74,6 @@ class KdbxKey {
}
}

class BrowserEntrySettings {
BrowserEntrySettings({
this.version = 1,
this.behaviour = BrowserAutoFillBehaviour.Default,
required this.minimumMatchAccuracy,
this.priority = 0,
this.hide = false,
this.realm = '',
List<Pattern>? includeUrls,
List<Pattern>? excludeUrls,
List<BrowserFieldModel>? fields,
}) : includeUrls = includeUrls ?? [],
excludeUrls = excludeUrls ?? [],
fields = fields ?? [];

factory BrowserEntrySettings.fromMap(Map<String, dynamic>? map,
{required MatchAccuracy minimumMatchAccuracy}) {
if (map == null) {
return BrowserEntrySettings(minimumMatchAccuracy: minimumMatchAccuracy);
}

return BrowserEntrySettings(
version: map['version'] as int? ?? 1,
behaviour: getBehaviour(map),
minimumMatchAccuracy: getMam(map),
priority: map['priority'] as int? ?? 0,
hide: map['hide'] as bool? ?? false,
realm: map['hTTPRealm'] as String?,
includeUrls: getIncludeUrls(map),
excludeUrls: getExcludeUrls(map),
fields: List<BrowserFieldModel>.from((map['formFieldList']
as List<dynamic>?)
?.cast<Map<String, dynamic>>()
.map<BrowserFieldModel>((x) => BrowserFieldModel.fromMap(x)) ??
<BrowserFieldModel>[]),
);
}

factory BrowserEntrySettings.fromJson(String source,
{required MatchAccuracy minimumMatchAccuracy}) =>
BrowserEntrySettings.fromMap(json.decode(source) as Map<String, dynamic>?,
minimumMatchAccuracy: minimumMatchAccuracy);

int version;
// enum
BrowserAutoFillBehaviour behaviour;
// enum
MatchAccuracy minimumMatchAccuracy;
int priority; // always 0
bool hide;
String? realm;
List<Pattern> includeUrls;
List<Pattern> excludeUrls;
List<BrowserFieldModel> fields;

BrowserEntrySettings copyWith({
int? version,
BrowserAutoFillBehaviour? behaviour,
MatchAccuracy? minimumMatchAccuracy,
int? priority,
bool? hide,
String? realm,
List<Pattern>? includeUrls,
List<Pattern>? excludeUrls,
List<BrowserFieldModel>? fields,
}) {
return BrowserEntrySettings(
version: version ?? this.version,
behaviour: behaviour ?? this.behaviour,
minimumMatchAccuracy: minimumMatchAccuracy ?? this.minimumMatchAccuracy,
priority: priority ?? this.priority,
hide: hide ?? this.hide,
realm: realm ?? this.realm,
includeUrls: includeUrls ?? this.includeUrls,
excludeUrls: excludeUrls ?? this.excludeUrls,
fields: fields ?? this.fields,
);
}

static BrowserAutoFillBehaviour getBehaviour(Map<String, dynamic> map) {
if (map['neverAutoFill'] as bool? ?? false) {
return BrowserAutoFillBehaviour.NeverAutoFillNeverAutoSubmit;
} else if (map['alwaysAutoSubmit'] as bool? ?? false) {
return BrowserAutoFillBehaviour.AlwaysAutoFillAlwaysAutoSubmit;
} else if ((map['alwaysAutoFill'] as bool? ?? false) &&
(map['neverAutoSubmit'] as bool? ?? false)) {
return BrowserAutoFillBehaviour.AlwaysAutoFillNeverAutoSubmit;
} else if (map['neverAutoSubmit'] as bool? ?? false) {
return BrowserAutoFillBehaviour.NeverAutoSubmit;
} else if (map['alwaysAutoFill'] as bool? ?? false) {
return BrowserAutoFillBehaviour.AlwaysAutoFill;
} else {
return BrowserAutoFillBehaviour.Default;
}
}

static MatchAccuracy getMam(Map<String, dynamic> map) {
if (map['blockHostnameOnlyMatch'] as bool? ?? false) {
return MatchAccuracy.Exact;
} else if (map['blockDomainOnlyMatch'] as bool? ?? false) {
return MatchAccuracy.Hostname;
} else {
return MatchAccuracy.Domain;
}
}

static Map<String, bool> parseBehaviour(BrowserAutoFillBehaviour behaviour) {
switch (behaviour) {
case BrowserAutoFillBehaviour.AlwaysAutoFill:
return {
'alwaysAutoFill': true,
'alwaysAutoSubmit': false,
'neverAutoFill': false,
'neverAutoSubmit': false,
};
case BrowserAutoFillBehaviour.NeverAutoSubmit:
return {
'alwaysAutoFill': false,
'alwaysAutoSubmit': false,
'neverAutoFill': false,
'neverAutoSubmit': true,
};
case BrowserAutoFillBehaviour.AlwaysAutoFillAlwaysAutoSubmit:
return {
'alwaysAutoFill': true,
'alwaysAutoSubmit': true,
'neverAutoFill': false,
'neverAutoSubmit': false,
};
case BrowserAutoFillBehaviour.NeverAutoFillNeverAutoSubmit:
return {
'alwaysAutoFill': false,
'alwaysAutoSubmit': false,
'neverAutoFill': true,
'neverAutoSubmit': true,
};
case BrowserAutoFillBehaviour.AlwaysAutoFillNeverAutoSubmit:
return {
'alwaysAutoFill': true,
'alwaysAutoSubmit': false,
'neverAutoFill': false,
'neverAutoSubmit': true,
};
case BrowserAutoFillBehaviour.Default:
return {
'alwaysAutoFill': false,
'alwaysAutoSubmit': false,
'neverAutoFill': false,
'neverAutoSubmit': false,
};
}
}

static Map<String, bool> parseMam(MatchAccuracy mam) {
switch (mam) {
case MatchAccuracy.Domain:
return {
'blockDomainOnlyMatch': false,
'blockHostnameOnlyMatch': false,
};
case MatchAccuracy.Hostname:
return {
'blockDomainOnlyMatch': true,
'blockHostnameOnlyMatch': false,
};
default:
return {
'blockDomainOnlyMatch': false,
'blockHostnameOnlyMatch': true,
};
}
}

static Map<String, List<String>> parseUrls(
List<Pattern> includeUrls, List<Pattern> excludeUrls) {
final altURLs = <String>[];
final regExURLs = <String>[];
final blockedURLs = <String>[];
final regExBlockedURLs = <String>[];
for (final p in includeUrls) {
if (p is RegExp) {
regExURLs.add(p.pattern);
} else if (p is String) {
altURLs.add(p);
}
}
for (final p in excludeUrls) {
if (p is RegExp) {
regExBlockedURLs.add(p.pattern);
} else if (p is String) {
blockedURLs.add(p);
}
}
return <String, List<String>>{
'altURLs': altURLs,
'regExURLs': regExURLs,
'blockedURLs': blockedURLs,
'regExBlockedURLs': regExBlockedURLs,
};
}

static List<Pattern> getIncludeUrls(Map<String, dynamic> map) {
final includeUrls = <Pattern>[];
final altUrls = (map['altURLs'] as List<dynamic>?)?.cast<String>();
final regExURLs = (map['regExURLs'] as List<dynamic>?)?.cast<String>();
if (altUrls != null) {
altUrls.forEach(includeUrls.add);
}
if (regExURLs != null) {
for (final url in regExURLs) {
includeUrls.add(RegExp(url));
}
}
return includeUrls;
}

static List<Pattern> getExcludeUrls(Map<String, dynamic> map) {
final excludeUrls = <Pattern>[];
final blockedURLs = (map['blockedURLs'] as List<dynamic>?)?.cast<String>();
final regExBlockedURLs =
(map['regExBlockedURLs'] as List<dynamic>?)?.cast<String>();
if (blockedURLs != null) {
blockedURLs.forEach(excludeUrls.add);
}
if (regExBlockedURLs != null) {
for (final url in regExBlockedURLs) {
excludeUrls.add(RegExp(url));
}
}
return excludeUrls;
}

Map<String, dynamic> toMap() {
return <String, dynamic>{
'version': version,
'priority': priority,
'hide': hide,
'hTTPRealm': realm,
'formFieldList': fields.map((x) => x.toMap()).toList(),
...parseBehaviour(behaviour),
...parseMam(minimumMatchAccuracy),
...parseUrls(includeUrls, excludeUrls),
};
}

String toJson() => json.encode(toMap());

@override
String toString() {
return 'BrowserSettingsModel(version: $version, behaviour: $behaviour, minimumMatchAccuracy: $minimumMatchAccuracy, priority: $priority, hide: $hide, realm: $realm, includeUrls: $includeUrls, excludeUrls: $excludeUrls, fields: $fields)';
}

@override
// ignore: avoid_renaming_method_parameters
bool operator ==(Object o) {
if (identical(this, o)) {
return true;
}
final unOrdDeepEq = const DeepCollectionEquality.unordered().equals;
return o is BrowserEntrySettings &&
o.version == version &&
o.behaviour == behaviour &&
o.minimumMatchAccuracy == minimumMatchAccuracy &&
o.priority == priority &&
o.hide == hide &&
o.realm == realm &&
unOrdDeepEq(o.includeUrls, includeUrls) &&
unOrdDeepEq(o.excludeUrls, excludeUrls) &&
unOrdDeepEq(o.fields, fields);
}

@override
int get hashCode {
return version.hashCode ^
behaviour.hashCode ^
minimumMatchAccuracy.hashCode ^
priority.hashCode ^
hide.hashCode ^
realm.hashCode ^
includeUrls.hashCode ^
excludeUrls.hashCode ^
fields.hashCode;
}
}

enum BrowserAutoFillBehaviour {
Default,
AlwaysAutoFill,
NeverAutoSubmit,
AlwaysAutoFillNeverAutoSubmit,
AlwaysAutoFillAlwaysAutoSubmit,
NeverAutoFillNeverAutoSubmit
}

enum MatchAccuracy { Exact, Hostname, Domain }

extension KdbxEntryInternal on KdbxEntry {
KdbxEntry cloneInto(KdbxGroup otherGroup,
{bool toHistoryEntry = false, bool withNewUuid = false}) =>
Expand Down Expand Up @@ -461,7 +167,7 @@ class KdbxEntry extends KdbxObject {
}) : history = [],
super.create(file.ctx, file, 'Entry', parent) {
icon.set(KdbxIcon.Key);
_browserSettings = BrowserEntrySettings(
_browserSettings = BrowserEntrySettingsV1(
minimumMatchAccuracy:
file.body.meta.browserSettings.defaultMatchAccuracy);
}
Expand Down Expand Up @@ -523,27 +229,27 @@ class KdbxEntry extends KdbxObject {
customData['KeeVault.AndroidPackageNames'] = json.encode(names);
}

BrowserEntrySettings? _browserSettings;
BrowserEntrySettings get browserSettings {
BrowserEntrySettingsV1? _browserSettings;
BrowserEntrySettingsV1 get browserSettings {
if (_browserSettings == null) {
final tempJson = stringEntries
.firstWhereOrNull((s) => s.key.key == 'KPRPC JSON')
?.value;

if (tempJson != null) {
_browserSettings = BrowserEntrySettings.fromJson(tempJson.getText(),
_browserSettings = BrowserEntrySettingsV1.fromJson(tempJson.getText(),
minimumMatchAccuracy:
file!.body.meta.browserSettings.defaultMatchAccuracy);
} else {
_browserSettings = BrowserEntrySettings(
_browserSettings = BrowserEntrySettingsV1(
minimumMatchAccuracy:
file!.body.meta.browserSettings.defaultMatchAccuracy);
}
}
return _browserSettings!;
}

set browserSettings(BrowserEntrySettings settings) {
set browserSettings(BrowserEntrySettingsV1 settings) {
setString(
KdbxKey('KPRPC JSON'), ProtectedValue.fromString(settings.toJson()));
_browserSettings = null;
Expand Down
3 changes: 3 additions & 0 deletions lib/src/kdbx_meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import 'package:kdbx/src/kdbx_format.dart';
import 'package:kdbx/src/kdbx_header.dart';
import 'package:kdbx/src/kdbx_object.dart';
import 'package:kdbx/src/kdbx_xml.dart';
import 'package:kdbx/src/kee_vault_model/enums.dart';
import 'package:logging/logging.dart';
import 'package:quiver/iterables.dart';
import 'package:uuid/uuid.dart';
import 'package:xml/xml.dart' as xml;
import 'package:xml/xml.dart';

import 'kee_vault_model/kee_vault_model.dart';

final _logger = Logger('kdbx_meta');

class KdbxMeta extends KdbxNode implements KdbxNodeContext {
Expand Down
Loading

0 comments on commit 35e1f27

Please sign in to comment.