From c14ab91f130307191124e2f01292b89e7aafbf43 Mon Sep 17 00:00:00 2001 From: Shakes4cc <50526300+Shakes4cc@users.noreply.github.com> Date: Sun, 25 Jan 2026 01:28:26 +0100 Subject: [PATCH 1/2] Add magic number checks for image file validation Implements magic number checks to detect images in files without extensions. --- .../QuickLook.Plugin.ImageViewer/Plugin.cs | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs index e1f001bab..a4731344e 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs @@ -117,9 +117,83 @@ public bool CanHandle(string path) if (WebHandler.TryCanHandle(path)) return true; + if (Directory.Exists(path)) + return false; + // Disabled due mishandling text file types e.g., "*.config". // Only check extension for well known image and animated image types. - return !Directory.Exists(path) && WellKnownExtensions.Any(ext => path.EndsWith(ext, StringComparison.OrdinalIgnoreCase)); + if (WellKnownExtensions.Any(ext => path.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) + return true; + + // For files without extensions, check magic numbers for common image formats + if (!Path.HasExtension(path)) + { + return IsImageByMagicNumber(path); + } + + return false; + } + + private static bool IsImageByMagicNumber(string path) + { + try + { + if (!File.Exists(path)) + return false; + + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + if (fs.Length < 12) + return false; + + var buffer = new byte[12]; + var bytesRead = fs.Read(buffer, 0, buffer.Length); + if (bytesRead < 4) + return false; + + // PNG: 89 50 4E 47 0D 0A 1A 0A + if (bytesRead >= 8 && + buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47 && + buffer[4] == 0x0D && buffer[5] == 0x0A && buffer[6] == 0x1A && buffer[7] == 0x0A) + { + return true; + } + + // JPEG: FF D8 FF + if (bytesRead >= 3 && + buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF) + { + return true; + } + + // GIF: GIF87a or GIF89a + if (bytesRead >= 6 && + buffer[0] == 0x47 && buffer[1] == 0x49 && buffer[2] == 0x46 && + buffer[3] == 0x38 && (buffer[4] == 0x37 || buffer[4] == 0x39) && buffer[5] == 0x61) + { + return true; + } + + // BMP: BM + if (bytesRead >= 2 && + buffer[0] == 0x42 && buffer[1] == 0x4D) + { + return true; + } + + // WebP: RIFF....WEBP + if (bytesRead >= 12 && + buffer[0] == 0x52 && buffer[1] == 0x49 && buffer[2] == 0x46 && buffer[3] == 0x46 && + buffer[8] == 0x57 && buffer[9] == 0x45 && buffer[10] == 0x42 && buffer[11] == 0x50) + { + return true; + } + + return false; + } + catch + { + return false; + } } public void Prepare(string path, ContextObject context) From 828c2ab41442af824b3da83b68c7f666b28a109b Mon Sep 17 00:00:00 2001 From: Shakes4cc <50526300+Shakes4cc@users.noreply.github.com> Date: Sun, 25 Jan 2026 02:08:32 +0100 Subject: [PATCH 2/2] Refactor image signature checks for better clarity --- .../QuickLook.Plugin.ImageViewer/Plugin.cs | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs index a4731344e..e6392ea7b 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs @@ -141,57 +141,77 @@ private static bool IsImageByMagicNumber(string path) if (!File.Exists(path)) return false; + ReadOnlySpan pngSignature = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + ReadOnlySpan jpegSignature = new byte[] { 0xFF, 0xD8, 0xFF }; + ReadOnlySpan gif87Signature = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }; + ReadOnlySpan gif89Signature = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; + ReadOnlySpan bmpSignature = new byte[] { 0x42, 0x4D }; + ReadOnlySpan webpRiffSignature = new byte[] { 0x52, 0x49, 0x46, 0x46 }; + ReadOnlySpan webpWebpSignature = new byte[] { 0x57, 0x45, 0x42, 0x50 }; + + const int maxSignatureLength = 12; + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - if (fs.Length < 12) + if (fs.Length < bmpSignature.Length) return false; - var buffer = new byte[12]; + var buffer = new byte[maxSignatureLength]; var bytesRead = fs.Read(buffer, 0, buffer.Length); - if (bytesRead < 4) + if (bytesRead < bmpSignature.Length) return false; // PNG: 89 50 4E 47 0D 0A 1A 0A - if (bytesRead >= 8 && - buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47 && - buffer[4] == 0x0D && buffer[5] == 0x0A && buffer[6] == 0x1A && buffer[7] == 0x0A) + if (bytesRead >= pngSignature.Length && + buffer.AsSpan(0, pngSignature.Length).SequenceEqual(pngSignature)) { return true; } // JPEG: FF D8 FF - if (bytesRead >= 3 && - buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF) + if (bytesRead >= jpegSignature.Length && + buffer.AsSpan(0, jpegSignature.Length).SequenceEqual(jpegSignature)) { return true; } // GIF: GIF87a or GIF89a - if (bytesRead >= 6 && - buffer[0] == 0x47 && buffer[1] == 0x49 && buffer[2] == 0x46 && - buffer[3] == 0x38 && (buffer[4] == 0x37 || buffer[4] == 0x39) && buffer[5] == 0x61) + if (bytesRead >= gif87Signature.Length && + (buffer.AsSpan(0, gif87Signature.Length).SequenceEqual(gif87Signature) || + buffer.AsSpan(0, gif89Signature.Length).SequenceEqual(gif89Signature))) { return true; } // BMP: BM - if (bytesRead >= 2 && - buffer[0] == 0x42 && buffer[1] == 0x4D) + if (bytesRead >= bmpSignature.Length && + buffer.AsSpan(0, bmpSignature.Length).SequenceEqual(bmpSignature)) { return true; } // WebP: RIFF....WEBP if (bytesRead >= 12 && - buffer[0] == 0x52 && buffer[1] == 0x49 && buffer[2] == 0x46 && buffer[3] == 0x46 && - buffer[8] == 0x57 && buffer[9] == 0x45 && buffer[10] == 0x42 && buffer[11] == 0x50) + buffer.AsSpan(0, webpRiffSignature.Length).SequenceEqual(webpRiffSignature) && + buffer.AsSpan(8, webpWebpSignature.Length).SequenceEqual(webpWebpSignature)) { return true; } return false; } - catch + catch (IOException ex) + { + ProcessHelper.WriteLog($"IO error while checking image magic number for {path}: {ex.Message}"); + return false; + } + catch (UnauthorizedAccessException ex) + { + ProcessHelper.WriteLog($"Access denied while checking image magic number for {path}: {ex.Message}"); + return false; + } + catch (Exception ex) { + ProcessHelper.WriteLog($"Unexpected error while checking image magic number for {path}: {ex}"); return false; } }