-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Show ADS (alternative data streams) preview in file properties #15078
Comments
Thanks for the feedback, How would it be more useful? They show in the normal file view as they can be edited like a file |
I have 10-15 ADS for each file and I only want to see the ADS in the properties windows and not in the window with all the files because...It turns into a huge mess of files mixed with ADS, This is probably a very rare case, but it does happen :D For example I have ADS: checkStatus, checkDate, hashMD5 and more other |
But all my ADS contain ASCII text and no binary data, so these fields can be displayed as a table "ADS-name | ADS-content". |
How would this work for streams with multiple lines of text? |
Show only first ~50 symbols in one row, I haven't multi line data in ADS, something like this: using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
public class AlternateStreamReader
{
// Dictionary to store stream names and their contents
private Dictionary<string, string> _streams = new Dictionary<string, string>();
// Maximum content length before truncation
private const int MAX_CONTENT_LENGTH = 50;
// Regular expression pattern for detecting binary content
// This pattern looks for common binary characters
private static readonly Regex BinaryPattern = new Regex(
"[\x00-\x08\x0B\x0C\x0E-\x1F]",
RegexOptions.Compiled
);
/// <summary>
/// Reads all alternate data streams for a given file
/// </summary>
/// <param name="filePath">Full path to the file to analyze</param>
/// <returns>Dictionary with stream names as keys and their content as values</returns>
public Dictionary<string, string> ReadAlternateStreams(string filePath)
{
_streams.Clear();
// Check if file exists
if (!File.Exists(filePath))
{
throw new FileNotFoundException("The specified file was not found.", filePath);
}
// Get all streams for the file
var streamInfos = Directory.GetFiles(filePath + ":*");
foreach (var streamPath in streamInfos)
{
try
{
// Extract stream name from the full path
// Format is "filename.ext:streamname:$DATA"
string streamName = ExtractStreamName(streamPath);
// Read stream content
string content = ReadStreamContent(streamPath);
// Add to dictionary if stream name was successfully extracted
if (!string.IsNullOrEmpty(streamName))
{
_streams[streamName] = content;
}
}
catch (Exception ex)
{
// Log or handle the error as needed
Console.WriteLine($"Error reading stream {streamPath}: {ex.Message}");
}
}
return _streams;
}
/// <summary>
/// Extracts the stream name from the full stream path
/// </summary>
/// <param name="streamPath">Full path including the stream name</param>
/// <returns>Stream name without the :$DATA suffix</returns>
private string ExtractStreamName(string streamPath)
{
// Find the position of the first colon (after drive letter)
int firstColon = streamPath.IndexOf(':', 2);
if (firstColon == -1) return string.Empty;
// Extract everything between the first colon and :$DATA
string streamPart = streamPath.Substring(firstColon + 1);
return streamPart.Replace(":$DATA", "");
}
/// <summary>
/// Reads and processes the content of a stream
/// </summary>
/// <param name="streamPath">Full path to the stream</param>
/// <returns>Processed stream content</returns>
private string ReadStreamContent(string streamPath)
{
// Read all bytes from the stream
byte[] content = File.ReadAllBytes(streamPath);
// If content is empty, return empty string
if (content.Length == 0) return string.Empty;
// Try to convert to string using UTF8
string stringContent;
try
{
stringContent = Encoding.UTF8.GetString(content);
// Check if content appears to be binary
if (IsBinaryContent(content, stringContent))
{
return "<binary data>";
}
}
catch
{
// If conversion fails, assume binary data
return "<binary data>";
}
// Truncate if necessary
if (stringContent.Length > MAX_CONTENT_LENGTH)
{
return stringContent.Substring(0, MAX_CONTENT_LENGTH) + "...";
}
return stringContent;
}
/// <summary>
/// Attempts to determine if content is binary
/// </summary>
/// <param name="bytes">Raw byte content</param>
/// <param name="stringContent">Content converted to string</param>
/// <returns>True if content appears to be binary</returns>
private bool IsBinaryContent(byte[] bytes, string stringContent)
{
// Check for null bytes (common in binary files)
if (Array.IndexOf(bytes, (byte)0) != -1)
return true;
// Check for binary characters using regex
if (BinaryPattern.IsMatch(stringContent))
return true;
// Additional heuristic: check ratio of printable to non-printable characters
int nonPrintable = 0;
foreach (char c in stringContent)
{
if (char.IsControl(c) && !char.IsWhiteSpace(c))
nonPrintable++;
}
// If more than 10% non-printable characters, consider it binary
return (double)nonPrintable / stringContent.Length > 0.1;
}
}
// Example usage:
public class Program
{
public static void Main()
{
var reader = new AlternateStreamReader();
try
{
var streams = reader.ReadAlternateStreams(@"C:\path\to\your\file.txt");
foreach (var stream in streams)
{
Console.WriteLine($"Stream: {stream.Key}");
Console.WriteLine($"Content: {stream.Value}");
Console.WriteLine();
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
} And when user click on "eye" button nearby one row - he will see a modal window with first 1000 bytes as a plain text or as a HEX view if data is binary: using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
public class AlternateStreamReader
{
// Dictionary to store stream names and their contents
private Dictionary<string, StreamContent> _streams = new Dictionary<string, StreamContent>();
// Maximum content length for plain text display
private const int MAX_CONTENT_LENGTH = 50;
// Maximum bytes to show in hex view
private const int MAX_HEX_BYTES = 1000;
// Number of bytes per line in hex view
private const int BYTES_PER_LINE = 16;
// Regular expression pattern for detecting binary content
private static readonly Regex BinaryPattern = new Regex(
"[\x00-\x08\x0B\x0C\x0E-\x1F]",
RegexOptions.Compiled
);
/// <summary>
/// Class to store stream content details
/// </summary>
public class StreamContent
{
public bool IsBinary { get; set; }
public string DisplayContent { get; set; }
public string HexView { get; set; }
public byte[] RawData { get; set; }
}
/// <summary>
/// Reads all alternate data streams for a given file
/// </summary>
/// <param name="filePath">Full path to the file to analyze</param>
/// <returns>Dictionary with stream names as keys and their content as values</returns>
public Dictionary<string, StreamContent> ReadAlternateStreams(string filePath)
{
_streams.Clear();
if (!File.Exists(filePath))
{
throw new FileNotFoundException("The specified file was not found.", filePath);
}
var streamInfos = Directory.GetFiles(filePath + ":*");
foreach (var streamPath in streamInfos)
{
try
{
string streamName = ExtractStreamName(streamPath);
var content = ReadStreamContent(streamPath);
if (!string.IsNullOrEmpty(streamName))
{
_streams[streamName] = content;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error reading stream {streamPath}: {ex.Message}");
}
}
return _streams;
}
/// <summary>
/// Extracts the stream name from the full stream path
/// </summary>
private string ExtractStreamName(string streamPath)
{
int firstColon = streamPath.IndexOf(':', 2);
if (firstColon == -1) return string.Empty;
string streamPart = streamPath.Substring(firstColon + 1);
return streamPart.Replace(":$DATA", "");
}
/// <summary>
/// Reads and processes the content of a stream
/// </summary>
private StreamContent ReadStreamContent(string streamPath)
{
var content = new StreamContent();
// Read the raw bytes
byte[] rawBytes = File.ReadAllBytes(streamPath);
content.RawData = rawBytes;
if (rawBytes.Length == 0)
{
content.IsBinary = false;
content.DisplayContent = string.Empty;
content.HexView = string.Empty;
return content;
}
// Try to convert to string using UTF8
try
{
string stringContent = Encoding.UTF8.GetString(rawBytes);
content.IsBinary = IsBinaryContent(rawBytes, stringContent);
if (content.IsBinary)
{
content.DisplayContent = "<binary data>";
// Generate hex view for binary data
content.HexView = GenerateHexView(rawBytes);
}
else
{
// For text content, show first 1000 bytes as text
content.DisplayContent = stringContent.Length > MAX_CONTENT_LENGTH
? stringContent.Substring(0, MAX_CONTENT_LENGTH) + "..."
: stringContent;
// For text content longer than 50 chars, still generate hex view
if (rawBytes.Length > MAX_CONTENT_LENGTH)
{
content.HexView = GenerateHexView(rawBytes);
}
}
}
catch
{
content.IsBinary = true;
content.DisplayContent = "<binary data>";
content.HexView = GenerateHexView(rawBytes);
}
return content;
}
/// <summary>
/// Generates a hex view of the data with ASCII representation
/// </summary>
/// <param name="bytes">Raw byte data</param>
/// <returns>Formatted hex view string</returns>
private string GenerateHexView(byte[] bytes)
{
var sb = new StringBuilder();
// Limit to MAX_HEX_BYTES
int bytesToShow = Math.Min(bytes.Length, MAX_HEX_BYTES);
for (int i = 0; i < bytesToShow; i += BYTES_PER_LINE)
{
// Add offset
sb.AppendFormat("{0:X8}: ", i);
// Add hex values
for (int j = 0; j < BYTES_PER_LINE; j++)
{
if (i + j < bytesToShow)
{
sb.AppendFormat("{0:X2} ", bytes[i + j]);
}
else
{
sb.Append(" "); // 3 spaces for alignment
}
}
// Add separator
sb.Append("| ");
// Add ASCII representation
for (int j = 0; j < BYTES_PER_LINE; j++)
{
if (i + j < bytesToShow)
{
byte b = bytes[i + j];
// Show printable characters, replace others with dot
char c = (b >= 32 && b <= 126) ? (char)b : '.';
sb.Append(c);
}
}
sb.AppendLine();
}
if (bytes.Length > MAX_HEX_BYTES)
{
sb.AppendLine("...");
}
return sb.ToString();
}
/// <summary>
/// Attempts to determine if content is binary
/// </summary>
private bool IsBinaryContent(byte[] bytes, string stringContent)
{
if (Array.IndexOf(bytes, (byte)0) != -1)
return true;
if (BinaryPattern.IsMatch(stringContent))
return true;
int nonPrintable = 0;
foreach (char c in stringContent)
{
if (char.IsControl(c) && !char.IsWhiteSpace(c))
nonPrintable++;
}
return (double)nonPrintable / stringContent.Length > 0.1;
}
}
// Example usage:
public class Program
{
public static void Main()
{
var reader = new AlternateStreamReader();
try
{
var streams = reader.ReadAlternateStreams(@"C:\path\to\your\file.txt");
foreach (var stream in streams)
{
Console.WriteLine($"Stream: {stream.Key}");
Console.WriteLine($"Content: {stream.Value.DisplayContent}");
if (!string.IsNullOrEmpty(stream.Value.HexView))
{
Console.WriteLine("\nHex View:");
Console.WriteLine(stream.Value.HexView);
}
Console.WriteLine();
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
} |
What feature or improvement do you think would benefit Files?
Hello, I know there is already a "Show alternate data streams" option in Files, but it shows all ADS nearby file, but it seems more useful to show ADS fields in file properties (like hashes).
Requirements
Files Version
3.3.0.0
Windows Version
Windows 10 22H2 19045.3930
Comments
No response
The text was updated successfully, but these errors were encountered: