Skip to content

Commit 944b700

Browse files
authored
Add magic number checks to support image files without an extension (#1868)
* Add magic number checks for image file validation Implements magic number checks to detect images in files without extensions. * Refactor image signature checks for better clarity
1 parent 75854ac commit 944b700

1 file changed

Lines changed: 95 additions & 1 deletion

File tree

  • QuickLook.Plugin/QuickLook.Plugin.ImageViewer

QuickLook.Plugin/QuickLook.Plugin.ImageViewer/Plugin.cs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,103 @@ public bool CanHandle(string path)
120120
if (WebHandler.TryCanHandle(path))
121121
return true;
122122

123+
if (Directory.Exists(path))
124+
return false;
125+
123126
// Disabled due mishandling text file types e.g., "*.config".
124127
// Only check extension for well known image and animated image types.
125-
return !Directory.Exists(path) && WellKnownExtensions.Any(ext => path.EndsWith(ext, StringComparison.OrdinalIgnoreCase));
128+
if (WellKnownExtensions.Any(ext => path.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
129+
return true;
130+
131+
// For files without extensions, check magic numbers for common image formats
132+
if (!Path.HasExtension(path))
133+
{
134+
return IsImageByMagicNumber(path);
135+
}
136+
137+
return false;
138+
}
139+
140+
private static bool IsImageByMagicNumber(string path)
141+
{
142+
try
143+
{
144+
if (!File.Exists(path))
145+
return false;
146+
147+
ReadOnlySpan<byte> pngSignature = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
148+
ReadOnlySpan<byte> jpegSignature = new byte[] { 0xFF, 0xD8, 0xFF };
149+
ReadOnlySpan<byte> gif87Signature = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 };
150+
ReadOnlySpan<byte> gif89Signature = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
151+
ReadOnlySpan<byte> bmpSignature = new byte[] { 0x42, 0x4D };
152+
ReadOnlySpan<byte> webpRiffSignature = new byte[] { 0x52, 0x49, 0x46, 0x46 };
153+
ReadOnlySpan<byte> webpWebpSignature = new byte[] { 0x57, 0x45, 0x42, 0x50 };
154+
155+
const int maxSignatureLength = 12;
156+
157+
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
158+
if (fs.Length < bmpSignature.Length)
159+
return false;
160+
161+
var buffer = new byte[maxSignatureLength];
162+
var bytesRead = fs.Read(buffer, 0, buffer.Length);
163+
if (bytesRead < bmpSignature.Length)
164+
return false;
165+
166+
// PNG: 89 50 4E 47 0D 0A 1A 0A
167+
if (bytesRead >= pngSignature.Length &&
168+
buffer.AsSpan(0, pngSignature.Length).SequenceEqual(pngSignature))
169+
{
170+
return true;
171+
}
172+
173+
// JPEG: FF D8 FF
174+
if (bytesRead >= jpegSignature.Length &&
175+
buffer.AsSpan(0, jpegSignature.Length).SequenceEqual(jpegSignature))
176+
{
177+
return true;
178+
}
179+
180+
// GIF: GIF87a or GIF89a
181+
if (bytesRead >= gif87Signature.Length &&
182+
(buffer.AsSpan(0, gif87Signature.Length).SequenceEqual(gif87Signature) ||
183+
buffer.AsSpan(0, gif89Signature.Length).SequenceEqual(gif89Signature)))
184+
{
185+
return true;
186+
}
187+
188+
// BMP: BM
189+
if (bytesRead >= bmpSignature.Length &&
190+
buffer.AsSpan(0, bmpSignature.Length).SequenceEqual(bmpSignature))
191+
{
192+
return true;
193+
}
194+
195+
// WebP: RIFF....WEBP
196+
if (bytesRead >= 12 &&
197+
buffer.AsSpan(0, webpRiffSignature.Length).SequenceEqual(webpRiffSignature) &&
198+
buffer.AsSpan(8, webpWebpSignature.Length).SequenceEqual(webpWebpSignature))
199+
{
200+
return true;
201+
}
202+
203+
return false;
204+
}
205+
catch (IOException ex)
206+
{
207+
ProcessHelper.WriteLog($"IO error while checking image magic number for {path}: {ex.Message}");
208+
return false;
209+
}
210+
catch (UnauthorizedAccessException ex)
211+
{
212+
ProcessHelper.WriteLog($"Access denied while checking image magic number for {path}: {ex.Message}");
213+
return false;
214+
}
215+
catch (Exception ex)
216+
{
217+
ProcessHelper.WriteLog($"Unexpected error while checking image magic number for {path}: {ex}");
218+
return false;
219+
}
126220
}
127221

128222
public void Prepare(string path, ContextObject context)

0 commit comments

Comments
 (0)