file-folder-helper/Day/Q12024/Helper-2024-01-05.cs
2024-08-09 09:54:22 -07:00

1345 lines
34 KiB
C#

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.Day.Q12024;
#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<Field>? Fields,
[property: JsonPropertyName("login")] Login Login
);
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Item))]
private partial class ItemSourceGenerationContext : JsonSerializerContext
{
}
private record Login(
[property: JsonPropertyName("uris")] IReadOnlyList<Uri> 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<Folder> Folders,
[property: JsonPropertyName("items")] IReadOnlyList<Item> 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<KeePassFileGroupEntryString> 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<Field> itemFields = (from l in keePassFileGroupEntryStrings select new Field(l.Key, l.Value.Value, 0)).ToList();
Login login = new(new Uri[] { 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<Item> GetItems(Guid folderId, string folderName, KeePassFileGroup keePassFileGroup)
{
List<Item> 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<Item> GetItems(KeePassFileRoot keePassFileRoot)
{
List<Item> 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<Folder> GetFolders(List<Item> items)
{
List<Folder> results = [];
Folder folder;
List<string> 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<Item> Filter(string xmlFile, ILogger<Worker> logger, List<Item> items)
{
List<Item> results = [];
int? port;
string key;
Item result;
Login login;
string? uri;
string? host;
string? name;
Guid? folderId;
string? username;
List<Item>? check;
string? folderName;
DateTime? creationTime;
string joinedHostPorts;
DateTime? revisionDate;
List<string> notes = [];
string? password = null;
string?[] checkPasswords;
Dictionary<string, List<Item>> 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<string, List<Item>> 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[] { 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<Item> items, string xmlFile)
{
List<string> 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<Worker> logger, List<string> args)
{
Root root;
string xml;
string json;
List<Item> items;
List<Folder> folders;
KeePassFile? keePassFile;
List<Item> 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!");
}
}