using Microsoft.Extensions.Logging; using System.ComponentModel; using System.Text.Json; using System.Text.Json.Serialization; using System.Xml; using System.Xml.Linq; using System.Xml.Serialization; namespace File_Folder_Helper.ADO2024.PI1; #pragma warning disable IDE1006, CS8618 [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] [XmlRoot(Namespace = "", IsNullable = false)] public partial class KeePassFile { private KeePassFileMeta metaField; private KeePassFileRoot rootField; public KeePassFileMeta Meta { get => metaField; set => metaField = value; } public KeePassFileRoot Root { get => rootField; set => rootField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileMeta { private string generatorField; private DateTime settingsChangedField; private object databaseNameField; private DateTime databaseNameChangedField; private object databaseDescriptionField; private DateTime databaseDescriptionChangedField; private object defaultUserNameField; private DateTime defaultUserNameChangedField; private ushort maintenanceHistoryDaysField; private object colorField; private DateTime masterKeyChangedField; private sbyte masterKeyChangeRecField; private sbyte masterKeyChangeForceField; private KeePassFileMetaMemoryProtection memoryProtectionField; private KeePassFileMetaIcon[] customIconsField; private string recycleBinEnabledField; private string recycleBinUUIDField; private DateTime recycleBinChangedField; private string entryTemplatesGroupField; private DateTime entryTemplatesGroupChangedField; private byte historyMaxItemsField; private uint historyMaxSizeField; private string lastSelectedGroupField; private string lastTopVisibleGroupField; private object binariesField; private object customDataField; public string Generator { get => generatorField; set => generatorField = value; } public DateTime SettingsChanged { get => settingsChangedField; set => settingsChangedField = value; } public object DatabaseName { get => databaseNameField; set => databaseNameField = value; } public DateTime DatabaseNameChanged { get => databaseNameChangedField; set => databaseNameChangedField = value; } public object DatabaseDescription { get => databaseDescriptionField; set => databaseDescriptionField = value; } public DateTime DatabaseDescriptionChanged { get => databaseDescriptionChangedField; set => databaseDescriptionChangedField = value; } public object DefaultUserName { get => defaultUserNameField; set => defaultUserNameField = value; } public DateTime DefaultUserNameChanged { get => defaultUserNameChangedField; set => defaultUserNameChangedField = value; } public ushort MaintenanceHistoryDays { get => maintenanceHistoryDaysField; set => maintenanceHistoryDaysField = value; } public object Color { get => colorField; set => colorField = value; } public DateTime MasterKeyChanged { get => masterKeyChangedField; set => masterKeyChangedField = value; } public sbyte MasterKeyChangeRec { get => masterKeyChangeRecField; set => masterKeyChangeRecField = value; } public sbyte MasterKeyChangeForce { get => masterKeyChangeForceField; set => masterKeyChangeForceField = value; } public KeePassFileMetaMemoryProtection MemoryProtection { get => memoryProtectionField; set => memoryProtectionField = value; } [XmlArrayItem("Icon", IsNullable = false)] public KeePassFileMetaIcon[] CustomIcons { get => customIconsField; set => customIconsField = value; } public string RecycleBinEnabled { get => recycleBinEnabledField; set => recycleBinEnabledField = value; } public string RecycleBinUUID { get => recycleBinUUIDField; set => recycleBinUUIDField = value; } public DateTime RecycleBinChanged { get => recycleBinChangedField; set => recycleBinChangedField = value; } public string EntryTemplatesGroup { get => entryTemplatesGroupField; set => entryTemplatesGroupField = value; } public DateTime EntryTemplatesGroupChanged { get => entryTemplatesGroupChangedField; set => entryTemplatesGroupChangedField = value; } public byte HistoryMaxItems { get => historyMaxItemsField; set => historyMaxItemsField = value; } public uint HistoryMaxSize { get => historyMaxSizeField; set => historyMaxSizeField = value; } public string LastSelectedGroup { get => lastSelectedGroupField; set => lastSelectedGroupField = value; } public string LastTopVisibleGroup { get => lastTopVisibleGroupField; set => lastTopVisibleGroupField = value; } public object Binaries { get => binariesField; set => binariesField = value; } public object CustomData { get => customDataField; set => customDataField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileMetaMemoryProtection { private string protectTitleField; private string protectUserNameField; private string protectPasswordField; private string protectURLField; private string protectNotesField; public string ProtectTitle { get => protectTitleField; set => protectTitleField = value; } public string ProtectUserName { get => protectUserNameField; set => protectUserNameField = value; } public string ProtectPassword { get => protectPasswordField; set => protectPasswordField = value; } public string ProtectURL { get => protectURLField; set => protectURLField = value; } public string ProtectNotes { get => protectNotesField; set => protectNotesField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileMetaIcon { private string uUIDField; private string dataField; private string nameField; private DateTime lastModificationTimeField; private bool lastModificationTimeFieldSpecified; public string UUID { get => uUIDField; set => uUIDField = value; } public string Data { get => dataField; set => dataField = value; } public string Name { get => nameField; set => nameField = value; } public DateTime LastModificationTime { get => lastModificationTimeField; set => lastModificationTimeField = value; } [XmlIgnore()] public bool LastModificationTimeSpecified { get => lastModificationTimeFieldSpecified; set => lastModificationTimeFieldSpecified = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileRoot { private KeePassFileGroup[] groupField; private KeePassFileGroupEntry[] entryField; private KeePassFileRootDeletedObject[] deletedObjectsField; [XmlElement("Group")] public KeePassFileGroup[] Group { get => groupField; set => groupField = value; } [XmlElement("Entry")] public KeePassFileGroupEntry[] Entry { get => entryField; set => entryField = value; } [XmlArrayItem("DeletedObject", IsNullable = false)] public KeePassFileRootDeletedObject[] DeletedObjects { get => deletedObjectsField; set => deletedObjectsField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroup { private string uUIDField; private string nameField; private object notesField; private byte iconIDField; private KeePassFileGroupTimes timesField; private string isExpandedField; private object defaultAutoTypeSequenceField; private string enableAutoTypeField; private string enableSearchingField; private KeePassFileGroup[] groupField; private KeePassFileGroupEntry[] entryField; public string UUID { get => uUIDField; set => uUIDField = value; } public string Name { get => nameField; set => nameField = value; } public object Notes { get => notesField; set => notesField = value; } public byte IconID { get => iconIDField; set => iconIDField = value; } public KeePassFileGroupTimes Times { get => timesField; set => timesField = value; } public string IsExpanded { get => isExpandedField; set => isExpandedField = value; } public object DefaultAutoTypeSequence { get => defaultAutoTypeSequenceField; set => defaultAutoTypeSequenceField = value; } public string EnableAutoType { get => enableAutoTypeField; set => enableAutoTypeField = value; } public string EnableSearching { get => enableSearchingField; set => enableSearchingField = value; } [XmlElement("Group")] public KeePassFileGroup[] Group { get => groupField; set => groupField = value; } [XmlElement("Entry")] public KeePassFileGroupEntry[] Entry { get => entryField; set => entryField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupTimes { private DateTime creationTimeField; private DateTime lastModificationTimeField; private DateTime expiryTimeField; private string expiresField; public DateTime CreationTime { get => creationTimeField; set => creationTimeField = value; } public DateTime LastModificationTime { get => lastModificationTimeField; set => lastModificationTimeField = value; } public DateTime ExpiryTime { get => expiryTimeField; set => expiryTimeField = value; } public string Expires { get => expiresField; set => expiresField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntry { private string uUIDField; private byte iconIDField; private object foregroundColorField; private object backgroundColorField; private object overrideURLField; private object tagsField; private KeePassFileGroupEntryTimes timesField; private KeePassFileGroupEntryString[] stringField; private KeePassFileGroupEntryAutoType autoTypeField; private KeePassFileGroupEntryEntry[] historyField; public string UUID { get => uUIDField; set => uUIDField = value; } public byte IconID { get => iconIDField; set => iconIDField = value; } public object ForegroundColor { get => foregroundColorField; set => foregroundColorField = value; } public object BackgroundColor { get => backgroundColorField; set => backgroundColorField = value; } public object OverrideURL { get => overrideURLField; set => overrideURLField = value; } public object Tags { get => tagsField; set => tagsField = value; } public KeePassFileGroupEntryTimes Times { get => timesField; set => timesField = value; } [XmlElement("String")] public KeePassFileGroupEntryString[] String { get => stringField; set => stringField = value; } public KeePassFileGroupEntryAutoType AutoType { get => autoTypeField; set => autoTypeField = value; } [XmlArrayItem("Entry", IsNullable = false)] public KeePassFileGroupEntryEntry[] History { get => historyField; set => historyField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryTimes { private DateTime creationTimeField; private DateTime lastModificationTimeField; private DateTime expiryTimeField; private string expiresField; public DateTime CreationTime { get => creationTimeField; set => creationTimeField = value; } public DateTime LastModificationTime { get => lastModificationTimeField; set => lastModificationTimeField = value; } public DateTime ExpiryTime { get => expiryTimeField; set => expiryTimeField = value; } public string Expires { get => expiresField; set => expiresField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryString { private string keyField; private KeePassFileGroupEntryStringValue valueField; public string Key { get => keyField; set => keyField = value; } public KeePassFileGroupEntryStringValue Value { get => valueField; set => valueField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryStringValue { private string protectInMemoryField; private string valueField; [XmlAttribute()] public string ProtectInMemory { get => protectInMemoryField; set => protectInMemoryField = value; } [XmlText()] public string Value { get => valueField; set => valueField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryAutoType { private string enabledField; private byte dataTransferObfuscationField; public string Enabled { get => enabledField; set => enabledField = value; } public byte DataTransferObfuscation { get => dataTransferObfuscationField; set => dataTransferObfuscationField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryEntry { private string uUIDField; private byte iconIDField; private object foregroundColorField; private object backgroundColorField; private object overrideURLField; private object tagsField; private KeePassFileGroupEntryEntryTimes timesField; private KeePassFileGroupEntryEntryString[] stringField; private KeePassFileGroupEntryEntryAutoType autoTypeField; public string UUID { get => uUIDField; set => uUIDField = value; } public byte IconID { get => iconIDField; set => iconIDField = value; } public object ForegroundColor { get => foregroundColorField; set => foregroundColorField = value; } public object BackgroundColor { get => backgroundColorField; set => backgroundColorField = value; } public object OverrideURL { get => overrideURLField; set => overrideURLField = value; } public object Tags { get => tagsField; set => tagsField = value; } public KeePassFileGroupEntryEntryTimes Times { get => timesField; set => timesField = value; } [XmlElement("String")] public KeePassFileGroupEntryEntryString[] String { get => stringField; set => stringField = value; } public KeePassFileGroupEntryEntryAutoType AutoType { get => autoTypeField; set => autoTypeField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryEntryTimes { private DateTime creationTimeField; private DateTime lastModificationTimeField; private DateTime expiryTimeField; private string expiresField; public DateTime CreationTime { get => creationTimeField; set => creationTimeField = value; } public DateTime LastModificationTime { get => lastModificationTimeField; set => lastModificationTimeField = value; } public DateTime ExpiryTime { get => expiryTimeField; set => expiryTimeField = value; } public string Expires { get => expiresField; set => expiresField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryEntryString { private string keyField; private KeePassFileGroupEntryEntryStringValue valueField; public string Key { get => keyField; set => keyField = value; } public KeePassFileGroupEntryEntryStringValue Value { get => valueField; set => valueField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryEntryStringValue { private string protectInMemoryField; private string valueField; [XmlAttribute()] public string ProtectInMemory { get => protectInMemoryField; set => protectInMemoryField = value; } [XmlText()] public string Value { get => valueField; set => valueField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileGroupEntryEntryAutoType { private string enabledField; private byte dataTransferObfuscationField; public string Enabled { get => enabledField; set => enabledField = value; } public byte DataTransferObfuscation { get => dataTransferObfuscationField; set => dataTransferObfuscationField = value; } } [Serializable()] [DesignerCategory("code")] [XmlType(AnonymousType = true)] public partial class KeePassFileRootDeletedObject { private string uUIDField; private DateTime deletionTimeField; public string UUID { get => uUIDField; set => uUIDField = value; } public DateTime DeletionTime { get => deletionTimeField; set => deletionTimeField = value; } } #pragma warning restore IDE1006, CS8618 internal static partial class Helper20240105 { // Folders with these names will be put in the root instead. private static readonly string[] _BlacklistedFolders = [ "KeePassHttp Passwords", "KeePassXC-Browser Passwords" ]; private static readonly string[] _BlacklistedFields = [ "KeePassXC-Browser Settings", "KeePassHttp Settings" ]; private record Field( [property: JsonPropertyName("name")] string? Name, [property: JsonPropertyName("value")] string? Value, [property: JsonPropertyName("type")] int? Type ); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Field))] private partial class FieldSourceGenerationContext : JsonSerializerContext { } private record Folder( [property: JsonPropertyName("id")] Guid? Id, [property: JsonPropertyName("name")] string? Name ); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Folder))] private partial class FolderSourceGenerationContext : JsonSerializerContext { } private record Item( [property: JsonPropertyName("revisionDate")] DateTime? RevisionDate, [property: JsonPropertyName("creationDate")] DateTime? CreationDate, [property: JsonPropertyName("folderId")] Guid? FolderId, [property: JsonPropertyName("folderName")] string? FolderName, [property: JsonPropertyName("type")] int Type, [property: JsonPropertyName("name")] string? Name, [property: JsonPropertyName("notes")] string? Notes, [property: JsonPropertyName("fields")] IReadOnlyList? Fields, [property: JsonPropertyName("login")] Login Login ); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Item))] private partial class ItemSourceGenerationContext : JsonSerializerContext { } private record Login( [property: JsonPropertyName("uris")] IReadOnlyList Uris, [property: JsonPropertyName("username")] string? Username, [property: JsonPropertyName("password")] string? Password ); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Login))] private partial class LoginSourceGenerationContext : JsonSerializerContext { } private record Root( [property: JsonPropertyName("folders")] IReadOnlyList Folders, [property: JsonPropertyName("items")] IReadOnlyList Items ); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Root))] private partial class RootSourceGenerationContext : JsonSerializerContext { } private record Uri( [property: JsonPropertyName("uri")] string? Value, [property: JsonPropertyName("host")] string? Host, [property: JsonPropertyName("port")] int? Port ); [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(Uri))] private partial class UriSourceGenerationContext : JsonSerializerContext { } private static int GetDeterministicHashCode(byte[] value) { int result; unchecked { int hash1 = (5381 << 16) + 5381; int hash2 = hash1; for (int i = 0; i < value.Length; i += 2) { hash1 = ((hash1 << 5) + hash1) ^ value[i]; if (i == value.Length - 1) break; hash2 = ((hash2 << 5) + hash2) ^ value[i + 1]; } result = hash1 + (hash2 * 1566083941); } return result; } private static string? ConvertPassword(string? value) => string.IsNullOrEmpty(value) ? "-" : GetDeterministicHashCode(System.Text.Encoding.ASCII.GetBytes(value)).ToString(); private static Item? GetEntry(Guid folderId, string folderName, KeePassFileGroupEntry keePassFileGroupEntry) { Item? result; string? name = keePassFileGroupEntry.String.Where(x => x.Key == "Title").Select(x => x.Value.Value).FirstOrDefault(); if (_BlacklistedFields.Contains(name)) result = null; else { int? port; string? host; string? uri = null; string? notes = null; string? password = null; string? username = null; DateTime creationTime = keePassFileGroupEntry.Times.CreationTime; DateTime revisionDate = keePassFileGroupEntry.Times.LastModificationTime; List keePassFileGroupEntryStrings = []; foreach (KeePassFileGroupEntryString keePassFileGroupEntryString in keePassFileGroupEntry.String) { if (keePassFileGroupEntryString.Key == "Title") continue; if (_BlacklistedFields.Contains(keePassFileGroupEntryString.Key)) continue; if (keePassFileGroupEntryString.Key == "URL") uri = keePassFileGroupEntryString.Value.Value; else if (keePassFileGroupEntryString.Key == "Notes") notes = keePassFileGroupEntryString.Value.Value; else if (keePassFileGroupEntryString.Key == "UserName") username = keePassFileGroupEntryString.Value.Value; else if (keePassFileGroupEntryString.Key == "Password") password = ConvertPassword(keePassFileGroupEntryString.Value.Value); else if (keePassFileGroupEntryString.Key == "URL") uri = keePassFileGroupEntryString.Value.Value; else keePassFileGroupEntryStrings.Add(keePassFileGroupEntryString); } if (string.IsNullOrEmpty(uri) || !uri.Contains(':')) { host = null; port = null; } else { host = new System.Uri(uri).Host; port = new System.Uri(uri).Port; } List itemFields = (from l in keePassFileGroupEntryStrings select new Field(l.Key, l.Value.Value, 0)).ToList(); Login login = new([new(uri, host, port)], username, password); result = new(revisionDate, creationTime, folderId, folderName, 1, name, string.IsNullOrEmpty(notes) ? null : notes, itemFields.Count > 0 ? itemFields : null, login); } return result; } private static List GetItems(Guid folderId, string folderName, KeePassFileGroup keePassFileGroup) { List results = []; if (keePassFileGroup.Entry is not null) { Item? item; foreach (KeePassFileGroupEntry keePassFileGroupEntry in keePassFileGroup.Entry) { item = GetEntry(folderId, folderName, keePassFileGroupEntry); if (item is null) continue; results.Add(item); } } if (keePassFileGroup.Group is not null) { foreach (KeePassFileGroup keePassFileGroupInner in keePassFileGroup.Group) { folderId = Guid.NewGuid(); results.AddRange(GetItems(folderId, $"{folderName}/{keePassFileGroupInner.Name}", keePassFileGroupInner)); } } return results; } private static MemoryStream ToStream(string @this) { MemoryStream memoryStream = new(); StreamWriter streamWriter = new(memoryStream); streamWriter.Write(@this); streamWriter.Flush(); memoryStream.Position = 0; return memoryStream; } private static KeePassFile? ParseXML(string value, bool throwExceptions) { KeePassFile? result; try { Type type = typeof(KeePassFile); Stream stream = ToStream(value.Trim()); XmlReader xmlReader = XmlReader.Create(stream, new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Document }); #pragma warning disable IL2026 XmlSerializer xmlSerializer = new(type, type.GetNestedTypes()); result = xmlSerializer.Deserialize(xmlReader) as KeePassFile; #pragma warning restore IL2026 stream.Dispose(); } catch (Exception) { if (throwExceptions) throw; result = null; } return result; } private static List GetItems(KeePassFileRoot keePassFileRoot) { List results = []; Guid folderId = Guid.NewGuid(); string folderName = string.Empty; if (keePassFileRoot.Entry is not null) { Item? item; foreach (KeePassFileGroupEntry keePassFileGroupEntry in keePassFileRoot.Entry) { item = GetEntry(folderId, folderName, keePassFileGroupEntry); if (item is null) continue; results.Add(item); } } if (keePassFileRoot.Group is not null) { foreach (KeePassFileGroup keePassFileGroup in keePassFileRoot.Group) { folderId = Guid.NewGuid(); folderName = keePassFileGroup.Name; results.AddRange(GetItems(folderId, folderName, keePassFileGroup)); } } return results; } private static List GetFolders(List items) { List results = []; Folder folder; List distinct = []; foreach (Item item in items) { if (item.FolderName is null) throw new NullReferenceException(nameof(item.FolderName)); if (distinct.Contains(item.FolderName)) continue; distinct.Add(item.FolderName); folder = new(item.FolderId, item.FolderName); results.Add(folder); } return results; } private static List Filter(string xmlFile, ILogger logger, List items) { List results = []; int? port; string key; Item result; Login login; string? uri; string? host; string? name; Guid? folderId; string? username; List? check; string? folderName; DateTime? creationTime; string joinedHostPorts; DateTime? revisionDate; List notes = []; string? password = null; string?[] checkPasswords; Dictionary> keyValuePairs = []; foreach (Item item in items) { joinedHostPorts = string.Join('-', item.Login.Uris.Select(l => $"{l.Host}:{l.Port}")); key = string.Concat(item.Login.Username, '-', string.IsNullOrEmpty(joinedHostPorts) ? Guid.NewGuid().ToString() : joinedHostPorts); if (!keyValuePairs.TryGetValue(key, out check)) { keyValuePairs.Add(key, []); if (!keyValuePairs.TryGetValue(key, out check)) throw new NotSupportedException(); } check.Add(item); } foreach (KeyValuePair> keyValuePair in keyValuePairs) { if (keyValuePair.Value.Count == 1) results.AddRange(keyValuePair.Value); else { checkPasswords = keyValuePair.Value.Select(l => l.Login.Password).Distinct().ToArray(); if (checkPasswords.Length == 1) { results.Add(keyValuePair.Value[0]); logger.LogInformation("Filtered XML duplicate Password [{keyValuePair.Key}] <{xmlFile}>.", keyValuePair.Key, xmlFile); } else { uri = null; host = null; name = null; port = null; notes.Clear(); folderId = null; username = null; folderName = null; creationTime = null; revisionDate = null; notes.Add("Unset Password"); logger.LogInformation("Filtered XML Unset Password [{keyValuePair.Key}] <{xmlFile}>.", keyValuePair.Key, xmlFile); foreach (Item item in from l in keyValuePair.Value orderby l.RevisionDate, l.Login.Password?.Length descending select l) { if (item.Login.Uris.Count == 1) { uri = item.Login.Uris[0].Value; host = item.Login.Uris[0].Host; port = item.Login.Uris[0].Port; } name = item.Name; folderId = item.FolderId; folderName = item.FolderName; username = item.Login.Username; creationTime = item.CreationDate; revisionDate = item.RevisionDate; notes.Add($"{item.Login.Password} on {item.RevisionDate}"); } login = new([new(uri, host, port)], username, password); result = new(revisionDate, creationTime, folderId, folderName, 1, name, string.Join(Environment.NewLine, notes), null, login); results.Add(result); } } } if (results.Count != items.Count) logger.LogInformation("Filtered XML {results.Count} != {items.Count} <{xmlFile}>.", results.Count, items.Count, xmlFile); results = (from l in results orderby l.Login.Uris[0].Host, l.Login.Username select l).ToList(); return results; } private static void SaveTabSeparatedValueFiles(List items, string xmlFile) { List lines = []; foreach (Item item in items) lines.Add($"{item.Login.Uris[0].Host}\t{item.Login.Uris[0].Port}\t{item.Login.Username}\t{item.Login.Password}"); File.WriteAllLines(Path.ChangeExtension(xmlFile, ".tvs"), lines); } internal static void ConvertKeePassExport(ILogger logger, List args) { Root root; string xml; string json; List items; List folders; KeePassFile? keePassFile; List filteredItems; string newExtension = args[3]; string[] xmlFiles = Directory.GetFiles(args[0], args[2]); foreach (string xmlFile in xmlFiles) { xml = XDocument.Load(xmlFile).ToString(); keePassFile = ParseXML(xml, throwExceptions: true); if (keePassFile is null) throw new NullReferenceException(nameof(keePassFile)); logger.LogInformation("Loaded XML <{xmlFile}>.", xmlFile); items = GetItems(keePassFile.Root); folders = GetFolders(items); filteredItems = Filter(xmlFile, logger, items); logger.LogInformation("Save generic output file..."); SaveTabSeparatedValueFiles(filteredItems, xmlFile); root = new(folders, filteredItems); logger.LogInformation("Serializing output file..."); json = JsonSerializer.Serialize(root, RootSourceGenerationContext.Default.Root); logger.LogInformation("Writing output file..."); File.WriteAllText(Path.ChangeExtension(xmlFile, newExtension), json); } logger.LogInformation("Done!"); } }