diff --git a/.github/workflows/dotnet-package.yml b/.github/workflows/dotnet-package.yml index d2e20c2..44c08b2 100644 --- a/.github/workflows/dotnet-package.yml +++ b/.github/workflows/dotnet-package.yml @@ -13,11 +13,11 @@ jobs: contents: read steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.x' # SDK Version to use. + dotnet-version: '9.0.x' # SDK Version to use. - name: Build working-directory: ./src run: dotnet build --configuration Release . diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 07bcb27..b860057 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -13,23 +13,15 @@ jobs: steps: - name: Clean run: rm -rf *.* - - uses: actions/checkout@v2 - - name: Setup .NET 6 - uses: actions/setup-dotnet@v1 + - uses: actions/checkout@v4 + - name: Setup .NET 9 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '6.0.x' - - name: Setup .NET 5 - uses: actions/setup-dotnet@v1 + dotnet-version: '9.0.x' + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 with: - dotnet-version: '5.0.x' - - name: Setup .NET Core 3.1 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '3.1.x' - - name: Setup .NET Core 2.1 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '2.1.x' + dotnet-version: '8.0.x' - name: Restore dependencies working-directory: ./src run: dotnet restore @@ -38,4 +30,4 @@ jobs: run: dotnet build --no-restore --configuration Release - name: Test working-directory: ./src - run: dotnet test --no-build --verbosity normal --configuration Release \ No newline at end of file + run: dotnet test --no-build --verbosity normal --configuration Release diff --git a/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj b/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj index 1891661..669c3ef 100644 --- a/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj +++ b/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj @@ -1,18 +1,21 @@  - netcoreapp2.1;netcoreapp3.1;net5.0;net6.0 + net8.0;net9.0 false - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests.cs b/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests.cs index b43da32..d066051 100644 --- a/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests.cs +++ b/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests.cs @@ -1,137 +1,136 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Xunit; namespace FileTypeInterrogator.Tests { public partial class FileTypeInterrogatorTests { - [TestMethod] + [Fact] public void CanDetectAscii() { const string extension = "ascii"; DetectType(extension, result => { - Assert.IsNotNull(result); - Assert.IsTrue(result.Name.StartsWith(extension, StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(result); + Assert.StartsWith(extension, result.Name, StringComparison.OrdinalIgnoreCase); }); } - [TestMethod] + [Fact] public void CanDetectUTF8() { const string extension = "utf8"; DetectType(extension, result => { - Assert.IsNotNull(result); - Assert.IsTrue(result.Name.StartsWith("UTF-8", StringComparison.OrdinalIgnoreCase)); - Assert.IsTrue(result.Name.IndexOf("BOM", StringComparison.OrdinalIgnoreCase) == -1); + Assert.NotNull(result); + Assert.StartsWith("UTF-8", result.Name, StringComparison.OrdinalIgnoreCase); + Assert.True(result.Name.IndexOf("BOM", StringComparison.OrdinalIgnoreCase) == -1); }); } - [TestMethod] + [Fact] public void CanDetectUTF8BOM() { const string extension = "utf8bom"; DetectType(extension, result => { - Assert.IsNotNull(result); - Assert.IsTrue(result.Name.StartsWith("UTF-8", StringComparison.OrdinalIgnoreCase)); - Assert.IsTrue(result.Name.IndexOf("BOM", StringComparison.OrdinalIgnoreCase) > -1); + Assert.NotNull(result); + Assert.StartsWith("UTF-8", result.Name, StringComparison.OrdinalIgnoreCase); + Assert.True(result.Name.IndexOf("BOM", StringComparison.OrdinalIgnoreCase) > -1); }); } - [DataTestMethod] - [DataRow("pdf", DisplayName = "PDF Test")] - [DataRow("fdf", DisplayName = "FDF Test")] + [Theory] + [InlineData("pdf")] + [InlineData("fdf")] public void CanDetectAdobe(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("ai", DisplayName = "AI Test")] - [DataRow("bmp", DisplayName = "BMP Test")] - [DataRow("gif", DisplayName = "GIF Test")] - [DataRow("ico", DisplayName = "ICO Test")] - [DataRow("jp2", DisplayName = "JP2 Test")] - [DataRow("jpg", DisplayName = "JPG Test")] - [DataRow("pcx", DisplayName = "PCX Test")] - [DataRow("png", DisplayName = "PNG Test")] - [DataRow("psd", DisplayName = "PSD Test")] - [DataRow("tif", DisplayName = "TIF Test")] - [DataRow("webp", DisplayName = "WEBP Test")] + [Theory] + [InlineData("bmp")] + [InlineData("gif")] + [InlineData("ico")] + [InlineData("jp2")] + [InlineData("jpg")] + [InlineData("pcx")] + [InlineData("png")] + [InlineData("psd")] + [InlineData("tif")] + [InlineData("webp")] public void CanDetectImages(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("3gp", DisplayName = "3GP Test")] - [DataRow("avi", DisplayName = "AVI Test")] - [DataRow("flv", DisplayName = "FLV Test")] - [DataRow("mid", DisplayName = "MID Test")] - [DataRow("mkv", DisplayName = "MKV Test")] - [DataRow("mp4", DisplayName = "MP4 Test")] - [DataRow("webm", DisplayName = "WEBM Test")] - [DataRow("wmv", DisplayName = "WMV Test")] + [Theory] + [InlineData("3gp")] + [InlineData("avi")] + [InlineData("flv")] + [InlineData("mid")] + [InlineData("mkv")] + [InlineData("mp4")] + [InlineData("webm")] + [InlineData("wmv")] public void CanDetectVideo(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("ac3", DisplayName = "AC3 Test")] - [DataRow("aiff", DisplayName = "AIFF Test")] - [DataRow("flac", DisplayName = "FLAC Test")] - [DataRow("mp3", DisplayName = "MP3 Test")] - [DataRow("ogg", DisplayName = "OGG Test")] - [DataRow("ra", DisplayName = "RA Test")] - [DataRow("wav", DisplayName = "WAV Test")] + [Theory] + [InlineData("ac3")] + [InlineData("aiff")] + [InlineData("flac")] + [InlineData("mp3")] + [InlineData("ogg")] + [InlineData("ra")] + [InlineData("wav")] public void CanDetectAudio(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("doc", DisplayName = "DOC Test")] - [DataRow("docx", DisplayName = "DOC Test")] - [DataRow("odp", DisplayName = "ODP Test")] - [DataRow("odt", DisplayName = "ODT Test")] - [DataRow("ppt", DisplayName = "PPT Test")] - [DataRow("pptx", DisplayName = "PPTX Test")] - [DataRow("rtf", DisplayName = "RTF Test")] - [DataRow("xls", DisplayName = "XLS Test")] - [DataRow("xlsx", DisplayName = "XLSX Test")] + [Theory] + [InlineData("doc")] + [InlineData("docx")] + [InlineData("odp")] + [InlineData("odt")] + [InlineData("ppt")] + [InlineData("pptx")] + [InlineData("rtf")] + [InlineData("xls")] + [InlineData("xlsx")] public void CanDetectOffice(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("otf", DisplayName = "OTF Test")] - [DataRow("ttf", DisplayName = "TTF Test")] - [DataRow("woff", DisplayName = "WOFF Test")] + [Theory] + [InlineData("otf")] + [InlineData("ttf")] + [InlineData("woff")] public void CanDetectFont(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("7z", DisplayName = "7Z Test")] - [DataRow("gz", DisplayName = "GZ Test")] - [DataRow("rar", DisplayName = "RAR Test")] - [DataRow("zip", DisplayName = "ZIP Test")] + [Theory] + [InlineData("7z")] + [InlineData("gz")] + [InlineData("rar")] + [InlineData("zip")] public void CanDetectCompressed(string extension) { DetectType(extension); } - [DataTestMethod] - [DataRow("eml", DisplayName = "EML Test")] - [DataRow("vcf", DisplayName = "VCF Test")] + [Theory] + [InlineData("eml")] + [InlineData("vcf")] public void CanDetectOther(string extension) { DetectType(extension); @@ -141,12 +140,12 @@ private void DetectType(string extension) { DetectType(extension, result => { - Assert.IsNotNull(result); - Assert.IsTrue( + Assert.NotNull(result); + Assert.True( result.FileType.Equals(extension, StringComparison.OrdinalIgnoreCase) || result.Alias?.Any(a => a.Equals(extension, StringComparison.OrdinalIgnoreCase)) == true, - "{0} and/or {1} do not equal {2}", - result.FileType, result.Alias?.FirstOrDefault(), extension); + string.Format("{0} and/or {1} do not equal {2}", + result.FileType, result.Alias?.FirstOrDefault(), extension)); }); } diff --git a/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests_Alias.cs b/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests_Alias.cs index c281c1c..03fad1d 100644 --- a/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests_Alias.cs +++ b/src/FileTypeInterrogator.Tests/FileTypeInterrogatorTests_Alias.cs @@ -1,20 +1,18 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.IO; +using System.IO; +using Xunit; namespace FileTypeInterrogator.Tests { - [TestClass] public partial class FileTypeInterrogatorTests { private IFileTypeInterrogator fileTypeInterrogator; - [TestInitialize] - public void Init() + public FileTypeInterrogatorTests() { fileTypeInterrogator = new FileTypeInterrogator(); } - [TestMethod] + [Fact] public void CanDetectAlias_Jpg() { var filePath = GetFileByType("jpg"); @@ -22,10 +20,10 @@ public void CanDetectAlias_Jpg() var result = fileTypeInterrogator.IsType(fileContents, "jpg"); - Assert.IsTrue(result); + Assert.True(result); } - [TestMethod] + [Fact] public void CanDetectAlias_Jpeg() { var filePath = GetFileByType("jpg"); @@ -33,10 +31,10 @@ public void CanDetectAlias_Jpeg() var result = fileTypeInterrogator.IsType(fileContents, "jpeg"); - Assert.IsTrue(result); + Assert.True(result); } - [TestMethod] + [Fact] public void CanDetectJpg_By_MimeType() { var filePath = GetFileByType("jpg"); @@ -44,7 +42,7 @@ public void CanDetectJpg_By_MimeType() var result = fileTypeInterrogator.IsType(fileContents, "image/jpeg"); - Assert.IsTrue(result); + Assert.True(result); } } } diff --git a/src/FileTypeInterrogator.sln b/src/FileTypeInterrogator.sln index 8f10d28..c22af02 100644 --- a/src/FileTypeInterrogator.sln +++ b/src/FileTypeInterrogator.sln @@ -1,11 +1,20 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.421 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTypeInterrogator", "FileTypeInterrogator\FileTypeInterrogator.csproj", "{E5333901-A30A-45D0-B787-BC2F58208F9F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTypeInterrogator.Tests", "FileTypeInterrogator.Tests\FileTypeInterrogator.Tests.csproj", "{8181D724-8886-4541-8819-9FF208A28074}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTypeInterrogator.Tests", "FileTypeInterrogator.Tests\FileTypeInterrogator.Tests.csproj", "{8181D724-8886-4541-8819-9FF208A28074}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{42DC82B3-A307-4945-AC5C-D0A6D527F942}" + ProjectSection(SolutionItems) = preProject + ..\.editorconfig = ..\.editorconfig + ..\.gitattributes = ..\.gitattributes + ..\.gitignore = ..\.gitignore + ..\LICENSE.md = ..\LICENSE.md + ..\README.md = ..\README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs b/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs index b53d442..d5b21eb 100644 --- a/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs +++ b/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs @@ -7,7 +7,7 @@ namespace FileTypeInterrogator { /// - /// Base for interacting files by magic number + /// Base for identifying files by magic number. /// public abstract class BaseFileTypeInterrogator : IFileTypeInterrogator { @@ -44,7 +44,7 @@ public FileTypeInfo DetectType(Stream inputStream) inputStream.Position = 0; byte[] byteBuffer = new byte[inputStream.Length]; - inputStream.Read(byteBuffer, 0, byteBuffer.Length); + _ = inputStream.Read(byteBuffer, 0, byteBuffer.Length); if (inputStream.CanSeek) inputStream.Position = 0; @@ -124,7 +124,7 @@ public bool IsType(byte[] fileContent, string extensionAliasOrMimeType) return false; } - private static bool IsMatchingType(IList input, FileTypeInfo type) + private static bool IsMatchingType(ReadOnlySpan input, FileTypeInfo type) { // find an initial match based on the header and offset var isMatch = FindMatch(input, type.Header, type.Offset); @@ -135,7 +135,7 @@ private static bool IsMatchingType(IList input, FileTypeInfo type) { // find all indices of matching the 1st byte of the additional sequence var matchingIndices = new List(); - for (int i = 0; i < input.Count; i++) + for (int i = 0; i < input.Length; i++) { if (input[i] == type.SubHeader[0]) matchingIndices.Add(i); @@ -154,19 +154,19 @@ private static bool IsMatchingType(IList input, FileTypeInfo type) return isMatch; } - private static bool FindMatch(IList input, IList searchArray, int offset = 0) + private static bool FindMatch(ReadOnlySpan input, ReadOnlySpan searchArray, int offset = 0) { // file isn't long enough to even search the proper index, not a match - if (input.Count <= offset) + if (input.Length <= offset) return false; int matchingCount = 0; - for (var i = 0; i < searchArray.Count; i++) + for (var i = 0; i < searchArray.Length; i++) { // set the offset location var calculatedOffset = i + offset; - if (input.Count <= calculatedOffset) + if (input.Length <= calculatedOffset) break; // if file offset is not set to zero, we need to take this into account when comparing. @@ -179,7 +179,7 @@ private static bool FindMatch(IList input, IList searchArray, int of } matchingCount++; } - return matchingCount == searchArray.Count; + return matchingCount == searchArray.Length; } private static IEnumerable LoadFileTypes(string flatFileData) @@ -234,7 +234,7 @@ private static bool IsText(byte[] input, out bool hasBOM) return isAscii || IsUTF8(input, out hasBOM); } - private static bool IsAscii(byte[] input) + private static bool IsAscii(ReadOnlySpan input) { const byte maxAscii = 0x7F; foreach (var b in input) diff --git a/src/FileTypeInterrogator/FileTypeInfo.cs b/src/FileTypeInterrogator/FileTypeInfo.cs index 98784ce..9f12a77 100644 --- a/src/FileTypeInterrogator/FileTypeInfo.cs +++ b/src/FileTypeInterrogator/FileTypeInfo.cs @@ -7,7 +7,8 @@ namespace FileTypeInterrogator /// public class FileTypeInfo { - internal FileTypeInfo(string name, string fileType, string mimeType, byte[] header, string[] alias = null, int offset = 0, byte[] subHeader = null) + internal FileTypeInfo(string name, string fileType, string mimeType, byte[] header, + string[] alias = null, int offset = 0, byte[] subHeader = null) { Name = name; MimeType = mimeType; @@ -21,9 +22,6 @@ internal FileTypeInfo(string name, string fileType, string mimeType, byte[] head /// /// Gets the name of the file type. /// - /// - /// The name. - /// public string Name { get; private set; } /// @@ -44,25 +42,16 @@ internal FileTypeInfo(string name, string fileType, string mimeType, byte[] head /// /// Gets unique header 'Magic Numbers' to identifiy this file type /// - /// - /// The header. - /// public byte[] Header { get; private set; } /// /// Gets the offset location of the Header details /// - /// - /// The offset. - /// public int Offset { get; private set; } /// /// Gets the additional identifier to guarantee uniqueness of the file type /// - /// - /// The additional identifier. - /// public byte[] SubHeader { get; private set; } /// diff --git a/src/FileTypeInterrogator/FileTypeInterrogator.csproj b/src/FileTypeInterrogator/FileTypeInterrogator.csproj index 8078597..5b06dfa 100644 --- a/src/FileTypeInterrogator/FileTypeInterrogator.csproj +++ b/src/FileTypeInterrogator/FileTypeInterrogator.csproj @@ -1,23 +1,24 @@  - netstandard1.3;netstandard2.0;netstandard2.1;net5.0;net6.0 + netstandard2.0;netstandard2.1;net8.0;net9.0 netstandard library for detecting file types by 'magic numbers', similar to the `file` command in Linux/Unix. Useful for validating file uploads instead of trusting file extensions. - 2022 @ ghost1face, BeyondTechIO + 2025 @ ghost1face, BeyondTechIO https://github.com/ghost1face/FileTypeInterrogator https://github.com/ghost1face/FileTypeInterrogator file-type-detection file-types magic-numbers content-type mime-type true - 1.1.0 - 1.1.0 + 2.0.0 + 2.0.0 ghost1face true LICENSE.md - * Update file type support - * Resolved issues with office types not being detected - * .NET6 support + * Remove .ai file support as it conflicts with PDF + * .NET8 and .NET9 support + * Removed support for unsupported frameworks + true true true @@ -28,19 +29,23 @@ true - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - all - runtime; build; native; contentfiles; analyzers - + + + + diff --git a/src/FileTypeInterrogator/definitions_flat b/src/FileTypeInterrogator/definitions_flat index 812a583..dc152bb 100644 --- a/src/FileTypeInterrogator/definitions_flat +++ b/src/FileTypeInterrogator/definitions_flat @@ -150,7 +150,6 @@ 0 long 736D5F Program Database pdb application/vnd.palm 0 long 737A657A Program Database pdb application/vnd.palm 0 long ACED0005737200126267626C69747A2E Program Database pdb application/vnd.palm -0 long 25504446 786D6C6E733A696C6C7573747261746F72 Adobe Illustrator Artwork File ai application/postscript 0 long 25504446 Portable Document Format File pdf application/pdf 0 long 50350A Portable Gray Map Image pgm image/x-portable-graymap 0 long 5B706C61796C6973745D PL/SQL Stored Procedure pls application/pls+xml