Skip to content
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8468341
Attempt nested for real this time
HeroponRikiBestest Nov 8, 2025
bb98653
forgot to include handling the base path
HeroponRikiBestest Nov 8, 2025
d025150
Reverted unnecesssary changes
HeroponRikiBestest Nov 8, 2025
ff39bad
Remove unneeded net6.0 gating
HeroponRikiBestest Nov 8, 2025
d2cb73f
Add comments
HeroponRikiBestest Nov 8, 2025
a931232
Finish comments
HeroponRikiBestest Nov 8, 2025
5a46e17
Might as well safeguard if no protections are returned.
HeroponRikiBestest Nov 8, 2025
f176f2e
Use object instead of dynamic
HeroponRikiBestest Nov 9, 2025
cc1503e
Remove weird empty string root node handling
HeroponRikiBestest Nov 9, 2025
502fbac
remove uneeded ref
HeroponRikiBestest Nov 9, 2025
f536fc4
Modify comment accordingly
HeroponRikiBestest Nov 9, 2025
8d8f3df
Merge regular and nested json writing
HeroponRikiBestest Nov 9, 2025
2f2472d
Simplify object value checking
HeroponRikiBestest Nov 9, 2025
4a4f3fe
change flag handling
HeroponRikiBestest Nov 10, 2025
c095b7e
Initial fixes
HeroponRikiBestest Nov 10, 2025
8ffa9d6
Invert if-else to de-nest main logic
HeroponRikiBestest Nov 10, 2025
4860eda
minor formatting fixes
HeroponRikiBestest Nov 10, 2025
a075997
Improved Json output
HeroponRikiBestest Nov 10, 2025
6e73cda
Remove unnecessary comments
HeroponRikiBestest Nov 10, 2025
50214d6
That's just a string
HeroponRikiBestest Nov 10, 2025
e840bbc
Slight improvement
HeroponRikiBestest Nov 10, 2025
8810c7d
Simplify casting
HeroponRikiBestest Nov 10, 2025
66afd7d
attept further simplification
HeroponRikiBestest Nov 10, 2025
e220db3
Further
HeroponRikiBestest Nov 10, 2025
7adc53a
Reduce nesting using inversion and continue
HeroponRikiBestest Nov 10, 2025
9decc06
Further simplified logic
HeroponRikiBestest Nov 10, 2025
e721e54
Replace my code with sabre's
HeroponRikiBestest Nov 10, 2025
4863878
De-nest using continue
HeroponRikiBestest Nov 10, 2025
801daa8
newline
HeroponRikiBestest Nov 10, 2025
04ca304
Remove all instances where it can end in a directory seperator
HeroponRikiBestest Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 107 additions & 2 deletions ProtectionScan/Features/MainFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ internal sealed class MainFeature : Feature
#if NETCOREAPP
private const string _jsonName = "json";
internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Output to json file");

private const string _nestedName = "nested";
internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file");
#endif

private const string _noArchivesName = "no-archives";
Expand Down Expand Up @@ -63,6 +66,11 @@ internal sealed class MainFeature : Feature
/// Enable JSON output
/// </summary>
public bool Json { get; private set; }

/// <summary>
/// Enable nested JSON output
/// </summary>
public bool Nested { get; private set; }
#endif

public MainFeature()
Expand All @@ -73,6 +81,7 @@ public MainFeature()
Add(DebugInput);
Add(FileOnlyInput);
#if NETCOREAPP
JsonInput.Add(NestedInput);
Add(JsonInput);
#endif
Add(NoContentsInput);
Expand All @@ -93,6 +102,7 @@ public override bool Execute()
FileOnly = GetBoolean(_fileOnlyName);
#if NETCOREAPP
Json = GetBoolean(_jsonName);
Nested = GetBoolean(_nestedName);
#endif

// Create scanner for all paths
Expand Down Expand Up @@ -248,9 +258,62 @@ private void WriteProtectionResultJson(string path, Dictionary<string, List<stri
// Attempt to open a protection file for writing
using var jsw = new StreamWriter(File.OpenWrite($"protection-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}.json"));

// Create the output data
var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true };
string serializedData = System.Text.Json.JsonSerializer.Serialize(protections, jsonSerializerOptions);
string serializedData;
if (Nested)
{
// A nested dictionary is used to achieve proper serialization.
var nestedDictionary = new Dictionary<string, object>();
var trimmedPath = path.TrimEnd(['\\', '/']);

// Sort the keys for consistent output
string[] keys = [.. protections.Keys];
Array.Sort(keys);

var modifyNodeList = new List<(Dictionary<string, object>, string, string[])>();

// Loop over all keys
foreach (string key in keys)
{
// Skip over files with no protection
var value = protections[key];
if (value.Count == 0)
continue;

// Sort the detected protections for consistent output
string[] fileProtections = [.. value];
Array.Sort(fileProtections);

// Inserts key and protections into nested dictionary, with the key trimmed of the base path.
InsertNode(nestedDictionary, key.Substring(trimmedPath.Length), fileProtections, modifyNodeList);
}

// Adds the non-leaf-node protections back in
for (int i = 0; i < modifyNodeList.Count; i++)
{
var copyDictionary = modifyNodeList[i].Item1[modifyNodeList[i].Item2];

var modifyNode = new List<object>();
modifyNode.Add(modifyNodeList[i].Item3);
modifyNode.Add(copyDictionary);

modifyNodeList[i].Item1[modifyNodeList[i].Item2] = modifyNode;
}

// Move nested dictionary into final dictionary with the base path as a key.
var finalDictionary = new Dictionary<string, Dictionary<string, object>>()
{
{trimmedPath, nestedDictionary}
};

// Create the output data
serializedData = System.Text.Json.JsonSerializer.Serialize(finalDictionary, jsonSerializerOptions);
}
else
{
// Create the output data
serializedData = System.Text.Json.JsonSerializer.Serialize(protections, jsonSerializerOptions);
}

// Write the output data
// TODO: this prints plus symbols wrong, probably some other things too
Expand All @@ -263,6 +326,48 @@ private void WriteProtectionResultJson(string path, Dictionary<string, List<stri
Console.WriteLine();
}
}

/// <summary>
/// Inserts file protection dictionary entries into a nested dictionary based on path
/// </summary>
/// <param name="nestedDictionary">File or directory path</param>
/// <param name="path">The "key" for the given protection entry, already trimmed of its base path</param>
/// <param name="protections">The scanned protection(s) for a given file</param>
public static void InsertNode(Dictionary<string, object> nestedDictionary, string path, string[] protections, List<(Dictionary<string, object>, string, string[])> modifyNodeList)
{
var current = nestedDictionary;
var pathParts = path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);

// Traverses the nested dictionary until the "leaf" dictionary is reached.
for (int i = 0; i < pathParts.Length - 1; i++)
{
var part = pathParts[i];

// Inserts new subdictionaries if one doesn't already exist
if (!current.ContainsKey(part))
{
var innerDictionary = new Dictionary<string, object>();
current[part] = innerDictionary;
current = innerDictionary;
continue;
}

var innerObject = current[part];

// Handle instances where a protection was already assigned to the current node
if (innerObject is string[] existingProtections)
{
modifyNodeList.Add((current, part, existingProtections));
innerObject = new Dictionary<string, object>();
}

current[part] = innerObject;
current = (Dictionary<string, object>)current[part];
}

// If the "leaf" dictionary has been reached, add the file and its protections.
current.Add(pathParts[^1], protections);
}
#endif
}
}