file-folder-helper/Day/Helper-2023-12-21.cs
Mike Phares 90380fdd43 CUDA -> ConvertId
KeePass -> ConvertKeePassExport
NMap -> SplitJsonFile
2023-12-25 17:10:26 -07:00

295 lines
12 KiB
C#

using Microsoft.Extensions.Logging;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Xml.Linq;
namespace File_Folder_Helper.Day;
internal static partial class Helper20231221
{
// 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 static Func<XElement, bool> OtherFields()
{
return x =>
{
string? key = x.Element("Key")?.Value;
return key != "Title" && key != "Notes" && key != "UserName" && key != "Password" &&
key != "URL" && !_BlacklistedFields.Contains(key);
};
}
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")] string? Id,
[property: JsonPropertyName("name")] string? Name
);
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Folder))]
private partial class FolderSourceGenerationContext : JsonSerializerContext
{
}
private record Item(
[property: JsonPropertyName("revisionDate")] string? RevisionDate,
[property: JsonPropertyName("creationDate")] string? CreationDate,
[property: JsonPropertyName("folderId")] string? FolderId,
[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
);
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Uri))]
private partial class UriSourceGenerationContext : JsonSerializerContext
{
}
private static Item? GetEntry(string folderId, XElement entry)
{
Item? result;
XElement[] stringFields = entry.Elements("String").ToArray();
string? name = stringFields.Where(x => x.Element("Key")?.Value == "Title").Select(x => x.Element("Value")?.Value).FirstOrDefault();
if (_BlacklistedFields.Contains(name))
result = null;
else
{
XElement[] timesFields = entry.Elements("Times").ToArray();
string? creationTime = timesFields.Elements("CreationTime").FirstOrDefault()?.Value;
string? revisionDate = timesFields.Elements("LastModificationTime").FirstOrDefault()?.Value;
string? uri = stringFields.Where(x => x.Element("Key")?.Value == "URL").Select(x => x.Element("Value")?.Value).FirstOrDefault();
string? notes = stringFields.Where(x => x.Element("Key")?.Value == "Notes").Select(x => x.Element("Value")?.Value).FirstOrDefault();
string? username = stringFields.Where(x => x.Element("Key")?.Value == "UserName").Select(x => x.Element("Value")?.Value).FirstOrDefault();
string? password = stringFields.Where(x => x.Element("Key")?.Value == "Password").Select(x => x.Element("Value")?.Value).FirstOrDefault();
string? host = string.IsNullOrEmpty(uri) || !uri.Contains(':') ? null : new System.Uri(uri).Host;
Login login = new(new Uri[] { new(uri, host) }, username, password);
List<Field> itemFields = stringFields.Where(OtherFields()).Select(x => new Field(x.Element("Key")?.Value, x.Element("Value")?.Value, 0)).ToList();
result = new(revisionDate,
creationTime,
folderId,
1,
name,
string.IsNullOrEmpty(notes) ? null : notes,
itemFields.Count > 0 ? itemFields : null,
login);
}
return result;
}
private static List<Item> Filter(List<Item> items)
{
List<Item> results = [];
string key;
Item result;
Login login;
string? uri;
string? host;
string? name;
string? folderId;
string? username;
List<Item>? check;
string? creationTime;
string? revisionDate;
List<string> notes = [];
string? password = null;
string?[] checkPasswords;
Dictionary<string, List<Item>> keyValuePairs = [];
foreach (Item item in items)
{
key = string.Concat(item.Login.Username, '-', string.Join('-', item.Login.Uris.Select(l => l.Host)));
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]);
else
{
uri = null;
host = null;
name = null;
notes.Clear();
folderId = null;
username = null;
creationTime = null;
revisionDate = null;
notes.Add("Unset Password");
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;
}
name = item.Name;
folderId = item.FolderId;
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) }, username, password);
result = new(revisionDate,
creationTime,
folderId,
1,
name,
string.Join(Environment.NewLine, notes),
null,
login);
results.Add(result);
}
}
}
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.Username}\t{item.Login.Password}");
File.WriteAllLines(Path.ChangeExtension(xmlFile, ".tvs"), lines);
}
internal static void ConvertKeePassExport(ILogger<Worker> logger, List<string> args)
{
Root root;
Item? item;
string json;
string folderId;
string groupKey;
List<Item> items;
XElement element;
string newGroupKey;
XDocument xDocument;
List<Folder> folders;
string childGroupName;
string outputPath = args[0];
string newExtension = args[3];
IEnumerable<XElement> childEntries;
IEnumerable<XElement> childElements;
Dictionary<string, string> namesToIds;
Queue<KeyValuePair<string, XElement>> groupsToProcess;
string[] xmlFiles = Directory.GetFiles(args[0], args[2]);
foreach (string xmlFile in xmlFiles)
{
xDocument = XDocument.Load(xmlFile);
if (xDocument.Root is null)
throw new Exception("Root element missing");
items = [];
folders = [];
namesToIds = [];
groupsToProcess = [];
logger.LogInformation($"Loaded XML {xmlFile}.", xmlFile);
groupsToProcess.Enqueue(new KeyValuePair<string, XElement>("", xDocument.Root.Descendants("Group").First()));
while (groupsToProcess.TryDequeue(out KeyValuePair<string, XElement> valuePair))
{
groupKey = valuePair.Key;
element = valuePair.Value;
folderId = Guid.NewGuid().ToString();
childElements = element.Elements("Group");
folders.Add(new Folder(folderId, groupKey));
foreach (XElement childElement in childElements)
{
childGroupName = (childElement.Element("Name")?.Value) ?? throw new Exception("Found group with no name, malformed file");
if (_BlacklistedFolders.Contains(childGroupName))
childGroupName = "";
newGroupKey = $"{groupKey}/{childGroupName}";
if (groupKey == "")
newGroupKey = childGroupName;
logger.LogInformation($"Found group '{newGroupKey}'");
groupsToProcess.Enqueue(new KeyValuePair<string, XElement>(newGroupKey, childElement));
}
childEntries = element.Elements("Entry");
foreach (XElement entry in childEntries)
{
item = GetEntry(folderId, entry);
if (item is null)
continue;
items.Add(item);
}
}
items = Filter(items);
root = new(folders, items);
logger.LogInformation("Serializing output file...");
json = JsonSerializer.Serialize(root, RootSourceGenerationContext.Default.Root);
logger.LogInformation("Writing output file...");
File.WriteAllText(Path.ChangeExtension(xmlFile, newExtension), json);
SaveTabSeparatedValueFiles(items, xmlFile);
}
logger.LogInformation("Done!");
}
}