diff --git a/Directory.Version.props b/Directory.Version.props
index 8d2acde..702302f 100644
--- a/Directory.Version.props
+++ b/Directory.Version.props
@@ -1,5 +1,5 @@
- 1.2.0
+ 1.3.0
\ No newline at end of file
diff --git a/README.md b/README.md
index f3cfc66..88244f9 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
# ByteGuard File Validator 
-**ByteGuard.FileValidator** is a lightweight security-focused library for validating user-supplied files in .NET applications.
+**ByteGuard.FileValidator** is a lightweight security-focused library for validating user-supplied files in .NET applications.
+
It helps you enforce consistent file upload rules by checking:
- Allowed file extensions
- File size limits
- File signatures (magic numbers) to detect spoofed types
- Security validation for Office Open XML / Open Document Formats (`.docx`, `.xlsx`, `.pptx`, `.odt`, `.odp`, `.ods`)
-- Malware scan result using a varity of scanners (_requires the addition of a specific ByteGuard.FileValidator scanner package_)
+- Malware scan result using a varity of scanners (_requires the addition of a specific [ByteGuard.FileValidator](https://github.com/ByteGuard-HQ/byteguard-file-validator-net/wiki/Available-AV-scanners) scanner package_)
> ⚠️ **Important:** This package is one layer in a defense-in-depth strategy.
> It does **not** replace endpoint protection, sandboxing, input validation, or other security controls.
@@ -18,9 +19,10 @@ It helps you enforce consistent file upload rules by checking:
- ✅ Validate files by **size**
- ✅ Validate files by **signature (_magic-numbers_)**
- ✅ Validate **specification conformance** for archive-based formats (_Open XML and Open Document Formats_)
-- ✅ **Ensure no malware** through a variety of antimalware scanners
+- ✅ **Ensure no malware** through a variety of [antimalware scanners](https://github.com/ByteGuard-HQ/byteguard-file-validator-net/wiki/Available-AV-scanners)
- ✅ Validate using file path, `Stream`, or `byte[]`
-- ✅ Configure which file types to support
+- ✅ [Rich integration with NET Core](https://github.com/ByteGuard-HQ/byteguard-file-validator-extensions-dependency-injection)
+- ✅ Configure which [file types](https://github.com/ByteGuard-HQ/byteguard-file-validator-net/wiki/Validation-features) to support
- ✅ Configure whether to **throw exceptions** or simply return a boolean
- ✅ **Fluent configuration API** for easy setup
@@ -38,7 +40,7 @@ dotnet add package ByteGuard.FileValidator
### Antimalware scanners
-In order to use the antimalware scanning capabilities, ensure you have a ByteGuard.FileValidator antimalware package referenced as well. You can find the relevant scanner package on [NuGet](https://www.nuget.org/packages?q=ByteGuard.FileValidator.Scanner.&includeComputedFrameworks=true&prerel=true&sortby=relevance) under the namespace `ByteGuard.FileValidator.Scanner`.
+In order to use the antimalware scanning capabilities, ensure you have a [ByteGuard.FileValidator antimalware package](https://github.com/ByteGuard-HQ/byteguard-file-validator-net/wiki/Available-AV-scanners) referenced as well. You can find the relevant scanner package on [NuGet](https://www.nuget.org/packages?q=ByteGuard.FileValidator.Scanner.&includeComputedFrameworks=true&prerel=true&sortby=relevance) under the namespace `ByteGuard.FileValidator.Scanner`.
## Usage
@@ -130,38 +132,7 @@ public async Task Upload(IFormFile file)
## Supported File Extensions
-The following file types are supported by the `FileValidator`:
-
-| Category | Supported extensions |
-| ------------- | --------------------------------------------------------------------------------- |
-| **Documents** | `.doc`, `.docx`, `.xls`, `.xlsx`, `.pptx`, `.odp`, `.ods`, `.odt`, `.pdf`, `.rtf` |
-| **Images** | `.jpg`, `.jpeg`, `.png,`, `.bmp` |
-| **Video** | `.mov`, `.avi`, `.mp4` |
-| **Audio** | `.m4a`, `.mp3`, `.wav` |
-
-### Validation coverage per type
-
-`IsValidFile` always validates:
-
-- File extension (_against `SupportedFileTypes`_)
-- File size (_against `FileSizeLimit`_)
-- File signature (_magic number_)
-- Malware scan result (_if an antimalware scanner has been configured_)
-
-For some formats, additional checks are performed:
-
-- **Microsoft Office / Open Document Format** (`.docx`, `.xlsx`, `.pptx`, `.ods`, `.odp`, `.odt`):
- - Extension
- - File size
- - Signature
- - Basic specification conformance validation
- - Malware scan result
-
-- **Other binary formats**:
- - Extension
- - File size
- - Signature
- - Malware scan result
+The file validator supports a variety of different file extensions. The complete list including the individual validation mechanisms per type, is available on the [WIKI](https://github.com/ByteGuard-HQ/byteguard-file-validator-net/wiki/Validation-features).
## Configuration Options
diff --git a/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs b/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs
index 88cfcba..bfca612 100644
--- a/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs
+++ b/src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs
@@ -36,7 +36,7 @@ public static void ThrowIfInvalid(FileValidatorConfiguration configuration)
}
// Validate file type is supported by the current version of FileValidator.
- if (!FileValidator.SupportedFileDefinitions.Any(f => f.FileType.Equals(fileType)))
+ if (!FileDefinitions.SupportedFileDefinitions.Any(f => f.FileType.Equals(fileType)))
{
throw new UnsupportedFileException($"File type '{fileType}' is not supported in the current version of FileValidator.");
}
diff --git a/src/ByteGuard.FileValidator/FileDefinitions.cs b/src/ByteGuard.FileValidator/FileDefinitions.cs
new file mode 100644
index 0000000..34c6f16
--- /dev/null
+++ b/src/ByteGuard.FileValidator/FileDefinitions.cs
@@ -0,0 +1,316 @@
+using ByteGuard.FileValidator.Models;
+
+namespace ByteGuard.FileValidator;
+
+internal static class FileDefinitions
+{
+ ///
+ /// List of all supported valid file definitions, incl. their header signatures (magic numbers)
+ /// and potentially their corresponding valid subtype signatures.
+ ///
+ internal static readonly List SupportedFileDefinitions = new List
+ {
+ new FileDefinition
+ {
+ FileType = FileExtensions.Jpeg,
+ ValidSignatures = new List
+ {
+ new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Jpg,
+ ValidSignatures = new List
+ {
+ new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Jpe,
+ ValidSignatures = new List
+ {
+ new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Pdf,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x25, 0x50, 0x44, 0x46, 0x2D } // %PDF-
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Png,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } // ‰PNG␍␊␚␊
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Bmp,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x42, 0x4D } // BM
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Doc,
+ ValidSignatures = new List
+ {
+ new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Docx,
+ // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .docx.
+ ValidSignatures = new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Odp,
+ // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odp.
+ ValidSignatures = new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Ods,
+ // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .ods.
+ ValidSignatures = new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Odt,
+ // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odt.
+ ValidSignatures = new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Rtf,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 } // {\rtf1
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Xlsx,
+ // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .xlsx.
+ ValidSignatures = new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Xls,
+ ValidSignatures = new List
+ {
+ new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Pptx,
+ // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .pptx.
+ ValidSignatures = new List
+ {
+ new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.M4a,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp
+ },
+ SignatureOffset = 4,
+ HasSubtype = true,
+ SubtypeOffset = 8,
+ ValidSubtypeSignatures = new List
+ {
+ new byte[] { 0x4D, 0x34, 0x41, 0x20 } // M4A_
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Mov,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp
+ },
+ SignatureOffset = 4,
+ HasSubtype = true,
+ SubtypeOffset = 8,
+ ValidSubtypeSignatures = new List
+ {
+ new byte[] { 0x71, 0x74, 0x20, 0x20 } // qt__ (Quicktime)
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Avi,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF
+ },
+ HasSubtype = true,
+ SubtypeOffset = 8,
+ ValidSubtypeSignatures = new List
+ {
+ new byte[] { 0x41, 0x56, 0x49, 0x20 } // AVI_
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Mp3,
+ ValidSignatures = new List
+ {
+ new byte[] { 0xFF, 0xFB }, // Without ID3 tag
+ new byte[] { 0xFF, 0xF2 }, // Without ID3 tag
+ new byte[] { 0xFF, 0xF3 }, // Without ID3 tag
+ new byte[] { 0x49, 0x44, 0x33 } // ID3
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Mp4,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp
+ },
+ SignatureOffset = 4,
+ HasSubtype = true,
+ SubtypeOffset = 8,
+ ValidSubtypeSignatures = new List
+ {
+ new byte[] { 0x6D, 0x6D, 0x70, 0x34 }, // mmp4 (MP4)
+ new byte[] { 0x6D, 0x70, 0x34, 0x32 }, // mp42 (MP4 v2)
+ new byte[] { 0x69, 0x73, 0x6F, 0x6D }, // isom (ISO Base Media File)
+ new byte[] { 0x4D, 0x53, 0x4E, 0x56 } // MSNV (MPEG-4)
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Wav,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF
+ },
+ HasSubtype = true,
+ SubtypeOffset = 8,
+ ValidSubtypeSignatures = new List
+ {
+ new byte[] { 0x57, 0x41, 0x56, 0x45 } // WAVE
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Txt,
+ AllowMissingSignature = true
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Csv,
+ AllowMissingSignature = true
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Ico,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x00, 0x00, 0x01, 0x00 }, // ␀␀␁␀
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Heic,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63 } // ftypheic
+ },
+ SignatureOffset = 4
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Gif,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x47, 0x49, 0x46, 0x38 }, // GIF8
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Tif,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x49, 0x49, 0x2A, 0x00 }, // II*␀ (Intel byte order)
+ new byte[] { 0x4D, 0x4D, 0x00, 0x2A }, // MM␀* (Motorola byte order)
+ new byte[] { 0x49, 0x49, 0x2B, 0x00 }, // II+␀ (BigTIFF, Intel byte order)
+ new byte[] { 0x4D, 0x4D, 0x00, 0x2B } // MM␀+ (BigTIFF, Motorola byte order)
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Tiff,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x49, 0x49, 0x2A, 0x00 }, // II*␀ (Intel byte order)
+ new byte[] { 0x4D, 0x4D, 0x00, 0x2A }, // MM␀* (Motorola byte order)
+ new byte[] { 0x49, 0x49, 0x2B, 0x00 }, // II+␀ (BigTIFF, Intel byte order)
+ new byte[] { 0x4D, 0x4D, 0x00, 0x2B } // MM␀+ (BigTIFF, Motorola byte order)
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Ogg,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Oga,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Ogv,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS
+ }
+ },
+ new FileDefinition
+ {
+ FileType = FileExtensions.Ogx,
+ ValidSignatures = new List
+ {
+ new byte[] { 0x4F, 0x67, 0x67, 0x53 } // OggS
+ }
+ }
+ };
+}
diff --git a/src/ByteGuard.FileValidator/FileExtensions.cs b/src/ByteGuard.FileValidator/FileExtensions.cs
index fbbdee1..e48fa49 100644
--- a/src/ByteGuard.FileValidator/FileExtensions.cs
+++ b/src/ByteGuard.FileValidator/FileExtensions.cs
@@ -109,5 +109,60 @@ public static class FileExtensions
/// Waveform Audio File Format.
///
public const string Wav = ".wav";
+
+ ///
+ /// Text File.
+ ///
+ public const string Txt = ".txt";
+
+ ///
+ /// Comma-Separated Values.
+ ///
+ public const string Csv = ".csv";
+
+ ///
+ /// Icon File.
+ ///
+ public const string Ico = ".ico";
+
+ ///
+ /// High Efficiency Image Container.
+ ///
+ public const string Heic = ".heic";
+
+ ///
+ /// Graphics Interchange Format.
+ ///
+ public const string Gif = ".gif";
+
+ ///
+ /// Tag Image File Format.
+ ///
+ public const string Tif = ".tif";
+
+ ///
+ /// Tag Image File Format.
+ ///
+ public const string Tiff = ".tiff";
+
+ ///
+ /// Ogg Vorbis Audio.
+ ///
+ public const string Ogg = ".ogg";
+
+ ///
+ /// Ogg Vorbis Audio.
+ ///
+ public const string Oga = ".oga";
+
+ ///
+ /// Ogg Video.
+ ///
+ public const string Ogv = ".ogv";
+
+ ///
+ /// Ogg Multiplex.
+ ///
+ public const string Ogx = ".ogx";
}
}
diff --git a/src/ByteGuard.FileValidator/FileValidator.cs b/src/ByteGuard.FileValidator/FileValidator.cs
index 1451ed3..77333c0 100644
--- a/src/ByteGuard.FileValidator/FileValidator.cs
+++ b/src/ByteGuard.FileValidator/FileValidator.cs
@@ -1,7 +1,6 @@
using DocumentFormat.OpenXml.Packaging;
using ByteGuard.FileValidator.Configuration;
using ByteGuard.FileValidator.Exceptions;
-using ByteGuard.FileValidator.Models;
using ByteGuard.FileValidator.Validators;
using ByteGuard.FileValidator.Scanners;
@@ -12,227 +11,6 @@ namespace ByteGuard.FileValidator
///
public class FileValidator
{
- ///
- /// List of all supported valid file definitions, incl. their header signatures (magic numbers)
- /// and potentially their corresponding valid subtype signatures.
- ///
- internal static readonly List SupportedFileDefinitions = new List
- {
- new FileDefinition
- {
- FileType = FileExtensions.Jpeg,
- ValidSignatures = new List
- {
- new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Jpg,
- ValidSignatures = new List
- {
- new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Jpe,
- ValidSignatures = new List
- {
- new byte[] { 0xFF, 0xD8, 0xFF } // ÿØÿ
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Pdf,
- ValidSignatures = new List
- {
- new byte[] { 0x25, 0x50, 0x44, 0x46, 0x2D } // %PDF-
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Png,
- ValidSignatures = new List
- {
- new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } // ‰PNG␍␊␚␊
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Bmp,
- ValidSignatures = new List
- {
- new byte[] { 0x42, 0x4D } // BM
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Doc,
- ValidSignatures = new List
- {
- new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Docx,
- // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .docx.
- ValidSignatures = new List
- {
- new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Odp,
- // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odp.
- ValidSignatures = new List
- {
- new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Ods,
- // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .ods.
- ValidSignatures = new List
- {
- new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Odt,
- // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .odt.
- ValidSignatures = new List
- {
- new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Rtf,
- ValidSignatures = new List
- {
- new byte[] { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 } // {\rtf1
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Xlsx,
- // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .xlsx.
- ValidSignatures = new List
- {
- new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Xls,
- ValidSignatures = new List
- {
- new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } // ÐÏ␑ࡱ␚á
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Pptx,
- // WARNING: This shares the same signature as .zip and could potentially allow for .zip disguised as .pptx.
- ValidSignatures = new List
- {
- new byte[] { 0x50, 0x4B, 0x03, 0x04 } // PK␃␄
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.M4a,
- ValidSignatures = new List
- {
- new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp
- },
- SignatureOffset = 4,
- HasSubtype = true,
- SubtypeOffset = 8,
- ValidSubtypeSignatures = new List
- {
- new byte[] { 0x4D, 0x34, 0x41, 0x20 } // M4A_
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Mov,
- ValidSignatures = new List
- {
- new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp
- },
- SignatureOffset = 4,
- HasSubtype = true,
- SubtypeOffset = 8,
- ValidSubtypeSignatures = new List
- {
- new byte[] { 0x71, 0x74, 0x20, 0x20 } // qt__ (Quicktime)
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Avi,
- ValidSignatures = new List
- {
- new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF
- },
- HasSubtype = true,
- SubtypeOffset = 8,
- ValidSubtypeSignatures = new List
- {
- new byte[] { 0x41, 0x56, 0x49, 0x20 } // AVI_
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Mp3,
- ValidSignatures = new List
- {
- new byte[] { 0xFF, 0xFB }, // Without ID3 tag
- new byte[] { 0xFF, 0xF2 }, // Without ID3 tag
- new byte[] { 0xFF, 0xF3 }, // Without ID3 tag
- new byte[] { 0x49, 0x44, 0x33 } // ID3
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Mp4,
- ValidSignatures = new List
- {
- new byte[] { 0x66, 0x74, 0x79, 0x70 } // ftyp
- },
- SignatureOffset = 4,
- HasSubtype = true,
- SubtypeOffset = 8,
- ValidSubtypeSignatures = new List
- {
- new byte[] { 0x6D, 0x6D, 0x70, 0x34 }, // mmp4 (MP4)
- new byte[] { 0x6D, 0x70, 0x34, 0x32 }, // mp42 (MP4 v2)
- new byte[] { 0x69, 0x73, 0x6F, 0x6D }, // isom (ISO Base Media File)
- new byte[] { 0x4D, 0x53, 0x4E, 0x56 } // MSNV (MPEG-4)
- }
- },
- new FileDefinition
- {
- FileType = FileExtensions.Wav,
- ValidSignatures = new List
- {
- new byte[] { 0x52, 0x49, 0x46, 0x46 } // RIFF
- },
- HasSubtype = true,
- SubtypeOffset = 8,
- ValidSubtypeSignatures = new List
- {
- new byte[] { 0x57, 0x41, 0x56, 0x45 } // WAVE
- }
- }
- };
-
///
/// Specific file extensions for files that are Open XML documents.
/// These require extra care when validating, as Open XML files are ZIP-archives and can contain potentially harmful content.
@@ -454,7 +232,7 @@ public bool IsValidFileType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
var isSupported = _configuration.SupportedFileTypes.Contains(extension, StringComparer.InvariantCultureIgnoreCase) &&
- SupportedFileDefinitions.Any(fd => fd.FileType.Equals(extension, StringComparison.InvariantCultureIgnoreCase));
+ FileDefinitions.SupportedFileDefinitions.Any(fd => fd.FileType.Equals(extension, StringComparison.InvariantCultureIgnoreCase));
if (!isSupported && _configuration.ThrowExceptionOnInvalidFile)
{
@@ -509,7 +287,7 @@ public bool HasValidSignature(string fileName, Stream stream)
throw new InvalidOperationException("Stream is not seekable.");
}
- var fileDefinition = SupportedFileDefinitions.FirstOrDefault(fd =>
+ var fileDefinition = FileDefinitions.SupportedFileDefinitions.FirstOrDefault(fd =>
fd.FileType.Equals(extension, StringComparison.InvariantCultureIgnoreCase));
if (fileDefinition == null)
@@ -522,6 +300,12 @@ public bool HasValidSignature(string fileName, Stream stream)
return false;
}
+ // If the file definition allows for missing signatures, we can return early as the signature validation is effectively bypassed.
+ if (fileDefinition.AllowMissingSignature)
+ {
+ return true;
+ }
+
// As PDF documents are somewhat special in terms of both signature validation,
// we need to investigate these files further. The PdfValidator is made specifically
// for this purpose.
diff --git a/src/ByteGuard.FileValidator/Models/FileDefinition.cs b/src/ByteGuard.FileValidator/Models/FileDefinition.cs
index 2700c2f..df623d7 100644
--- a/src/ByteGuard.FileValidator/Models/FileDefinition.cs
+++ b/src/ByteGuard.FileValidator/Models/FileDefinition.cs
@@ -10,6 +10,16 @@ internal class FileDefinition
///
public string FileType { get; set; } = default!;
+ ///
+ /// Whether this file type is allowed to omit a binary signature. Defaults to false.
+ ///
+ ///
+ /// Some file types, such as .txt files, do not have a binary signature.
+ /// Setting this property to true allows files of this type to be considered valid even if they do not have a binary signature.
+ /// This is useful for file types that do not have a binary signature, but still need to be validated by other mechanisms.
+ ///
+ public bool AllowMissingSignature { get; set; } = false;
+
///
/// Valid header signatures.
///
diff --git a/tests/ByteGuard.FileValidator.Tests.Unit/FileValidatorTests.cs b/tests/ByteGuard.FileValidator.Tests.Unit/FileValidatorTests.cs
index 35bd941..facb7fc 100644
--- a/tests/ByteGuard.FileValidator.Tests.Unit/FileValidatorTests.cs
+++ b/tests/ByteGuard.FileValidator.Tests.Unit/FileValidatorTests.cs
@@ -418,6 +418,27 @@ public void HasValidSignature_FileNameDoesNotHaveExtension_ShouldThrowArgumentEx
Assert.Throws(act);
}
+ [Theory(DisplayName = "HasValidSignature(byte[]) should skip signature validation for file types that allow missing signatures and return true")]
+ [InlineData(new byte[] { 0x74, 0x65, 0x73, 0x74 }, "test.txt")] // TXT
+ [InlineData(new byte[] { 0x74, 0x65, 0x73, 0x74, 0x3B, 0x63, 0x6F, 0x6C, 0x75, 0x6D, 0x6E, 0x31 }, "test.csv")] // CSV
+ public void HasValidSignature_FileTypeAllowsMissingSignature_ShouldReturnTrue(byte[] fileBytes, string fileName)
+ {
+ // Arrange
+ var config = new FileValidatorConfiguration
+ {
+ SupportedFileTypes = [Path.GetExtension(fileName)],
+ FileSizeLimit = ByteSize.MegaBytes(25),
+ ThrowExceptionOnInvalidFile = true
+ };
+ var fileValidator = new FileValidator(config);
+
+ // Act
+ var result = fileValidator.HasValidSignature(fileName, fileBytes);
+
+ // Assert
+ Assert.True(result);
+ }
+
[Theory(DisplayName = "IsOpenXmlFormat should return true for valid Open XML files")]
[InlineData("test.docx")] // DOCX
[InlineData("test.xlsx")] // XLSX