-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
fingerprint
command, to both assembly
and appbundle
branches
... to uniquely identify assemblies and appbundles using `Info.plist`
- Loading branch information
Showing
5 changed files
with
207 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
using System.ComponentModel; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Spectre.Console.Cli; | ||
using Claunia.PropertyList; | ||
|
||
namespace CilOut.Commands; | ||
|
||
public sealed class AppBundleFingerprintCommand : Command<AppBundleFingerprintCommand.Settings> { | ||
|
||
public sealed class Settings : AppBundleSettings { | ||
|
||
[Description ("Output using a diff-friendly, tab seperated values, format")] | ||
[CommandOption ("-d|--diff-friendly")] | ||
[DefaultValue (false)] | ||
public bool DiffFriendly { get; init; } | ||
} | ||
|
||
public override int Execute ([NotNull] CommandContext context, [NotNull] Settings settings) | ||
{ | ||
var result = ReturnCodes.Success; | ||
var appbundle = settings.AppBundle!; | ||
SortedDictionary<string, SortedDictionary<string, string>> fingerprints = new (); | ||
foreach (var file in Directory.EnumerateFiles (appbundle, "*", SearchOption.AllDirectories)) { | ||
switch (Path.GetExtension (file).ToLowerInvariant ()) { | ||
case ".dll": | ||
case ".exe": | ||
var assembly_result = AssemblyFingerprintCommand.Fingerprint (file, out var minutiae); | ||
if (assembly_result == ReturnCodes.Success) | ||
fingerprints.Add (file, minutiae); | ||
|
||
if (assembly_result != ReturnCodes.Success) | ||
result = assembly_result; | ||
break; | ||
case ".plist": | ||
try { | ||
fingerprints.Add (file, ParseInfoPlist (file)); | ||
} | ||
catch { | ||
result = ReturnCodes.Failure; | ||
} | ||
break; | ||
} | ||
} | ||
|
||
foreach (var (file, minutiae) in fingerprints) { | ||
var n = appbundle.Length; | ||
if (file [n] == '/') | ||
n++; | ||
var assembly = file [n..]; | ||
if (settings.DiffFriendly) { | ||
AssemblyFingerprintCommand.TabSeparatedValues (assembly, minutiae); | ||
} else { | ||
AssemblyFingerprintCommand.ShowResult (assembly, result, minutiae); | ||
} | ||
} | ||
|
||
return (int) result; | ||
} | ||
|
||
static SortedDictionary<string,string> ParseInfoPlist (string filename) | ||
{ | ||
SortedDictionary<string,string> minutiae = new (); | ||
NSDictionary? info = (NSDictionary) PropertyListParser.Parse (filename); | ||
if (info is not null) { | ||
foreach (var element in info) { | ||
switch (element.Key) { | ||
case "DTCompiler": | ||
case "DTPlatformBuild": | ||
case "DTPlatformName": | ||
case "DTPlatformVersion": | ||
case "DTSDKBuild": | ||
case "DTSDKName": | ||
case "DTXcode": | ||
case "DTXcodeBuild": | ||
minutiae.Add (element.Key, element.Value.ToString ()!); | ||
break; | ||
case "com.microsoft.ios": | ||
case "com.microsoft.macos": | ||
case "com.microsoft.maccatalyst": | ||
case "com.microsoft.tvos": | ||
case "com.microsoft.watchos": | ||
NSDictionary? dict = (NSDictionary) element.Value; | ||
if (dict is not null) { | ||
foreach (var subelement in dict) | ||
minutiae.Add (element.Key + "/" + subelement.Key, subelement.Value.ToString ()!); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
return minutiae; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System.ComponentModel; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Mono.Cecil; | ||
using Spectre.Console; | ||
using Spectre.Console.Cli; | ||
|
||
namespace CilOut.Commands; | ||
|
||
public sealed class AssemblyFingerprintCommand : Command<AssemblyFingerprintCommand.Settings> { | ||
|
||
public sealed class Settings : AssemblySettings { | ||
|
||
[Description ("Output using a diff-friendly, tab seperated values, format")] | ||
[CommandOption ("-d|--diff-friendly")] | ||
[DefaultValue (false)] | ||
public bool DiffFriendly { get; init; } | ||
} | ||
|
||
public override int Execute ([NotNull] CommandContext context, [NotNull] Settings settings) | ||
{ | ||
var assembly = settings.Assembly!; | ||
var result = Fingerprint (assembly, out var minutiae); | ||
|
||
if (settings.DiffFriendly) { | ||
TabSeparatedValues (Path.GetFileName (assembly), minutiae); | ||
} else { | ||
ShowResult (assembly, result, minutiae); | ||
} | ||
|
||
return (int) result; | ||
} | ||
|
||
public static void TabSeparatedValues (string assemblyName, SortedDictionary<string,string> minutiae) | ||
{ | ||
foreach (var (key, value) in minutiae) { | ||
Console.Write (assemblyName); | ||
Console.Write ('\t'); | ||
Console.Write (key); | ||
Console.Write ('\t'); | ||
Console.WriteLine (value); | ||
} | ||
} | ||
|
||
public static void ShowResult (string assemblyName, ReturnCodes result, SortedDictionary<string,string> minutiae) | ||
{ | ||
var asm = assemblyName.EscapeMarkup (); | ||
switch (result) { | ||
case ReturnCodes.Success: | ||
AnsiConsole.WriteLine ($"Assembly `{asm}` fingerprint is composed of"); | ||
foreach (var (key, value) in minutiae) { | ||
AnsiConsole.Write (key); | ||
AnsiConsole.Write (" : '"); | ||
AnsiConsole.Write (value); | ||
AnsiConsole.WriteLine ('\''); | ||
} | ||
break; | ||
case ReturnCodes.Failure: | ||
AnsiConsole.MarkupLine ($"Assembly `{asm}` has [bold]no[/] minutiae."); | ||
break; | ||
case ReturnCodes.CouldNotReadAssembly: | ||
AnsiConsole.MarkupLine ($"[red]Error: [/] Could not read assembly `{asm}`."); | ||
break; | ||
} | ||
} | ||
|
||
public static ReturnCodes Fingerprint (string assembly, out SortedDictionary<string,string> minutiae) | ||
{ | ||
minutiae = new (); | ||
try { | ||
AssemblyDefinition ad = AssemblyDefinition.ReadAssembly (assembly); | ||
// FullName includes [Assembly]Version, Culture and PublicKeyToken | ||
minutiae.Add ("FullName", ad.FullName); | ||
if (ad.HasCustomAttributes) { | ||
foreach (var ca in ad.CustomAttributes) { | ||
switch (ca.AttributeType.FullName) { | ||
case "System.Reflection.AssemblyCompanyAttribute": | ||
case "System.Reflection.AssemblyConfigurationAttribute": | ||
case "System.Reflection.AssemblyFileVersionAttribute": // can differ from AssemblyVersion | ||
case "System.Reflection.AssemblyInformationalVersionAttribute": | ||
case "System.Runtime.Versioning.TargetFrameworkAttribute": | ||
case "System.Runtime.Versioning.TargetPlatformAttribute": | ||
case "System.Runtime.Versioning.SupportedOSPlatform": | ||
minutiae.Add (ca.AttributeType.Name, GetConstructorArgument (ca, 0)); | ||
break; | ||
case "System.Reflection.AssemblyMetadataAttribute": | ||
minutiae.Add (ca.AttributeType.Name + "." + GetConstructorArgument (ca, 0), GetConstructorArgument (ca, 1)); | ||
break; | ||
} | ||
} | ||
} | ||
return minutiae.Count > 0 ? ReturnCodes.Success : ReturnCodes.Failure; | ||
} catch (Exception e) { | ||
if (Environment.GetEnvironmentVariable ("V") is not null) | ||
AnsiConsole.WriteException (e); | ||
return ReturnCodes.CouldNotReadAssembly; | ||
} | ||
} | ||
|
||
static string GetConstructorArgument (CustomAttribute ca, int n) | ||
{ | ||
if (!ca.HasConstructorArguments) | ||
return ""; | ||
if (n >= ca.ConstructorArguments.Count) | ||
return ""; | ||
return ca.ConstructorArguments [0].Value.ToString () ?? ""; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters