using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Xml; using BurnOutSharp.Matching; namespace BurnOutSharp { internal static class Utilities { #region Dictionary Manipulation /// /// Append one result to a results dictionary /// /// Dictionary to append to /// Key to add information to /// String value to add public static void AppendToDictionary(ConcurrentDictionary> original, string key, string value) { var values = new ConcurrentQueue(); values.Enqueue(value); AppendToDictionary(original, key, values); } /// /// Append one result to a results dictionary /// /// Dictionary to append to /// Key to add information to /// String value to add public static void AppendToDictionary(ConcurrentDictionary> original, string key, ConcurrentQueue values) { // If the dictionary is null, just return if (original == null) return; // Use a placeholder value if the key is null key = key ?? "NO FILENAME"; // Add the key if needed and then append the lists original.TryAdd(key, new ConcurrentQueue()); original[key].AddRange(values); } /// /// Append one results dictionary to another /// /// Dictionary to append to /// Dictionary to pull from public static void AppendToDictionary(ConcurrentDictionary> original, ConcurrentDictionary> addition) { // If either dictionary is missing, just return if (original == null || addition == null) return; // Loop through each of the addition keys and add accordingly foreach (string key in addition.Keys) { original.TryAdd(key, new ConcurrentQueue()); original[key].AddRange(addition[key]); } } /// /// Remove empty or null keys from a results dictionary /// /// Dictionary to clean public static void ClearEmptyKeys(ConcurrentDictionary> original) { // If the dictionary is missing, we can't do anything if (original == null) return; // Get a list of all of the keys var keys = original.Keys.ToList(); // Iterate and reset keys for (int i = 0; i < keys.Count; i++) { // Get the current key string key = keys[i]; // If the key is empty, remove it if (original[key] == null || !original[key].Any()) original.TryRemove(key, out _); } } /// /// Prepend a parent path from dictionary keys, if possible /// /// Dictionary to strip values from /// Path to strip from the keys public static void PrependToKeys(ConcurrentDictionary> original, string pathToPrepend) { // If the dictionary is missing, we can't do anything if (original == null) return; // Use a placeholder value if the path is null pathToPrepend = (pathToPrepend ?? "ARCHIVE").TrimEnd(Path.DirectorySeparatorChar); // Get a list of all of the keys var keys = original.Keys.ToList(); // Iterate and reset keys for (int i = 0; i < keys.Count; i++) { // Get the current key string currentKey = keys[i]; // Otherwise, get the new key name and transfer over string newKey = $"{pathToPrepend}{Path.DirectorySeparatorChar}{currentKey.Trim(Path.DirectorySeparatorChar)}"; original[newKey] = original[currentKey]; original.TryRemove(currentKey, out _); } } /// /// Strip a parent path from dictionary keys, if possible /// /// Dictionary to strip values from /// Path to strip from the keys public static void StripFromKeys(ConcurrentDictionary> original, string pathToStrip) { // If either is missing, we can't do anything if (original == null || string.IsNullOrEmpty(pathToStrip)) return; // Get a list of all of the keys var keys = original.Keys.ToList(); // Iterate and reset keys for (int i = 0; i < keys.Count; i++) { // Get the current key string currentKey = keys[i]; // If the key doesn't start with the path, don't touch it if (!currentKey.StartsWith(pathToStrip, StringComparison.OrdinalIgnoreCase)) continue; // Otherwise, get the new key name and transfer over string newKey = currentKey.Substring(pathToStrip.Length); original[newKey] = original[currentKey]; original.TryRemove(currentKey, out _); } } #endregion #region Concurrent Manipulation /// /// Add a range of values from one queue to another /// /// Queue to add data to /// Queue to get data from public static void AddRange(this ConcurrentQueue original, ConcurrentQueue values) { while (!values.IsEmpty) { if (!values.TryDequeue(out string value)) return; original.Enqueue(value); } } #endregion #region Byte Arrays /// /// Find all positions of one array in another, if possible, if possible /// public static List FindAllPositions(this byte[] stack, byte?[] needle, int start = 0, int end = -1) { // Get the outgoing list List positions = new List(); // Initialize the loop variables bool found = true; int lastPosition = start; var matcher = new ContentMatch(needle, end: end); // Loop over and get all positions while (found) { matcher.Start = lastPosition; (found, lastPosition) = matcher.Match(stack, false); if (found) positions.Add(lastPosition); } return positions; } /// /// Find the first position of one array in another, if possible /// public static bool FirstPosition(this byte[] stack, byte?[] needle, out int position, int start = 0, int end = -1) { var matcher = new ContentMatch(needle, start, end); (bool found, int foundPosition) = matcher.Match(stack, false); position = foundPosition; return found; } /// /// Find the last position of one array in another, if possible /// public static bool LastPosition(this byte[] stack, byte?[] needle, out int position, int start = 0, int end = -1) { var matcher = new ContentMatch(needle, start, end); (bool found, int foundPosition) = matcher.Match(stack, true); position = foundPosition; return found; } /// /// See if a byte array starts with another /// public static bool StartsWith(this byte[] stack, byte?[] needle) { return stack.FirstPosition(needle, out int _, start: 0, end: 1); } /// /// See if a byte array ends with another /// public static bool EndsWith(this byte[] stack, byte?[] needle) { return stack.FirstPosition(needle, out int _, start: stack.Length - needle.Length); } #endregion #region Protection /// /// Get the file version as reported by the filesystem /// /// File to check for version /// Version string, null on error public static string GetFileVersion(string file) { if (file == null || !File.Exists(file)) return string.Empty; FileVersionInfo fvinfo = FileVersionInfo.GetVersionInfo(file); if (fvinfo.FileVersion == null) return ""; if (fvinfo.FileVersion != "") return fvinfo.FileVersion.Replace(", ", "."); else return fvinfo.ProductVersion.Replace(", ", "."); } /// /// Wrapper for GetFileVersion for use in content matching /// /// File to check for version /// Byte array representing the file contents /// Last matched positions in the contents /// Version string, null on error public static string GetFileVersion(string file, byte[] fileContent, List positions) { return GetFileVersion(file); } /// /// Wrapper for GetFileVersion for use in path matching /// /// File to check for version /// Full list of input paths /// Version string, null on error public static string GetFileVersion(string firstMatchedString, IEnumerable files) { return GetFileVersion(firstMatchedString); } /// /// Get the assembly version as determined by an embedded assembly manifest /// /// Byte array representing the file contents /// Version string, null on error /// TODO: How do we find the manifest specifically better? public static string GetManifestVersion(byte[] fileContent) { // byte?[] manifestEnd = new byte?[] { 0x3C, 0x2F, 0x61, 0x73, 0x73, 0x65, 0x6D, 0x62, 0x6C, 0x79, 0x3E }; if (!fileContent.FirstPosition(manifestEnd, out int manifestEndPosition, start: manifestStartPosition)) return null; // Read in the manifest to a string int manifestLength = manifestEndPosition + "".Length - manifestStartPosition; string manifestString = Encoding.ASCII.GetString(fileContent, manifestStartPosition, manifestLength); // Try to read the XML in from the string try { // Load the XML string as a document var manifestDoc = new XmlDocument(); manifestDoc.LoadXml(manifestString); // If the XML has no children, it's invalid if (!manifestDoc.HasChildNodes) return null; // Try to read the assembly node var assemblyNode = manifestDoc["assembly"]; if (assemblyNode == null) return null; // Try to read the assemblyIdentity var assemblyIdentityNode = assemblyNode["assemblyIdentity"]; if (assemblyIdentityNode == null) return null; // Return the version attribute, if possible return assemblyIdentityNode.GetAttribute("version"); } catch { return null; } } /// /// Get the assembly version as determined by an embedded assembly manifest /// /// Byte array representing the file contents /// Version string, null on error /// TODO: How do we find the manifest specifically better? public static string GetManifestDescription(byte[] fileContent) { // byte?[] manifestEnd = new byte?[] { 0x3C, 0x2F, 0x61, 0x73, 0x73, 0x65, 0x6D, 0x62, 0x6C, 0x79, 0x3E }; if (!fileContent.FirstPosition(manifestEnd, out int manifestEndPosition, start: manifestStartPosition)) return null; // Read in the manifest to a string int manifestLength = manifestEndPosition + "".Length - manifestStartPosition; string manifestString = Encoding.ASCII.GetString(fileContent, manifestStartPosition, manifestLength); // Try to read the XML in from the string try { // Load the XML string as a document var manifestDoc = new XmlDocument(); manifestDoc.LoadXml(manifestString); // If the XML has no children, it's invalid if (!manifestDoc.HasChildNodes) return null; // Try to read the assembly node var assemblyNode = manifestDoc["assembly"]; if (assemblyNode == null) return null; // Try to read the assemblyIdentity var assemblyIdentityNode = assemblyNode["assemblyIdentity"]; if (assemblyIdentityNode == null) return null; // Return the content of the description node, if possible var DescriptionNode = assemblyNode["description"]; if (DescriptionNode == null) return null; return DescriptionNode.InnerXml; } catch { return null; } } #endregion } }