Skip to content

Commit 95a20fb

Browse files
Add nested json output for protectionscan (#391)
* Attempt nested for real this time * forgot to include handling the base path * Reverted unnecesssary changes * Remove unneeded net6.0 gating * Add comments * Finish comments * Might as well safeguard if no protections are returned. * Use object instead of dynamic * Remove weird empty string root node handling * remove uneeded ref * Modify comment accordingly * Merge regular and nested json writing * Simplify object value checking * change flag handling Co-authored-by: Matt Nadareski <mnadareski@outlook.com> * Initial fixes * Invert if-else to de-nest main logic * minor formatting fixes * Improved Json output * Remove unnecessary comments * That's just a string * Slight improvement * Simplify casting * attept further simplification * Further * Reduce nesting using inversion and continue * Further simplified logic * Replace my code with sabre's * De-nest using continue * newline * Remove all instances where it can end in a directory seperator --------- Co-authored-by: Matt Nadareski <mnadareski@outlook.com>
1 parent dd1e496 commit 95a20fb

File tree

1 file changed

+107
-2
lines changed

1 file changed

+107
-2
lines changed

ProtectionScan/Features/MainFeature.cs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ internal sealed class MainFeature : Feature
3232
#if NETCOREAPP
3333
private const string _jsonName = "json";
3434
internal readonly FlagInput JsonInput = new(_jsonName, ["-j", "--json"], "Output to json file");
35+
36+
private const string _nestedName = "nested";
37+
internal readonly FlagInput NestedInput = new(_nestedName, ["-n", "--nested"], "Output to nested json file");
3538
#endif
3639

3740
private const string _noArchivesName = "no-archives";
@@ -63,6 +66,11 @@ internal sealed class MainFeature : Feature
6366
/// Enable JSON output
6467
/// </summary>
6568
public bool Json { get; private set; }
69+
70+
/// <summary>
71+
/// Enable nested JSON output
72+
/// </summary>
73+
public bool Nested { get; private set; }
6674
#endif
6775

6876
public MainFeature()
@@ -73,6 +81,7 @@ public MainFeature()
7381
Add(DebugInput);
7482
Add(FileOnlyInput);
7583
#if NETCOREAPP
84+
JsonInput.Add(NestedInput);
7685
Add(JsonInput);
7786
#endif
7887
Add(NoContentsInput);
@@ -93,6 +102,7 @@ public override bool Execute()
93102
FileOnly = GetBoolean(_fileOnlyName);
94103
#if NETCOREAPP
95104
Json = GetBoolean(_jsonName);
105+
Nested = GetBoolean(_nestedName);
96106
#endif
97107

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

251-
// Create the output data
252261
var jsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { WriteIndented = true };
253-
string serializedData = System.Text.Json.JsonSerializer.Serialize(protections, jsonSerializerOptions);
262+
string serializedData;
263+
if (Nested)
264+
{
265+
// A nested dictionary is used to achieve proper serialization.
266+
var nestedDictionary = new Dictionary<string, object>();
267+
var trimmedPath = path.TrimEnd(['\\', '/']);
268+
269+
// Sort the keys for consistent output
270+
string[] keys = [.. protections.Keys];
271+
Array.Sort(keys);
272+
273+
var modifyNodeList = new List<(Dictionary<string, object>, string, string[])>();
274+
275+
// Loop over all keys
276+
foreach (string key in keys)
277+
{
278+
// Skip over files with no protection
279+
var value = protections[key];
280+
if (value.Count == 0)
281+
continue;
282+
283+
// Sort the detected protections for consistent output
284+
string[] fileProtections = [.. value];
285+
Array.Sort(fileProtections);
286+
287+
// Inserts key and protections into nested dictionary, with the key trimmed of the base path.
288+
InsertNode(nestedDictionary, key.Substring(trimmedPath.Length), fileProtections, modifyNodeList);
289+
}
290+
291+
// Adds the non-leaf-node protections back in
292+
for (int i = 0; i < modifyNodeList.Count; i++)
293+
{
294+
var copyDictionary = modifyNodeList[i].Item1[modifyNodeList[i].Item2];
295+
296+
var modifyNode = new List<object>();
297+
modifyNode.Add(modifyNodeList[i].Item3);
298+
modifyNode.Add(copyDictionary);
299+
300+
modifyNodeList[i].Item1[modifyNodeList[i].Item2] = modifyNode;
301+
}
302+
303+
// Move nested dictionary into final dictionary with the base path as a key.
304+
var finalDictionary = new Dictionary<string, Dictionary<string, object>>()
305+
{
306+
{trimmedPath, nestedDictionary}
307+
};
308+
309+
// Create the output data
310+
serializedData = System.Text.Json.JsonSerializer.Serialize(finalDictionary, jsonSerializerOptions);
311+
}
312+
else
313+
{
314+
// Create the output data
315+
serializedData = System.Text.Json.JsonSerializer.Serialize(protections, jsonSerializerOptions);
316+
}
254317

255318
// Write the output data
256319
// TODO: this prints plus symbols wrong, probably some other things too
@@ -263,6 +326,48 @@ private void WriteProtectionResultJson(string path, Dictionary<string, List<stri
263326
Console.WriteLine();
264327
}
265328
}
329+
330+
/// <summary>
331+
/// Inserts file protection dictionary entries into a nested dictionary based on path
332+
/// </summary>
333+
/// <param name="nestedDictionary">File or directory path</param>
334+
/// <param name="path">The "key" for the given protection entry, already trimmed of its base path</param>
335+
/// <param name="protections">The scanned protection(s) for a given file</param>
336+
public static void InsertNode(Dictionary<string, object> nestedDictionary, string path, string[] protections, List<(Dictionary<string, object>, string, string[])> modifyNodeList)
337+
{
338+
var current = nestedDictionary;
339+
var pathParts = path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
340+
341+
// Traverses the nested dictionary until the "leaf" dictionary is reached.
342+
for (int i = 0; i < pathParts.Length - 1; i++)
343+
{
344+
var part = pathParts[i];
345+
346+
// Inserts new subdictionaries if one doesn't already exist
347+
if (!current.ContainsKey(part))
348+
{
349+
var innerDictionary = new Dictionary<string, object>();
350+
current[part] = innerDictionary;
351+
current = innerDictionary;
352+
continue;
353+
}
354+
355+
var innerObject = current[part];
356+
357+
// Handle instances where a protection was already assigned to the current node
358+
if (innerObject is string[] existingProtections)
359+
{
360+
modifyNodeList.Add((current, part, existingProtections));
361+
innerObject = new Dictionary<string, object>();
362+
}
363+
364+
current[part] = innerObject;
365+
current = (Dictionary<string, object>)current[part];
366+
}
367+
368+
// If the "leaf" dictionary has been reached, add the file and its protections.
369+
current.Add(pathParts[^1], protections);
370+
}
266371
#endif
267372
}
268373
}

0 commit comments

Comments
 (0)