diff --git a/Mime-Detective.sln b/Mime-Detective.sln index 450e522..90215a1 100644 --- a/Mime-Detective.sln +++ b/Mime-Detective.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.13 +VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{098944FB-C1C9-48BE-AA37-CD3C5C336A84}" ProjectSection(SolutionItems) = preProject @@ -22,7 +22,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A50202E7-0 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mime-Detective", "src\Mime-Detective\Mime-Detective.csproj", "{40608F32-BF6E-4DE4-85AE-EF71C69EF18D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mime-Detective.Benchmarks", "test\Mime-Detective.Benchmarks\Mime-Detective.Benchmarks.csproj", "{7F622459-3B42-4393-A08D-BEB47432628A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mime-Detective.Benchmarks", "test\Mime-Detective.Benchmarks\Mime-Detective.Benchmarks.csproj", "{7F622459-3B42-4393-A08D-BEB47432628A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -51,4 +51,7 @@ Global {40608F32-BF6E-4DE4-85AE-EF71C69EF18D} = {17C4E0DE-B863-4A81-B755-62E663D041F1} {7F622459-3B42-4393-A08D-BEB47432628A} = {A50202E7-0386-4EB3-B09C-00EFCAE360F7} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D55424FE-E903-489F-8709-BC2A5802AB45} + EndGlobalSection EndGlobal diff --git a/src/Mime-Detective/Analyzers/ArrayBasedTrie.cs b/src/Mime-Detective/Analyzers/ArrayBasedTrie.cs new file mode 100644 index 0000000..d12f362 --- /dev/null +++ b/src/Mime-Detective/Analyzers/ArrayBasedTrie.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +namespace MimeDetective.Analyzers +{ + public sealed class ArrayBasedTrie : IFileAnalyzer + { + public const int NullStandInValue = 256; + public const int MaxNodeSize = 257; + + private List Nodes = new List(10); + + /// + /// Constructs an empty ArrayBasedTrie, to add definitions + /// + public ArrayBasedTrie() + { + } + + /// + /// Constructs an ArrayBasedTrie from an Enumerable of FileTypes, to add more definitions + /// + /// + public ArrayBasedTrie(IEnumerable types) + { + if (types is null) + throw new ArgumentNullException(nameof(types)); + + foreach (var type in types) + { + if ((object)type != null) + Insert(type); + } + + Nodes = Nodes.OrderBy(x => x.Offset).ToList(); + } + + public FileType Search(in ReadResult readResult) + { + FileType match = null; + + //iterate through offset nodes + for (int offsetNodeIndex = 0; offsetNodeIndex < Nodes.Count; offsetNodeIndex++) + { + //get offset node + var offsetNode = Nodes[offsetNodeIndex]; + + int i = offsetNode.Offset; + byte value = readResult.Array[i]; + + var node = offsetNode.Children[value]; + + if (node is null) + { + node = offsetNode.Children[NullStandInValue]; + + if (node is null) + break; + } + + if ((object)node.Record != null) + match = node.Record; + + i++; + + //iterate through the current trie + for (; i < readResult.ReadLength; i++) + { + value = readResult.Array[i]; + + var prevNode = node; + node = node.Children[value]; + + if (node is null) + { + node = prevNode.Children[NullStandInValue]; + + if (node is null) + break; + } + + if ((object)node.Record != null) + match = node.Record; + } + + if ((object)match != null) + break; + } + + return match; + } + + public void Insert(FileType type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + OffsetNode match = null; + + foreach (var offsetNode in Nodes) + { + if (offsetNode.Offset == type.HeaderOffset) + { + match = offsetNode; + break; + } + } + + if (match is null) + { + match = new OffsetNode(type.HeaderOffset); + Nodes.Add(match); + } + + match.Insert(type); + } + + private sealed class OffsetNode + { + public readonly ushort Offset; + public readonly Node[] Children; + + public OffsetNode(ushort offset) + { + if (offset > (MimeTypes.MaxHeaderSize - 1)) + throw new ArgumentException("Offset cannot be greater than MaxHeaderSize - 1"); + + Offset = offset; + Children = new Node[MaxNodeSize]; + } + + public void Insert(FileType type) + { + int i = 0; + byte? value = type.Header[i]; + int arrayPos = value ?? NullStandInValue; + + var node = Children[arrayPos]; + + if (node is null) + { + node = new Node(value); + Children[arrayPos] = node; + } + + i++; + + for (; i < type.Header.Length; i++) + { + value = type.Header[i]; + arrayPos = value ?? NullStandInValue; + var prevNode = node; + node = node.Children[arrayPos]; + + if (node is null) + { + var newNode = new Node(value); + + if (i == type.Header.Length - 1) + newNode.Record = type; + + node = prevNode.Children[arrayPos] = newNode; + } + } + } + } + + private sealed class Node + { + public readonly Node[] Children; + + //if complete node then this not null + public FileType Record; + + public readonly byte? Value; + + public Node(byte? value) + { + Value = value; + Children = new Node[MaxNodeSize]; + Record = null; + } + } + } +} \ No newline at end of file diff --git a/src/Mime-Detective/Analyzers/DictionaryBasedTrie.cs b/src/Mime-Detective/Analyzers/DictionaryBasedTrie.cs new file mode 100644 index 0000000..a37909c --- /dev/null +++ b/src/Mime-Detective/Analyzers/DictionaryBasedTrie.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MimeDetective.Analyzers +{ + public sealed class DictionaryBasedTrie : IFileAnalyzer + { + private const ushort NullStandInValue = 256; + + //root dictionary contains the nodes with offset values + private Dictionary Nodes { get; } = new Dictionary(); + + /// + /// Constructs an empty DictionaryBasedTrie + /// + public DictionaryBasedTrie() + { + + } + + /// + /// Constructs a DictionaryBasedTrie from an Enumerable of FileTypes + /// + /// + public DictionaryBasedTrie(IEnumerable types) + { + if (types is null) + throw new ArgumentNullException(nameof(types)); + + foreach (var type in types) + { + Insert(type); + } + } + + public FileType Search(in ReadResult readResult) + { + FileType match = null; + var enumerator = Nodes.GetEnumerator(); + + while (match is null && enumerator.MoveNext()) + { + Node node = enumerator.Current.Value; + + for (int i = node.Value; i < readResult.ReadLength; i++) + { + Node prevNode = node; + + if (!prevNode.Children.TryGetValue(readResult.Array[i], out node) + && !prevNode.Children.TryGetValue(NullStandInValue, out node)) + break; + + if ((object)node.Record != null) + match = node.Record; + } + + if ((object)match != null) + break; + } + + return match; + } + + public void Insert(FileType type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (!Nodes.TryGetValue(type.HeaderOffset, out var offsetNode)) + { + offsetNode = new Node(type.HeaderOffset); + Nodes.Add(type.HeaderOffset, offsetNode); + } + + offsetNode.Insert(type); + } + + private sealed class Node + { + public readonly Dictionary Children = new Dictionary(); + + //if complete node then this not null + public FileType Record; + + public readonly ushort Value; + + public Node(ushort value) + { + Value = value; + } + + public void Insert(FileType type) + { + int i = 0; + ushort value = type.Header[i] ?? NullStandInValue; + + if (!Children.TryGetValue(value, out Node node)) + { + node = new Node(value); + Children.Add(value, node); + } + + i++; + + for (; i < type.Header.Length; i++) + { + value = type.Header[i] ?? NullStandInValue; + + if (!node.Children.ContainsKey(value)) + { + Node newNode = new Node(value); + node.Children.Add(value, newNode); + } + + node = node.Children[value]; + } + + node.Record = type; + } + } + } +} + diff --git a/src/Mime-Detective/Analyzers/IFileAnalyzer.cs b/src/Mime-Detective/Analyzers/IFileAnalyzer.cs new file mode 100644 index 0000000..4cfa8c7 --- /dev/null +++ b/src/Mime-Detective/Analyzers/IFileAnalyzer.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.CompilerServices; + +namespace MimeDetective.Analyzers +{ + public interface IReadOnlyFileAnalyzer + { + FileType Search(in ReadResult readResult); + } + + public interface IFileAnalyzer : IReadOnlyFileAnalyzer + { + void Insert(FileType fileType); + } +} diff --git a/src/Mime-Detective/Analyzers/LinearCountingAnalyzer.cs b/src/Mime-Detective/Analyzers/LinearCountingAnalyzer.cs new file mode 100644 index 0000000..7bba089 --- /dev/null +++ b/src/Mime-Detective/Analyzers/LinearCountingAnalyzer.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MimeDetective.Analyzers +{ + public class LinearCountingAnalyzer : IFileAnalyzer + { + private readonly List types; + + /// + /// Constructs an empty LinearCountingAnalyzer, use to add file types + /// + public LinearCountingAnalyzer() + { + types = new List(); + } + + /// + /// Constructs a LinearCountingAnalyzer using the supplied IEnumerable + /// + /// + public LinearCountingAnalyzer(IEnumerable fileTypes) + { + if (fileTypes is null) + throw new ArgumentNullException(nameof(fileTypes)); + + types = new List(); + + foreach (var fileType in fileTypes) + { + if ((object)fileType != null) + Insert(fileType); + } + } + + public void Insert(FileType fileType) + { + if (fileType is null) + throw new ArgumentNullException(nameof(fileType)); + + types.Add(fileType); + } + + public FileType Search(in ReadResult readResult) + { + if (readResult.ReadLength == 0) + return null; + + uint highestMatchingCount = 0; + FileType highestMatchingType = null; + + // compare the file header to the stored file headers + for (int typeIndex = 0; typeIndex < types.Count; typeIndex++) + { + FileType type = types[typeIndex]; + + uint matchingCount = 0; + int iOffset = type.HeaderOffset; + int readLength = iOffset + type.Header.Length; + + if (readLength > readResult.ReadLength) + continue; + + for (int i = 0; iOffset < readLength; i++, iOffset++) + { + if (type.Header[i] is null || type.Header[i].Value == readResult.Array[iOffset]) + matchingCount++; + } + + if (type.Header.Length == matchingCount && matchingCount > highestMatchingCount) + { + highestMatchingType = type; + highestMatchingCount = matchingCount; + } + } + + return highestMatchingType; + } + } +} diff --git a/src/Mime-Detective/Analyzers/MSOfficeAnalyzer.cs b/src/Mime-Detective/Analyzers/MSOfficeAnalyzer.cs new file mode 100644 index 0000000..017b63d --- /dev/null +++ b/src/Mime-Detective/Analyzers/MSOfficeAnalyzer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MimeDetective.Analyzers +{ + //TODO maybe turn this into an OLE Doc type analyzer + public class MsOfficeAnalyzer : IReadOnlyFileAnalyzer + { + public FileType Key { get; } = MimeTypes.MS_OFFICE; + + public static FileType[] MsDocTypes { get; } = new FileType[] { MimeTypes.PPT, MimeTypes.WORD, MimeTypes.EXCEL }; + + private readonly DictionaryBasedTrie dictTrie; + + public MsOfficeAnalyzer() + { + dictTrie = new DictionaryBasedTrie(MsDocTypes); + } + + public FileType Search(in ReadResult readResult) + { + return dictTrie.Search(in readResult) ?? Key; + } + } +} diff --git a/src/Mime-Detective/Analyzers/MimeAnalyzers.cs b/src/Mime-Detective/Analyzers/MimeAnalyzers.cs new file mode 100644 index 0000000..a418664 --- /dev/null +++ b/src/Mime-Detective/Analyzers/MimeAnalyzers.cs @@ -0,0 +1,57 @@ +using System; +using MimeDetective.Analyzers; +using System.Collections.Generic; + +namespace MimeDetective +{ + /// + /// This static class controls/holds all analyzers used by all extension methods + /// + public static class MimeAnalyzers + { + private static IFileAnalyzer primaryAnalyzer = new DictionaryBasedTrie(MimeTypes.Types); + + /// + /// + /// + public static IFileAnalyzer PrimaryAnalyzer + { + get + { + return primaryAnalyzer; + } + + set + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + primaryAnalyzer = value; + } + } + + //secondary headers should go here + //special handling cases go here + public static Dictionary SecondaryAnalyzers { get; } = new Dictionary(); + + static MimeAnalyzers() + { + SecondaryAnalyzers.Add(MimeTypes.ZIP, new ZipFileAnalyzer()); + SecondaryAnalyzers.Add(MimeTypes.MS_OFFICE, new MsOfficeAnalyzer()); + } + + internal static FileType GetFileType(in ReadResult readResult) + { + FileType match = null; + + match = PrimaryAnalyzer.Search(in readResult); + + if ((object)match != null && SecondaryAnalyzers.TryGetValue(match, out var secondaryAnalyzer)) + { + match = secondaryAnalyzer.Search(in readResult); + } + + return match; + } + } +} \ No newline at end of file diff --git a/src/Mime-Detective/Analyzers/ZipFileAnalyzer.cs b/src/Mime-Detective/Analyzers/ZipFileAnalyzer.cs new file mode 100644 index 0000000..3cc845a --- /dev/null +++ b/src/Mime-Detective/Analyzers/ZipFileAnalyzer.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; + +namespace MimeDetective.Analyzers +{ + public class ZipFileAnalyzer : IReadOnlyFileAnalyzer + { + public FileType Key { get; } = MimeTypes.ZIP; + + //todo if creating a memorysteam there should be a check for min size to avoid most exceptions + //should this catch exceptions generated by passing in a zip header but no the whole file? + /// + /// + /// + /// + /// Any resulting match or for no match + public FileType Search(in ReadResult readResult) + { + bool locallyCreatedStream = false; + Stream mStream = null; + + if (readResult.Source is null) + { + mStream = new MemoryStream(readResult.Array, 0, readResult.ReadLength); + locallyCreatedStream = true; + } + else + mStream = readResult.Source; + + if (mStream.CanSeek && mStream.Position > 0) + mStream.Seek(0, SeekOrigin.Begin); + + using (ZipArchive zipData = new ZipArchive(mStream, ZipArchiveMode.Read, leaveOpen: true)) + { + //check for office xml formats + var officeXml = CheckForOpenXMLDocument(zipData); + + if (officeXml != null) + return officeXml; + + //check for open office formats + var openOffice = CheckForOpenDocument(zipData); + + if (openOffice != null) + return openOffice; + } + + if (locallyCreatedStream) + mStream.Dispose(); + + return MimeTypes.ZIP; + } + + public FileType CheckForOpenXMLDocument(ZipArchive zipData) + { + foreach (var entry in zipData.Entries) + { + if (entry.FullName.StartsWith("word/")) + return MimeTypes.WORDX; + else if (entry.FullName.StartsWith("xl/")) + return MimeTypes.EXCELX; + else if (entry.FullName.StartsWith("ppt/")) + return MimeTypes.PPTX; + } + + return null; + } + + //check for open doc formats + public FileType CheckForOpenDocument(ZipArchive zipFile) + { + ZipArchiveEntry ooMimeType = null; + + foreach (var entry in zipFile.Entries) + { + if (entry.FullName == "mimetype") + { + ooMimeType = entry; + break; + } + } + + if (ooMimeType is null) + return null; + + using (var textReader = new StreamReader(ooMimeType.Open())) + { + var mimeType = textReader.ReadToEnd(); + + if (mimeType == MimeTypes.ODT.Mime) + return MimeTypes.ODT; + else if (mimeType == MimeTypes.ODS.Mime) + return MimeTypes.ODS; + else if (mimeType == MimeTypes.ODP.Mime) + return MimeTypes.ODP; + else if (mimeType == MimeTypes.ODG.Mime) + return MimeTypes.ODG; + else + return null; + } + } + } +} diff --git a/src/Mime-Detective/Extensions/ByteArrayExtensions.cs b/src/Mime-Detective/Extensions/ByteArrayExtensions.cs index 7a07580..95666aa 100644 --- a/src/Mime-Detective/Extensions/ByteArrayExtensions.cs +++ b/src/Mime-Detective/Extensions/ByteArrayExtensions.cs @@ -1,26 +1,23 @@ using System; -using static MimeDetective.InputHelpers; namespace MimeDetective { - public static class ByteArrayExtensions - { - /// - /// Read header of bytes and depending on the information in the header - /// return object FileType. - /// Return null in case when the file type is not identified. - /// Throws Application exception if the file can not be read or does not exist - /// - /// The FileInfo object. - /// FileType or null not identified - public static FileType GetFileType(this byte[] bytes) - { - if (bytes is null) - throw new ArgumentNullException(nameof(bytes)); - - ReadResult readResult = new ReadResult(bytes, bytes.Length); - - return MimeTypes.GetFileType(in readResult); - } - } + public static class ByteArrayExtensions + { + /// + /// Read header of bytes and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null not identified + public static FileType GetFileType(this byte[] bytes) + { + using (ReadResult readResult = new ReadResult(bytes, Math.Min(bytes.Length, MimeTypes.MaxHeaderSize))) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } + } } diff --git a/src/Mime-Detective/Extensions/FileInfo/DocumentExtensions.cs b/src/Mime-Detective/Extensions/FileInfo/DocumentExtensions.cs index 4de2bf1..19d1b05 100644 --- a/src/Mime-Detective/Extensions/FileInfo/DocumentExtensions.cs +++ b/src/Mime-Detective/Extensions/FileInfo/DocumentExtensions.cs @@ -2,58 +2,73 @@ namespace MimeDetective { - /// - /// A set of extension methods for use with document formats. - /// - public static partial class FileInfoExtensions - { - /// - /// Determines whether the specified file is RTF document. - /// - /// The FileInfo. - /// - /// true if the specified file is RTF; otherwise, false. - /// - public static bool IsRtf(this FileInfo fileInfo) => fileInfo.IsType(MimeTypes.RTF); - - /// - /// Determines whether the specified file is PDF. - /// - /// The file. - /// - /// true if the specified file is PDF; otherwise, false. - /// - public static bool IsPdf(this FileInfo file) => file.IsType(MimeTypes.PDF); - - - /// - /// Determines whether the specified file info is ms-word document file - /// This includes .doc and .docx files - /// - /// The file info. - /// - /// true if the specified file info is doc; otherwise, false. - /// - public static bool IsWord(this FileInfo fileInfo) => (fileInfo.IsType(MimeTypes.WORD) || fileInfo.IsType(MimeTypes.WORDX)); - - /// - /// Determines whether the specified file info is ms-word document file - /// This includes .ppt and .pptx files - /// - /// The file info. - /// - /// true if the specified file info is doc; otherwise, false. - /// - public static bool IsPowerPoint(this FileInfo fileInfo) => (fileInfo.IsType(MimeTypes.PPT) || fileInfo.IsType(MimeTypes.PPTX)); - - /// - /// Determines whether the specified file info is ms-word document file - /// this includes old xls and xlsx files - /// - /// The file info. - /// - /// true if the specified file info is doc; otherwise, false. - /// - public static bool IsExcel(this FileInfo fileInfo) => (fileInfo.IsType(MimeTypes.EXCEL) || fileInfo.IsType(MimeTypes.EXCELX)); - } + /// + /// A set of extension methods for use with document formats. + /// + public static partial class FileInfoExtensions + { + /// + /// Determines whether the specified file is RTF document. + /// + /// The FileInfo. + /// + /// true if the specified file is RTF; otherwise, false. + /// + public static bool IsRtf(this FileInfo fileInfo) => fileInfo.IsType(MimeTypes.RTF); + + /// + /// Determines whether the specified file is PDF. + /// + /// The file. + /// + /// true if the specified file is PDF; otherwise, false. + /// + public static bool IsPdf(this FileInfo file) => file.IsType(MimeTypes.PDF); + + + /// + /// Determines whether the specified file info is ms-word document file + /// This includes .doc and .docx files + /// + /// The file info. + /// + /// true if the specified file info is doc; otherwise, false. + /// + public static bool IsWord(this FileInfo fileInfo) + { + var fileType = fileInfo.GetFileType(); + + return (fileType == MimeTypes.WORD) || (fileType == MimeTypes.WORDX) || (fileType == MimeTypes.MS_OFFICE); + } + + /// + /// Determines whether the specified file info is ms-word document file + /// This includes .ppt and .pptx files + /// + /// The file info. + /// + /// true if the specified file info is doc; otherwise, false. + /// + public static bool IsPowerPoint(this FileInfo fileInfo) + { + var fileType = fileInfo.GetFileType(); + + return (fileType == MimeTypes.PPT) || (fileType == MimeTypes.PPTX) || (fileType == MimeTypes.MS_OFFICE); + } + + /// + /// Determines whether the specified file info is ms-word document file + /// this includes old xls and xlsx files + /// + /// The file info. + /// + /// true if the specified file info is doc; otherwise, false. + /// + public static bool IsExcel(this FileInfo fileInfo) + { + var fileType = fileInfo.GetFileType(); + + return (fileType == MimeTypes.EXCEL) || (fileType == MimeTypes.EXCELX) || (fileType == MimeTypes.MS_OFFICE); + } + } } \ No newline at end of file diff --git a/src/Mime-Detective/Extensions/FileInfo/FileInfoExtensions.cs b/src/Mime-Detective/Extensions/FileInfo/FileInfoExtensions.cs index ba250a5..bd9d6ef 100644 --- a/src/Mime-Detective/Extensions/FileInfo/FileInfoExtensions.cs +++ b/src/Mime-Detective/Extensions/FileInfo/FileInfoExtensions.cs @@ -2,117 +2,116 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using static MimeDetective.InputHelpers; namespace MimeDetective { - public static partial class FileInfoExtensions - { - /// - /// Read header of a file and depending on the information in the header - /// return object FileType. - /// Return null in case when the file type is not identified. - /// Throws Application exception if the file can not be read or does not exist - /// - /// The FileInfo object. - /// FileType or null not identified - public static FileType GetFileType(this FileInfo file) - { - if (file is null) - throw new ArgumentNullException(nameof(file)); - - var stream = file.OpenRead(); - - ReadResult readResult = ReadFileHeader(stream, MimeTypes.MaxHeaderSize); - - return MimeTypes.GetFileType(in readResult); - } - - public static async Task GetFileTypeAsync(this FileInfo file) - { - if (file is null) - throw new ArgumentNullException(nameof(file)); - - var stream = file.OpenRead(); - - ReadResult readResult = await ReadFileHeaderAsync(stream, MimeTypes.MaxHeaderSize); - - return MimeTypes.GetFileType(in readResult); - } - - /// - /// Determines whether provided file belongs to one of the provided list of files - /// - /// The file. - /// The required types. - /// - /// true if file of the one of the provided types; otherwise, false. - /// - public static bool IsFileOfTypes(this FileInfo file, List requiredTypes) - { - FileType currentType = file.GetFileType(); - - //TODO Write a test to check if this null check is correct - if (currentType.Mime == null) - return false; - - return requiredTypes.Contains(currentType); - } - - /// - /// Determines whether provided file belongs to one of the provided list of files, - /// where list of files provided by string with Comma-Separated-Values of extensions - /// - /// The file. - /// The required types. - /// - /// true if file of the one of the provided types; otherwise, false. - /// - public static bool IsFileOfTypes(this FileInfo file, String CSV) - { - List providedTypes = MimeTypes.GetFileTypesByExtensions(CSV); - - return file.IsFileOfTypes(providedTypes); - } - - /// - /// Determines whether the specified file is of provided type - /// - /// The file. - /// The FileType - /// - /// true if the specified file is type; otherwise, false. - /// - public static bool IsType(this FileInfo file, FileType type) - { - FileType actualType = GetFileType(file); - - //TODO Write a test to check if this null check is correct - if (actualType.Mime == null) - return false; - - return (actualType.Equals(type)); - } - - /// - /// Checks if the file is executable - /// - /// - /// - public static bool IsExe(this FileInfo fileInfo) => fileInfo.IsType(MimeTypes.DLL_EXE); - - /// - /// Check if the file is Microsoft Installer. - /// Beware, many Microsoft file types are starting with the same header. - /// So use this one with caution. If you think the file is MSI, just need to confirm, use this method. - /// But it could be MSWord or MSExcel, or Powerpoint... - /// - /// - /// - public static bool IsMsi(this FileInfo fileInfo) - { - // MSI has a generic DOCFILE header. Also it matches PPT files - return fileInfo.IsType(MimeTypes.PPT) || fileInfo.IsType(MimeTypes.MSDOC); - } - } + public static partial class FileInfoExtensions + { + /// + /// Read header of a file and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null (for no match) + public static FileType GetFileType(this FileInfo file) + { + using (ReadResult readResult = ReadResult.ReadFileHeader(file)) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } + + /// + /// Read header of a file and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null (for no match) + public static async Task GetFileTypeAsync(this FileInfo file) + { + using (ReadResult readResult = await ReadResult.ReadFileHeaderAsync(file)) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } + + /// + /// Determines whether provided file belongs to one of the provided list of files + /// + /// The file. + /// The required types. + /// + /// true if file of the one of the provided types; otherwise, false. + /// + public static bool IsFileOfTypes(this FileInfo file, List requiredTypes) + { + FileType currentType = file.GetFileType(); + + //TODO Write a test to check if this null check is correct + if (currentType.Mime == null) + return false; + + return requiredTypes.Contains(currentType); + } + + /// + /// Determines whether provided file belongs to one of the provided list of files, + /// where list of files provided by string with Comma-Separated-Values of extensions + /// + /// The file. + /// The required types. + /// + /// true if file of the one of the provided types; otherwise, false. + /// + public static bool IsFileOfTypes(this FileInfo file, String CSV) + { + List providedTypes = MimeTypes.GetFileTypesByExtensions(CSV); + + return file.IsFileOfTypes(providedTypes); + } + + /// + /// Determines whether the specified file is of provided type + /// + /// The file. + /// The FileType + /// + /// true if the specified file is type; otherwise, false. + /// + public static bool IsType(this FileInfo file, FileType type) + { + FileType actualType = GetFileType(file); + + //TODO Write a test to check if this null check is correct + if (actualType.Mime is null) + return false; + + return (actualType.Equals(type)); + } + + /// + /// Checks if the file is executable + /// + /// + /// + public static bool IsExe(this FileInfo fileInfo) => fileInfo.IsType(MimeTypes.DLL_EXE); + + /// + /// Check if the file is Microsoft Installer. + /// Beware, many Microsoft file types are starting with the same header. + /// So use this one with caution. If you think the file is MSI, just need to confirm, use this method. + /// But it could be MSWord or MSExcel, or Powerpoint... + /// + /// + /// + public static bool IsMsi(this FileInfo fileInfo) + { + // MSI has a generic DOCFILE header. Also it matches PPT files + return fileInfo.IsType(MimeTypes.PPT) || fileInfo.IsType(MimeTypes.MS_OFFICE); + } + } } diff --git a/src/Mime-Detective/Extensions/StreamExtensions.cs b/src/Mime-Detective/Extensions/StreamExtensions.cs index 4ddc392..bd048e1 100644 --- a/src/Mime-Detective/Extensions/StreamExtensions.cs +++ b/src/Mime-Detective/Extensions/StreamExtensions.cs @@ -1,82 +1,73 @@ using System; using System.IO; using System.Threading.Tasks; -using static MimeDetective.InputHelpers; namespace MimeDetective.Extensions { - public static class StreamExtensions - { - /// - /// Read header of a stream and depending on the information in the header - /// return object FileType. - /// Return null in case when the file type is not identified. - /// Throws Application exception if the file can not be read or does not exist - /// - /// The FileInfo object. - /// FileType or null not identified - public static FileType GetFileType(this Stream stream) - { - if (stream is null) - throw new ArgumentNullException("Stream cannot be null"); + public static class StreamExtensions + { + /// + /// Read header of a stream and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null not identified + public static FileType GetFileType(this Stream stream) + { + using (ReadResult readResult = ReadResult.ReadHeaderFromStream(stream, shouldDisposeStream: false, shouldResetStreamPosition: true)) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } - ReadResult readResult = ReadHeaderFromStream(stream, MimeTypes.MaxHeaderSize, shouldDisposeStream: false); + /// + /// Read header of a stream and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null not identified + public static FileType GetFileType(this Stream stream, bool shouldDisposeStream = false, bool shouldResetStreamPosition = true) + { + using (ReadResult readResult = ReadResult.ReadHeaderFromStream(stream, shouldDisposeStream, shouldResetStreamPosition)) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } - return MimeTypes.GetFileType(in readResult); - } + /// + /// Read header of a stream and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null not identified + public static async Task GetFileTypeAsync(this Stream stream) + { + using (ReadResult readResult = await ReadResult.ReadHeaderFromStreamAsync(stream, shouldDisposeStream: false, shouldResetStreamPosition: true)) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } - /// - /// Read header of a stream and depending on the information in the header - /// return object FileType. - /// Return null in case when the file type is not identified. - /// Throws Application exception if the file can not be read or does not exist - /// - /// The FileInfo object. - /// FileType or null not identified - public static FileType GetFileType(this Stream stream, bool shouldDisposeStream = false) - { - if (stream is null) - throw new ArgumentNullException("Stream cannot be null"); - - ReadResult readResult = ReadHeaderFromStream(stream, MimeTypes.MaxHeaderSize, shouldDisposeStream); - - return MimeTypes.GetFileType(in readResult); - } - - /// - /// Read header of a stream and depending on the information in the header - /// return object FileType. - /// Return null in case when the file type is not identified. - /// Throws Application exception if the file can not be read or does not exist - /// - /// The FileInfo object. - /// FileType or null not identified - public static async Task GetFileTypeAsync(this Stream stream) - { - if (stream is null) - throw new ArgumentNullException("Stream cannot be null"); - - ReadResult readResult = await InputHelpers.ReadHeaderFromStreamAsync(stream, MimeTypes.MaxHeaderSize, shouldDisposeStream: false); - - return MimeTypes.GetFileType(in readResult); - } - - /// - /// Read header of a stream and depending on the information in the header - /// return object FileType. - /// Return null in case when the file type is not identified. - /// Throws Application exception if the file can not be read or does not exist - /// - /// The FileInfo object. - /// FileType or null not identified - public static async Task GetFileTypeAsync(this Stream stream, bool shouldDisposeStream = false) - { - if (stream is null) - throw new ArgumentNullException("Stream cannot be null"); - - ReadResult readResult = await ReadHeaderFromStreamAsync(stream, MimeTypes.MaxHeaderSize, shouldDisposeStream); - - return MimeTypes.GetFileType(in readResult); - } - } + /// + /// Read header of a stream and depending on the information in the header + /// return object FileType. + /// Return null in case when the file type is not identified. + /// Throws Application exception if the file can not be read or does not exist + /// + /// The FileInfo object. + /// FileType or null not identified + public static async Task GetFileTypeAsync(this Stream stream, bool shouldDisposeStream = false, bool shouldResetStreamPosition = true) + { + using (ReadResult readResult = await ReadResult.ReadHeaderFromStreamAsync(stream, shouldDisposeStream, shouldResetStreamPosition)) + { + return MimeAnalyzers.GetFileType(in readResult); + } + } + } } \ No newline at end of file diff --git a/src/Mime-Detective/FileType.cs b/src/Mime-Detective/FileType.cs index 85a08fb..bd2cdee 100644 --- a/src/Mime-Detective/FileType.cs +++ b/src/Mime-Detective/FileType.cs @@ -3,100 +3,100 @@ namespace MimeDetective { - /// - /// Little data structure to hold information about file types. - /// Holds information about binary header at the start of the file - /// - public class FileType : IEquatable - { - public byte?[] Header { get; } - - public ushort HeaderOffset { get; } - - public string Extension { get; } - - public string Mime { get; } - - private readonly int hashCode; - - /// - /// Initializes a new instance of the class - /// Takes the details of offset for the header - /// - /// Byte array with header. - /// The header offset - how far into the file we need to read the header - /// String with extension. - /// The description of MIME. - public FileType(byte?[] header, string extension, string mime, ushort offset = 0) - { - Header = header ?? throw new ArgumentNullException(nameof(header), $"cannot be null, {nameof(FileType)} needs file header data"); - - HeaderOffset = offset; - Extension = extension; - Mime = mime; - - hashCode = (base.GetHashCode() ^ Header.GetHashCode() ^ HeaderOffset ^ Extension.GetHashCode() ^ Mime.GetHashCode()); - } - - public static bool operator == (FileType a, FileType b) - { - if (a is null && b is null) - return true; - - if (b is null) - return a.Equals(b); - else - return b.Equals(a); - } - - public static bool operator !=(FileType a, FileType b) => !(a == b); - - public override bool Equals(object other) - { - if (other is null) - return false; - - if(other is FileType type) - { - if (HeaderOffset == type.HeaderOffset - && Extension.Equals(type.Extension) - && Mime.Equals(type.Mime) - && CompareHeaders(Header, type.Header)) - return true; - } - - return false; - } - - //todo add tests for both - public bool Equals(FileType other) - { - if (other is null) - return false; - - if (HeaderOffset == other.HeaderOffset - && Extension.Equals(other.Extension) - && Mime.Equals(other.Mime) - && CompareHeaders(Header, other.Header)) - return true; - - return false; - } - - private static bool CompareHeaders(byte?[] array1, byte?[] array2) - { - if (array1.Length != array2.Length) - return false; - - for (int i = 0; i < array1.Length; i++) - if (array1[i] != array2[i]) - return false; - - return true; - } - - public override int GetHashCode() => hashCode; - - public override string ToString() => Extension; - } + /// + /// Data Structure to hold information about file types. + /// Holds information about binary header at the start of the file + /// + public class FileType : IEquatable + { + public byte?[] Header { get; } + + public ushort HeaderOffset { get; } + + public string Extension { get; } + + public string Mime { get; } + + private readonly int hashCode; + + /// + /// Initializes a new instance of the class + /// Takes the details of offset for the header + /// + /// Byte array with header. + /// The header offset - how far into the file we need to read the header + /// String with extension. + /// The description of MIME. + public FileType(byte?[] header, string extension, string mime, ushort offset = 0) + { + Header = header ?? throw new ArgumentNullException(nameof(header), $"cannot be null, {nameof(FileType)} needs file header data"); + + if (offset > (MimeTypes.MaxHeaderSize - 1)) + throw new ArgumentException("Header Offset cannot exceed Max Header Size - 1"); + + HeaderOffset = offset; + Extension = extension; + Mime = mime; + + hashCode = (base.GetHashCode() ^ Header.GetHashCode() ^ HeaderOffset ^ Extension.GetHashCode() ^ Mime.GetHashCode()); + } + + public static bool operator == (FileType a, FileType b) + { + if (a is null && b is null) + return true; + + if (b is null) + return a.Equals(b); + + return b.Equals(a); + } + + public static bool operator !=(FileType a, FileType b) => !(a == b); + + public override bool Equals(object other) + { + if (other is null) + return false; + + if (other is FileType type + && HeaderOffset == type.HeaderOffset + && Extension.Equals(type.Extension) + && Mime.Equals(type.Mime) + && CompareHeaders(Header, type.Header)) + return true; + + return false; + } + + public bool Equals(FileType other) + { + if (other is null) + return false; + + if (HeaderOffset == other.HeaderOffset + && Extension.Equals(other.Extension) + && Mime.Equals(other.Mime) + && CompareHeaders(Header, other.Header)) + return true; + + return false; + } + + private static bool CompareHeaders(byte?[] array1, byte?[] array2) + { + if (array1.Length != array2.Length) + return false; + + for (int i = 0; i < array1.Length; i++) + if (array1[i] != array2[i]) + return false; + + return true; + } + + public override int GetHashCode() => hashCode; + + public override string ToString() => Extension; + } } \ No newline at end of file diff --git a/src/Mime-Detective/Helpers/ReadResult.cs b/src/Mime-Detective/Helpers/ReadResult.cs new file mode 100644 index 0000000..b0899d8 --- /dev/null +++ b/src/Mime-Detective/Helpers/ReadResult.cs @@ -0,0 +1,160 @@ +using System; +using System.Buffers; +using System.IO; +using System.Threading.Tasks; + +namespace MimeDetective +{ + //TODO document when Stream Dispose and ShouldReset are used in IDispose + //TODO handle if stream cannot seek + //TODO if the read fails resources may leak below + /// + /// + /// Layout of this structure will be prone to change (aka plan to back properties with a flags enum) + /// + public readonly struct ReadResult : IDisposable + { + public readonly byte[] Array; + public readonly Stream Source; + public readonly int ReadLength; + + public bool IsArrayRented { get; } + + public bool ShouldDisposeStream { get; } + + public bool ShouldResetStreamPosition { get; } + + /// + /// Non rented array input, Array is Input + /// + /// + /// + public ReadResult(byte[] array, int readLength) + { + if (array is null) + ThrowHelpers.ByteArrayCannotBeNull(); + + if ((uint)readLength > (uint)array.Length) + ThrowHelpers.ReadLengthCannotBeOutOfBounds(); + + Array = array; + Source = null; + ReadLength = readLength; + IsArrayRented = false; + ShouldDisposeStream = false; + ShouldResetStreamPosition = false; + } + + private ReadResult(byte[] array, Stream source, int readLength, bool isArrayRented, bool shouldDisposeStream, bool shouldResetStreamPosition) + { + Array = array; + Source = source; + ReadLength = readLength; + IsArrayRented = isArrayRented; + ShouldDisposeStream = shouldDisposeStream; + ShouldResetStreamPosition = shouldResetStreamPosition; + } + + /// + /// Reads the file header - first (16) bytes from the file + /// + /// The file to work with + /// Array of bytes + public static ReadResult ReadFileHeader(FileInfo file) + { + if (file is null) + ThrowHelpers.FileInfoCannotBeNull(); + + if (!file.Exists) + ThrowHelpers.FileDoesNotExist(file); + + FileStream fileStream = file.OpenRead(); + + byte[] header = ArrayPool.Shared.Rent(MimeTypes.MaxHeaderSize); + + int bytesRead = fileStream.Read(header, 0, MimeTypes.MaxHeaderSize); + + return new ReadResult(header, fileStream, bytesRead, isArrayRented: true, shouldDisposeStream: true, shouldResetStreamPosition: false); + } + + public static async Task ReadFileHeaderAsync(FileInfo file) + { + if (file is null) + ThrowHelpers.FileInfoCannotBeNull(); + + if (!file.Exists) + ThrowHelpers.FileDoesNotExist(file); + + FileStream fileStream = file.OpenRead(); + + byte[] header = ArrayPool.Shared.Rent(MimeTypes.MaxHeaderSize); + + int bytesRead = await fileStream.ReadAsync(header, 0, MimeTypes.MaxHeaderSize); + + return new ReadResult(header, fileStream, bytesRead, isArrayRented: true, shouldDisposeStream: true, shouldResetStreamPosition: false); + } + + /// + /// Takes a stream does, not dispose of stream, resets read position to beginning though + /// + /// + /// + /// + public static ReadResult ReadHeaderFromStream(Stream stream, bool shouldDisposeStream = false, bool shouldResetStreamPosition = true) + { + if (stream is null) + ThrowHelpers.StreamCannotBeNull(); + + if (!stream.CanRead) + ThrowHelpers.CannotReadFromStream(); + + if (stream.CanSeek && stream.Position > 0) + stream.Seek(0, SeekOrigin.Begin); + + byte[] header = ArrayPool.Shared.Rent(MimeTypes.MaxHeaderSize); + + int bytesRead = stream.Read(header, 0, MimeTypes.MaxHeaderSize); + + return new ReadResult(header, stream, bytesRead, isArrayRented: true, shouldDisposeStream, shouldResetStreamPosition); + } + + //TODO Figure out how to handle non-seekable Streams + /// + /// Takes a stream does, not dispose of stream, resets read position to beginning though + /// + /// + /// + /// + public static async Task ReadHeaderFromStreamAsync(Stream stream, bool shouldDisposeStream = false, bool shouldResetStreamPosition = true) + { + if (stream is null) + ThrowHelpers.StreamCannotBeNull(); + + if (!stream.CanRead) + ThrowHelpers.CannotReadFromStream(); + + if (stream.CanSeek && stream.Position > 0) + stream.Seek(0, SeekOrigin.Begin); + + byte[] header = ArrayPool.Shared.Rent(MimeTypes.MaxHeaderSize); + + int bytesRead = await stream.ReadAsync(header, 0, MimeTypes.MaxHeaderSize); + + return new ReadResult(header, stream, bytesRead, isArrayRented: true, shouldDisposeStream, shouldResetStreamPosition); + } + + public void Dispose() + { + bool sourceIsNotNull = (object)Source != null; + + if (sourceIsNotNull && ShouldResetStreamPosition && Source.CanSeek) + Source.Seek(0, SeekOrigin.Begin); + + if (sourceIsNotNull && ShouldDisposeStream) + Source.Dispose(); + + if (IsArrayRented) + ArrayPool.Shared.Return(Array); + } + } +} \ No newline at end of file diff --git a/src/Mime-Detective/Helpers/ThrowHelpers.cs b/src/Mime-Detective/Helpers/ThrowHelpers.cs new file mode 100644 index 0000000..8f48aff --- /dev/null +++ b/src/Mime-Detective/Helpers/ThrowHelpers.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; + +namespace MimeDetective +{ + internal static class ThrowHelpers + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void CannotReadFromStream() + { + throw new IOException("Could not read from Stream"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void StreamCannotBeNull() + { + throw new ArgumentNullException("Stream cannot be null"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ByteArrayCannotBeNull() + { + throw new ArgumentNullException("Byte Array cannot be null"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ReadLengthCannotBeOutOfBounds() + { + throw new ArgumentOutOfRangeException("Read Length cannot be out of bound of Array"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void FileInfoCannotBeNull() + { + throw new ArgumentNullException("File Info cannot be null"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void FileDoesNotExist(FileInfo fileInfo) + { + throw new FileNotFoundException($"File: {fileInfo.Name} does not exist in Directory: {fileInfo.Directory}"); + } + } +} diff --git a/src/Mime-Detective/InputHelpers.cs b/src/Mime-Detective/InputHelpers.cs deleted file mode 100644 index e7364d1..0000000 --- a/src/Mime-Detective/InputHelpers.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace MimeDetective -{ - internal static class InputHelpers - { - internal struct ReadResult - { - public readonly byte[] Array; - public Stream Source; - public readonly int ReadLength; - public readonly bool IsArrayRented; - public bool ShouldDisposeStream; - - /// - /// Non rented array input, Array is Input - /// - /// - /// - public ReadResult(byte[] array, int readLength) - { - this.Array = array; - this.Source = null; - this.ReadLength = readLength; - this.IsArrayRented = false; - this.ShouldDisposeStream = true; - } - - public ReadResult(byte[] array, Stream source, int readLength, bool isArrayRented, bool shouldDisposeStream = true) - { - this.Array = array; - this.Source = source; - this.ReadLength = readLength; - this.IsArrayRented = isArrayRented; - this.ShouldDisposeStream = shouldDisposeStream; - } - - public void CreateMemoryStreamIfSourceIsNull() - { - if (Source is null) - { - Source = new MemoryStream(Array, 0, (int)ReadLength); - ShouldDisposeStream = true; - } - } - } - - /// - /// Reads the file header - first (16) bytes from the file - /// - /// The file to work with - /// Array of bytes - internal static ReadResult ReadFileHeader(FileStream fileStream, ushort maxHeaderSize) - { - byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); - - int bytesRead = fileStream.Read(header, 0, maxHeaderSize); - - return new ReadResult(header, fileStream, bytesRead, isArrayRented: true, shouldDisposeStream: true); - } - - internal static async Task ReadFileHeaderAsync(FileStream fileStream, ushort maxHeaderSize) - { - byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); - - int bytesRead = await fileStream.ReadAsync(header, 0, maxHeaderSize); - - return new ReadResult(header, fileStream, bytesRead, isArrayRented: true, shouldDisposeStream: true); - } - - /// - /// Takes a stream does, not dispose of stream, resets read position to beginning though - /// - /// - /// - /// - //TODO streamread result - internal static ReadResult ReadHeaderFromStream(Stream stream, ushort MaxHeaderSize, bool shouldDisposeStream) - { - if (!stream.CanRead) - throw new IOException("Could not read from Stream"); - - if (stream.Position > 0) - stream.Seek(0, SeekOrigin.Begin); - - byte[] header = ArrayPool.Shared.Rent(MaxHeaderSize); - - int bytesRead = stream.Read(header, 0, MaxHeaderSize); - - return new ReadResult(header, stream, bytesRead, isArrayRented: true, shouldDisposeStream); - } - - /// - /// Takes a stream does, not dispose of stream, resets read position to beginning though - /// - /// - /// - /// - //TODO throw helper [MethodImpl(MethodImplOptions.NoInlining)] - internal static async Task ReadHeaderFromStreamAsync(Stream stream, ushort MaxHeaderSize, bool shouldDisposeStream) - { - if (!stream.CanRead) - throw new IOException("Could not read from Stream"); - - if (stream.Position > 0) - stream.Seek(0, SeekOrigin.Begin); - - byte[] header = ArrayPool.Shared.Rent(MaxHeaderSize); - - int bytesRead = await stream.ReadAsync(header, 0, MaxHeaderSize); - - return new ReadResult(header, stream, bytesRead, isArrayRented: true, shouldDisposeStream); - } - } -} diff --git a/src/Mime-Detective/Mime-Detective.csproj b/src/Mime-Detective/Mime-Detective.csproj index ae2781a..994f1b1 100644 --- a/src/Mime-Detective/Mime-Detective.csproj +++ b/src/Mime-Detective/Mime-Detective.csproj @@ -18,8 +18,8 @@ 0.0.6.0 0.0.6.0 - fixed handling of small files - 0.0.6.0-alpha2 + See beta1 PR + 0.0.6.0-beta1 true @@ -28,6 +28,8 @@ + + @@ -36,10 +38,12 @@ + + - + diff --git a/src/Mime-Detective/MimeTypes.cs b/src/Mime-Detective/MimeTypes.cs index e1a60e9..00af6a7 100644 --- a/src/Mime-Detective/MimeTypes.cs +++ b/src/Mime-Detective/MimeTypes.cs @@ -3,9 +3,6 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using static MimeDetective.InputHelpers; namespace MimeDetective { @@ -18,11 +15,17 @@ namespace MimeDetective /// public static class MimeTypes { - // all the file types to be put into one list + // number of bytes we read from a file + // some file formats have headers offset to 512 bytes + public const ushort MaxHeaderSize = 560; #region Constants #region office, excel, ppt and documents, xml, pdf, rtf, msdoc + + /// + /// This is for usage when a file type definition requires content inspection instead of reading headers + /// public readonly static byte?[] EmptyHeader = new byte?[0]; // office and documents @@ -35,11 +38,17 @@ public static class MimeTypes //ms office and openoffice docs (they're zip files: rename and enjoy!) //don't add them to the list, as they will be 'subtypes' of the ZIP type + //Open Xml Document formats public readonly static FileType WORDX = new FileType(EmptyHeader, "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 512); public readonly static FileType PPTX = new FileType(EmptyHeader, "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation", 512); public readonly static FileType EXCELX = new FileType(EmptyHeader, "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 512); + + //Open Document formats public readonly static FileType ODT = new FileType(EmptyHeader, "odt", "application/vnd.oasis.opendocument.text", 512); public readonly static FileType ODS = new FileType(EmptyHeader, "ods", "application/vnd.oasis.opendocument.spreadsheet", 512); + public readonly static FileType ODP = new FileType(EmptyHeader, "odp", "application/vnd.oasis.opendocument.presentation", 512); + public readonly static FileType ODG = new FileType(EmptyHeader, "odg", "application/vnd.oasis.opendocument.graphics", 512); + // common documents public readonly static FileType RTF = new FileType(new byte?[] { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 }, "rtf", "application/rtf"); @@ -47,11 +56,10 @@ public static class MimeTypes public readonly static FileType PDF = new FileType(new byte?[] { 0x25, 0x50, 0x44, 0x46 }, "pdf", "application/pdf"); //todo place holder extension - public readonly static FileType MSDOC = new FileType(new byte?[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }, "msdoc", "application/octet-stream"); + public readonly static FileType MS_OFFICE = new FileType(new byte?[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }, "doc,ppt,xls", "application/octet-stream"); //application/xml text/xml - public readonly static FileType XML = new FileType(new byte?[] { 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x3F, 0x3E }, - "xml,xul", "text/xml"); + public readonly static FileType XML = new FileType(new byte?[] { 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x3D, 0x22, 0x31, 0x2E, 0x30, 0x22, 0x3F, 0x3E }, "xml,xul", "text/xml"); //text files public readonly static FileType TXT = new FileType(EmptyHeader, "txt", "text/plain"); @@ -72,6 +80,8 @@ public static class MimeTypes public readonly static FileType PNG = new FileType(new byte?[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, "png", "image/png"); public readonly static FileType GIF = new FileType(new byte?[] { 0x47, 0x49, 0x46, 0x38, null, 0x61 }, "gif", "image/gif"); public readonly static FileType BMP = new FileType(new byte?[] { 0x42, 0x4D }, "bmp", "image/bmp"); // or image/x-windows-bmp + + //TODO review this public readonly static FileType ICO = new FileType(new byte?[] { 0, 0, 1, 0 }, "ico", "image/x-icon"); //tiff @@ -86,8 +96,12 @@ public static class MimeTypes #region Video - //todo review these - //mp4 iso base file format, value: ....ftypisom + /// + /// Base Magic Word for all complex MPEG4 container formats + /// ex. ....ftypisom (header starting after ....) + /// + public readonly static FileType MP4Container = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70 }, "mp4,m4v,m4a,mp4a,mov", "video/mp4", 4); + public readonly static FileType Mp4ISOv1 = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D }, "mp4", "video/mp4", 4); public readonly static FileType Mp4QuickTime = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32 }, "m4v", "video/x-m4v", 4); @@ -98,18 +112,22 @@ public static class MimeTypes public readonly static FileType Mp4VideoFile = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70, 0x4D, 0x53, 0x4E, 0x56 }, "mp4", "video/mp4", 4); - public readonly static FileType Mp4A = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20 }, "mp4a", "audio/mp4", 4); + public readonly static FileType Mp4A = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20 }, "mp4a,m4a", "audio/mp4", 4); //FLV Flash video file public readonly static FileType FLV = new FileType(new byte?[] { 0x46, 0x4C, 0x56, 0x01 }, "flv", "application/unknown"); - public readonly static FileType ThridGPP2File = new FileType(new byte?[] { 0, 0, 0, 0x20, 0x66, 0x74, 0x79, 0x70, 0x33, 0x67, 0x70 }, "3gp", "video/3gg"); + public readonly static FileType ThreeGPP2File = new FileType(new byte?[] { 0x66, 0x74, 0x79, 0x70, 0x33, 0x67, 0x70 }, "3gp", "video/3gg", 4); #endregion Video #region Audio - public readonly static FileType Mp3 = new FileType(new byte?[] { 0x49, 0x44, 0x33 }, "mp3", "audio/mpeg"); + /// + /// MP3 file with ID3 Meta-data + /// + public readonly static FileType Mp3ID3 = new FileType(new byte?[] { 0x49, 0x44, 0x33 }, "mp3", "audio/mpeg"); + //todo this needs analyzer support public readonly static FileType Mp3SyncFrame = new FileType(new byte?[] { 0xFF, 0xF1 }, "mp3", "audio/mpeg"); //WAV Resource Interchange File Format -- Audio for Windows file, where xx xx xx xx is the file size (little endian), audio/wav audio/x-wav @@ -119,20 +137,23 @@ public static class MimeTypes //MID, MIDI Musical Instrument Digital Interface (MIDI) sound file public readonly static FileType MIDI = new FileType(new byte?[] { 0x4D, 0x54, 0x68, 0x64 }, "midi,mid", "audio/midi"); - public readonly static FileType Flac = new FileType(new byte?[] { 0x66, 0x4C, 0x61, 0x43, 0, 0, 0, 0x22 }, "flac", "audio/x-flac"); + /// + /// File type on Kessler's site is wrong it should be "fLaC" only + /// + public readonly static FileType Flac = new FileType(new byte?[] { 0x66, 0x4C, 0x61, 0x43 }, "flac", "audio/x-flac"); #endregion Audio #region Zip, 7zip, rar, dll_exe, tar, bz2, gz_tgz - public readonly static FileType GZ_TGZ = new FileType(new byte?[] { 0x1F, 0x8B, 0x08 }, "gz, tgz", "application/x-gz"); + public readonly static FileType GZ_TGZ = new FileType(new byte?[] { 0x1F, 0x8B, 0x08 }, "gz,tgz", "application/x-gz"); - public readonly static FileType ZIP_7z = new FileType(new byte?[] { 66, 77 }, "7z", "application/x-compressed"); - public readonly static FileType ZIP_7z_2 = new FileType(new byte?[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C }, "7z", "application/x-compressed"); + //public readonly static FileType ZIP_7z = new FileType(new byte?[] { 66, 77 }, "7z", "application/x-compressed"); + public readonly static FileType ZIP_7z = new FileType(new byte?[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C }, "7z", "application/x-compressed"); public readonly static FileType ZIP = new FileType(new byte?[] { 0x50, 0x4B, 0x03, 0x04 }, "zip", "application/x-compressed"); public readonly static FileType RAR = new FileType(new byte?[] { 0x52, 0x61, 0x72, 0x21 }, "rar", "application/x-compressed"); - public readonly static FileType DLL_EXE = new FileType(new byte?[] { 0x4D, 0x5A }, "dll, exe", "application/octet-stream"); + public readonly static FileType DLL_EXE = new FileType(new byte?[] { 0x4D, 0x5A }, "dll,exe", "application/octet-stream"); //Compressed tape archive file using standard (Lempel-Ziv-Welch) compression public readonly static FileType TAR_ZV = new FileType(new byte?[] { 0x1F, 0x9D }, "tar.z", "application/x-tar"); @@ -148,7 +169,7 @@ public static class MimeTypes #region Media ogg, dwg, pst, psd // media - public readonly static FileType OGG = new FileType(new byte?[] { 103, 103, 83, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0 }, "oga,ogg,ogv,ogx", "application/ogg"); + public readonly static FileType OGG = new FileType(new byte?[] { 0x4F, 0x67, 0x67, 0x53 }, "oga,ogg,ogv,ogx", "application/ogg"); public readonly static FileType PST = new FileType(new byte?[] { 0x21, 0x42, 0x44, 0x4E }, "pst", "application/octet-stream"); @@ -179,40 +200,41 @@ public static class MimeTypes #endregion Crypto aes, skr, skr_2, pkr /* - * 46 72 6F 6D 20 20 20 or From - 46 72 6F 6D 20 3F 3F 3F or From ??? - 46 72 6F 6D 3A 20 From: - EML A commmon file extension for e-mail files. Signatures shown here - are for Netscape, Eudora, and a generic signature, respectively. - EML is also used by Outlook Express and QuickMail. - */ + * 46 72 6F 6D 20 20 20 or From + 46 72 6F 6D 20 3F 3F 3F or From ??? + 46 72 6F 6D 3A 20 From: + EML A commmon file extension for e-mail files. Signatures shown here + are for Netscape, Eudora, and a generic signature, respectively. + EML is also used by Outlook Express and QuickMail. + */ public readonly static FileType EML_FROM = new FileType(new byte?[] { 0x46, 0x72, 0x6F, 0x6D }, "eml", "message/rfc822"); //EVTX Windows Vista event log file public readonly static FileType ELF = new FileType(new byte?[] { 0x45, 0x6C, 0x66, 0x46, 0x69, 0x6C, 0x65, 0x00 }, "elf", "text/plain"); - // number of bytes we read from a file - public const ushort MaxHeaderSize = 560; // some file formats have headers offset to 512 bytes - - public static readonly FileType[] Types = new FileType[] { PDF, WORD, EXCEL, JPEG, ZIP, RAR, RTF, PNG, PPT, GIF, DLL_EXE, MSDOC, - BMP, DLL_EXE, ZIP_7z, ZIP_7z_2, GZ_TGZ, TAR_ZH, TAR_ZV, OGG, ICO, XML, DWG, LIB_COFF, PST, PSD, BZ2, + public static readonly FileType[] Types = new FileType[] { PDF, JPEG, ZIP, RAR, RTF, PNG, GIF, DLL_EXE, MS_OFFICE, + BMP, DLL_EXE, ZIP_7z, GZ_TGZ, TAR_ZH, TAR_ZV, OGG, ICO, XML, DWG, LIB_COFF, PST, PSD, BZ2, AES, SKR, SKR_2, PKR, EML_FROM, ELF, TXT_UTF8, TXT_UTF16_BE, TXT_UTF16_LE, TXT_UTF32_BE, TXT_UTF32_LE, - Mp3, Wav, Flac, MIDI, + Mp3ID3, Wav, Flac, MIDI, Tiff, TiffLittleEndian, TiffBigEndian, TiffBig, - Mp4ISOv1, MovQuickTime, MP4VideoFiles, Mp4QuickTime, Mp4VideoFile, ThridGPP2File, Mp4A, FLV }; + MP4Container, Mp4ISOv1, MovQuickTime, MP4VideoFiles, Mp4QuickTime, Mp4VideoFile, ThreeGPP2File, Mp4A, FLV }; //public static readonly FileType[] sortedTypes = Types.OrderBy(x => x.Header.Length).ToArray(); - public static readonly FileType[] XmlTypes = new FileType[] { WORDX, EXCELX, PPTX, ODS, ODT }; + + /// + /// OpenDocument And OpenXML Document types + /// + public static readonly FileType[] XmlTypes = new FileType[] { WORDX, EXCELX, PPTX, ODS, ODT, ODG, ODP }; #endregion Constants - public static void SaveToXmlFile(string path) + public static void SaveToXmlFile(string path, IEnumerable types) { using (FileStream file = File.OpenWrite(path)) { - var serializer = new System.Xml.Serialization.XmlSerializer(Types.GetType()); - serializer.Serialize(file, Types); + var serializer = new System.Xml.Serialization.XmlSerializer(typeof(IEnumerable)); + serializer.Serialize(file, types); } } @@ -220,171 +242,28 @@ public static FileType[] LoadFromXmlFile(string path) { using (FileStream file = File.OpenRead(path)) { - var serializer = new System.Xml.Serialization.XmlSerializer(Types.GetType()); + var serializer = new System.Xml.Serialization.XmlSerializer(typeof(FileType[])); return (FileType[])serializer.Deserialize(file); } } - //todo break apart and split zip file handling to an IAnalyzer interface stuff design here - internal static FileType GetFileType(in ReadResult readResult) - { - if (readResult.ReadLength == 0) - return null; - - try - { - bool doesNotHaveValues = true; - - // checking if it's binary (not really exact, but should do the job) - // shouldn't work with UTF-16 OR UTF-32 files - for (int i = 0; i < readResult.ReadLength; i++) - { - if (readResult.Array[i] != 0) - { - doesNotHaveValues = false; - break; - } - } - - if (doesNotHaveValues) - return null; - - uint highestMatchingCount = 0; - FileType highestMatchingType = null; - - // compare the file header to the stored file headers - foreach (FileType type in Types) - { - uint matchingCount = GetFileMatchingCount(in readResult, type); - - if (type.Header.Length == matchingCount) - { - highestMatchingType = type; - break; - } - else if (matchingCount > highestMatchingCount) - { - highestMatchingCount = matchingCount; - highestMatchingType = type; - } - } - - if (ZIP.Equals(highestMatchingType)) - return FindZipType(readResult); - - return highestMatchingType; - } - finally - { - if (readResult.Source != null && readResult.ShouldDisposeStream) - readResult.Source.Dispose(); - - //this might be the perf issue - if (readResult.IsArrayRented) - ArrayPool.Shared.Return(readResult.Array); - } - } - - private static FileType FindZipType(ReadResult readResult) - { - //TODO this still needs disposed somehow - readResult.CreateMemoryStreamIfSourceIsNull(); - - if (readResult.Source.Position > 0) - readResult.Source.Seek(0, SeekOrigin.Begin); - - using (ZipArchive zipData = new ZipArchive(readResult.Source, ZipArchiveMode.Read, leaveOpen: true)) - { - //check for office xml formats - var officeXml = CheckForDocxAndXlsxStream(zipData); - - if (officeXml != null) - return officeXml; - - //check for open office formats - var openOffice = CheckForOdtAndOds(zipData); - - if (openOffice != null) - return openOffice; - } - - return ZIP; - } - /// /// Gets the list of FileTypes based on list of extensions in Comma-Separated-Values string /// /// The CSV String with extensions /// List of FileTypes - public static List GetFileTypesByExtensions(string CSV) + public static List GetFileTypesByExtensions(string csv) { List result = new List(); foreach (FileType type in Types) { - if (CSV.IndexOf(type.Extension, 0, StringComparison.OrdinalIgnoreCase) > 0) + if (csv.IndexOf(type.Extension, 0, StringComparison.OrdinalIgnoreCase) > 0) result.Add(type); } - return result; - } - - private static FileType CheckForDocxAndXlsxStream(ZipArchive zipData) - { - foreach (var entry in zipData.Entries) - { - if (entry.FullName.StartsWith("word/")) - return WORDX; - else if (entry.FullName.StartsWith("xl/")) - return EXCELX; - else if (entry.FullName.StartsWith("ppt/")) - return PPTX; - } - - return null; - } - - //check for open doc formats - private static FileType CheckForOdtAndOds(ZipArchive zipFile) - { - ZipArchiveEntry ooMimeType = null; - - foreach (var entry in zipFile.Entries) - { - if (entry.FullName == "mimetype") - { - ooMimeType = entry; - break; - } - } - - if (ooMimeType is null) - return null; - - using (var textReader = new StreamReader(ooMimeType.Open())) - { - var mimeType = textReader.ReadToEnd(); - - if (mimeType == ODT.Mime) - return ODT; - else if (mimeType == ODS.Mime) - return ODS; - else - return null; - } - } - - private static uint GetFileMatchingCount(in ReadResult readResult, FileType type) - { - uint matchingCount = 0; - - for (int i = 0, iOffset = type.HeaderOffset; i < type.Header.Length && i < readResult.ReadLength && iOffset < readResult.ReadLength; i++, iOffset++) - { - if (type.Header[i] is null || type.Header[i] == readResult.Array[iOffset]) - matchingCount++; - } - return matchingCount; - } + return result; + } } } \ No newline at end of file diff --git a/test/Mime-Detective.Benchmarks/Data/Assemblies/ManagedDLL.dll b/test/Mime-Detective.Benchmarks/Data/Assemblies/ManagedDLL.dll new file mode 100644 index 0000000..6a0d62d Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Assemblies/ManagedDLL.dll differ diff --git a/test/Mime-Detective.Benchmarks/Data/Assemblies/ManagedExe.exe b/test/Mime-Detective.Benchmarks/Data/Assemblies/ManagedExe.exe new file mode 100644 index 0000000..494dd2d Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Assemblies/ManagedExe.exe differ diff --git a/test/Mime-Detective.Benchmarks/Data/Assemblies/MixedDLL.dll b/test/Mime-Detective.Benchmarks/Data/Assemblies/MixedDLL.dll new file mode 100644 index 0000000..83c688e Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Assemblies/MixedDLL.dll differ diff --git a/test/Mime-Detective.Benchmarks/Data/Assemblies/MixedExe.exe b/test/Mime-Detective.Benchmarks/Data/Assemblies/MixedExe.exe new file mode 100644 index 0000000..7216abd Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Assemblies/MixedExe.exe differ diff --git a/test/Mime-Detective.Benchmarks/Data/Assemblies/NativeDLL.dll b/test/Mime-Detective.Benchmarks/Data/Assemblies/NativeDLL.dll new file mode 100644 index 0000000..b006d25 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Assemblies/NativeDLL.dll differ diff --git a/test/Mime-Detective.Benchmarks/Data/Assemblies/NativeExe.exe b/test/Mime-Detective.Benchmarks/Data/Assemblies/NativeExe.exe new file mode 100644 index 0000000..0c75315 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Assemblies/NativeExe.exe differ diff --git a/test/Mime-Detective.Benchmarks/Data/Audio/wavVLC.wav b/test/Mime-Detective.Benchmarks/Data/Audio/wavVLC.wav new file mode 100644 index 0000000..e5671d4 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Audio/wavVLC.wav differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/test.doc b/test/Mime-Detective.Benchmarks/Data/Documents/DocWord2016.doc similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/Documents/test.doc rename to test/Mime-Detective.Benchmarks/Data/Documents/DocWord2016.doc diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/DocWord97.doc b/test/Mime-Detective.Benchmarks/Data/Documents/DocWord97.doc new file mode 100644 index 0000000..9cc2e72 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/DocWord97.doc differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/test.docx b/test/Mime-Detective.Benchmarks/Data/Documents/DocxWord2016.docx similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/Documents/test.docx rename to test/Mime-Detective.Benchmarks/Data/Documents/DocxWord2016.docx diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/GithubTestPdf2.pdf b/test/Mime-Detective.Benchmarks/Data/Documents/GithubTestPdf2.pdf new file mode 100644 index 0000000..f46dbe5 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/GithubTestPdf2.pdf differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/OpenDocWord2016.odt b/test/Mime-Detective.Benchmarks/Data/Documents/OpenDocWord2016.odt new file mode 100644 index 0000000..f62c958 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/OpenDocWord2016.odt differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/PdfWord2016.pdf b/test/Mime-Detective.Benchmarks/Data/Documents/PdfWord2016.pdf new file mode 100644 index 0000000..03c98d8 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/PdfWord2016.pdf differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/test.ppt b/test/Mime-Detective.Benchmarks/Data/Documents/PptPowerpoint2016.ppt similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/Documents/test.ppt rename to test/Mime-Detective.Benchmarks/Data/Documents/PptPowerpoint2016.ppt diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/test.pptx b/test/Mime-Detective.Benchmarks/Data/Documents/PptxPowerpoint2016.pptx similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/Documents/test.pptx rename to test/Mime-Detective.Benchmarks/Data/Documents/PptxPowerpoint2016.pptx diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/RichTextWord2016.rtf b/test/Mime-Detective.Benchmarks/Data/Documents/RichTextWord2016.rtf new file mode 100644 index 0000000..9e11f0b --- /dev/null +++ b/test/Mime-Detective.Benchmarks/Data/Documents/RichTextWord2016.rtf @@ -0,0 +1,208 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;} +{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} +{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} +{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f43\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f44\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\f46\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f47\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f48\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f49\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\f50\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f51\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f413\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f414\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} +{\f416\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f417\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f418\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f419\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);} +{\f420\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f421\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;} +{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);} +{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} +{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} +{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} +{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; +\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\*\defchp \f31506\fs22 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 +\ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}} +{\*\rsidtbl \rsid1253431\rsid2841649\rsid5136090\rsid6582152\rsid9838298\rsid11690180\rsid11875463\rsid13904887\rsid15419559\rsid16282309}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0 +\mnaryLim1}{\info{\author Joshua Clark}{\operator Joshua Clark}{\creatim\yr2017\mo12\dy29\hr21\min20}{\revtim\yr2017\mo12\dy29\hr21\min20}{\version2}{\edmins1}{\nofpages1}{\nofwords1}{\nofchars11}{\nofcharsws11}{\vern47}}{\*\xmlnstbl {\xmlns1 http://schem +as.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen +\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot11875463\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang +{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 +\af31507 \ltrch\fcs0 \insrsid11875463 Hello World}{\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid10497837 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b6f4679893070000c9200000160000007468656d652f7468656d652f +7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f2a24fcfda33b6b164873dd648a5eef2547789aad28cc56208de532e81c026e49085bd +ed21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c99e191c3061463074977eefd5afde7bf5de53d5ddcf5e26d4bbc05c1096f6fcfa9d9aefe174ce16248d +7afeb3d9a4d2f13d2151ba4094a5b8e76fb0f03fbbf7eb5fdd454732c609f6403e1547a8e7c752ae8eaa5531876124eeb0154ee1bb25e30992f0caa3ea82a34b +d09bd06aa3566b55134452df4b51026a1f2f97648ebd9952e9dfdb2a1f53784da5500373caa74a35b6243476715e5708b11143cabd0b447b3eccb3609733fc52 +fa1e4542c2173dbfa6fffceabdbb5574940b517940d6909be8bf5c2e17589c37f49c3c3a2b260d823068f50bfd1a40e53e6edc1eb7c6ad429f06a0f91c569a71 +b175b61bc320c71aa0ecd1a17bd41e35eb16ded0dfdce3dc0fd5c7c26b50a63fd8c34f2643b0a285d7a00c1feee1c3417730b2f56b50866fede1dbb5fe28685b +fa3528a6243ddf43d7c25673b85d6d0159327aec8477c360d26ee4ca4b144443115d6a8a254be5a1584bd00bc6270050408a24493db959e1259a43140f112567 +9c7827248a21f056286502866b8ddaa4d684ffea13e827ed5174849121ad780113b137a4f87862cec94af6fc07a0d537206f7ffef9cdeb1fdfbcfee9cd575fbd +79fdf77c6eadca923b466964cafdf2dd1ffef3cd6fbd7ffff0ed2f5fff319b7a172f4cfcbbbffdeedd3ffef93ef5b0e2d2146ffff4fdbb1fbf7ffbe7dfffebaf +5f3bb4f7393a33e1339260e13dc297de5396c0021dfcf119bf9ec42c46c494e8a791402952b338f48f656ca11f6d10450edc00db767cce21d5b880f7d72f2cc2 +d398af2571687c182716f094313a60dc6985876a2ec3ccb3751ab927e76b13f714a10bd7dc43945a5e1eaf579063894be530c616cd2714a5124538c5d253dfb1 +738c1dabfb8210cbaea764ce99604be97d41bc01224e93ccc899154da5d03149c02f1b1741f0b7659bd3e7de8051d7aa47f8c246c2de40d4417e86a965c6fb68 +2d51e252394309350d7e8264ec2239ddf0b9891b0b099e8e3065de78818570c93ce6b05ec3e90f21cdb8dd7e4a37898de4929cbb749e20c64ce4889d0f6394ac +5cd829496313fbb938871045de13265df05366ef10f50e7e40e941773f27d872f787b3c133c8b026a53240d4376beef0e57dccacf89d6ee8126157aae9f3c44a +b17d4e9cd131584756689f604cd1255a60ec3dfbdcc160c05696cd4bd20f62c82ac7d815580f901dabea3dc5027a25d5dcece7c91322ac909de2881de073bad9 +493c1b9426881fd2fc08bc6eda7c0ca52e7105c0633a3f37818f08f480102f4ea33c16a0c308ee835a9fc4c82a60ea5db8e375c32dff5d658fc1be7c61d1b8c2 +be04197c6d1948eca6cc7b6d3343d49aa00c9819822ec3956e41c4727f29a28aab165b3be596f6a62ddd00dd91d5f42424fd6007b4d3fb84ffbbde073a8cb77f +f9c6b10f3e4ebfe3566c25ab6b763a8792c9f14e7f7308b7dbd50c195f904fbfa919a175fa04431dd9cf58b73dcd6d4fe3ffdff73487f6f36d2773a8dfb8ed64 +7ce8306e3b99fc70e5e3743265f3027d8d3af0c80e7af4b14f72f0d46749289dca0dc527421ffc08f83db398c0a092d3279eb838055cc5f0a8ca1c4c60e1228e +b48cc799fc0d91f134462b381daafb4a492472d591f0564cc0a1911e76ea5678ba4e4ed9223becacd7d5c16656590592e5782d2cc6e1a04a66e856bb3cc02bd4 +6bb6913e68dd1250b2d721614c6693683a48b4b783ca48fa58178ce620a157f65158741d2c3a4afdd6557b2c805ae115f8c1edc1cff49e1f06200242701e07cd +f942f92973f5d6bbda991fd3d3878c69450034d8db08283ddd555c0f2e4fad2e0bb52b78da2261849b4d425b46377822869fc17974aad1abd0b8aeafbba54b2d +7aca147a3e08ad9246bbf33e1637f535c8ede6069a9a9982a6de65cf6f35430899395af5fc251c1ac363b282d811ea3717a211dcbccc25cf36fc4d32cb8a0b39 +4222ce0cae934e960d122231f728497abe5a7ee1069aea1ca2b9d51b90103e59725d482b9f1a3970baed64bc5ce2b934dd6e8c284b67af90e1b35ce1fc568bdf +1cac24d91adc3d8d1797de195df3a708422c6cd795011744c0dd413db3e682c0655891c8caf8db294c79da356fa3740c65e388ae62945714339967709dca0b3a +faadb081f196af190c6a98242f8467912ab0a651ad6a5a548d8cc3c1aafb6121653923699635d3ca2aaa6abab39835c3b60cecd8f26645de60b53531e434b3c2 +67a97b37e576b7b96ea74f28aa0418bcb09fa3ea5ea12018d4cac92c6a8af17e1a56393b1fb56bc776811fa07695226164fdd656ed8edd8a1ae19c0e066f54f9 +416e376a6168b9ed2bb5a5f5adb979b1cdce5e40f2184197bba6526857c2c92e47d0104d754f92a50dd8222f65be35e0c95b73d2f3bfac85fd60d80887955a27 +1c57826650ab74c27eb3d20fc3667d1cd66ba341e31514161927f530bbb19fc00506dde4f7f67a7cefee3ed9ded1dc99b3a4caf4dd7c5513d777f7f5c6e1bb7b +8f40d2f9b2d598749bdd41abd26df627956034e854bac3d6a0326a0ddba3c9681876ba9357be77a1c141bf390c5ae34ea5551f0e2b41aba6e877ba9576d068f4 +8376bf330efaaff23606569ea58fdc16605ecdebde7f010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d65 +2f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d36 +3f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e +3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d985 +0528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000000000 +0000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000 +000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019020000 +7468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b6f4679893070000c92000001600000000000000 +000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000 +000000000000000000009d0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000980b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text; +\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; +\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; +\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; +\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; +\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; +\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; +\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; +\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; +\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; +\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; +\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; +\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; +\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; +\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; +\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e500000000000000000000000070df +a0b31481d301feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/StrictOpenXMLWord2016.docx b/test/Mime-Detective.Benchmarks/Data/Documents/StrictOpenXMLWord2016.docx new file mode 100644 index 0000000..9846a71 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/StrictOpenXMLWord2016.docx differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/XlsExcel2007.xls b/test/Mime-Detective.Benchmarks/Data/Documents/XlsExcel2007.xls new file mode 100644 index 0000000..c53e528 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/XlsExcel2007.xls differ diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/test.xls b/test/Mime-Detective.Benchmarks/Data/Documents/XlsExcel2016.xls similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/Documents/test.xls rename to test/Mime-Detective.Benchmarks/Data/Documents/XlsExcel2016.xls diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/test.xlsx b/test/Mime-Detective.Benchmarks/Data/Documents/XlsxExcel2016.xlsx similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/Documents/test.xlsx rename to test/Mime-Detective.Benchmarks/Data/Documents/XlsxExcel2016.xlsx diff --git a/test/Mime-Detective.Benchmarks/Data/Documents/microsoftPrintToPdf.pdf b/test/Mime-Detective.Benchmarks/Data/Documents/microsoftPrintToPdf.pdf new file mode 100644 index 0000000..14860c6 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Documents/microsoftPrintToPdf.pdf differ diff --git a/test/Mime-Detective.Benchmarks/Data/empty.jpg b/test/Mime-Detective.Benchmarks/Data/Images/empty.jpg similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/empty.jpg rename to test/Mime-Detective.Benchmarks/Data/Images/empty.jpg diff --git a/test/Mime-Detective.Benchmarks/Data/empty.zip b/test/Mime-Detective.Benchmarks/Data/Zip/Empty.zip similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/empty.zip rename to test/Mime-Detective.Benchmarks/Data/Zip/Empty.zip diff --git a/test/Mime-Detective.Benchmarks/Data/Zip/Images.7z b/test/Mime-Detective.Benchmarks/Data/Zip/Images.7z new file mode 100644 index 0000000..6b3f681 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Zip/Images.7z differ diff --git a/test/Mime-Detective.Benchmarks/Data/images.zip b/test/Mime-Detective.Benchmarks/Data/Zip/Images.zip similarity index 100% rename from test/Mime-Detective.Benchmarks/Data/images.zip rename to test/Mime-Detective.Benchmarks/Data/Zip/Images.zip diff --git a/test/Mime-Detective.Benchmarks/Data/Zip/Images7zip.tar b/test/Mime-Detective.Benchmarks/Data/Zip/Images7zip.tar new file mode 100644 index 0000000..f57fa81 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Zip/Images7zip.tar differ diff --git a/test/Mime-Detective.Benchmarks/Data/Zip/ImagesBy7zip.zip b/test/Mime-Detective.Benchmarks/Data/Zip/ImagesBy7zip.zip new file mode 100644 index 0000000..ea598d3 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Zip/ImagesBy7zip.zip differ diff --git a/test/Mime-Detective.Benchmarks/Data/Zip/TestBlender.rar b/test/Mime-Detective.Benchmarks/Data/Zip/TestBlender.rar new file mode 100644 index 0000000..91152b8 Binary files /dev/null and b/test/Mime-Detective.Benchmarks/Data/Zip/TestBlender.rar differ diff --git a/test/Mime-Detective.Benchmarks/Mime-Detective.Benchmarks.csproj b/test/Mime-Detective.Benchmarks/Mime-Detective.Benchmarks.csproj index 314fa1d..c0e8bd3 100644 --- a/test/Mime-Detective.Benchmarks/Mime-Detective.Benchmarks.csproj +++ b/test/Mime-Detective.Benchmarks/Mime-Detective.Benchmarks.csproj @@ -1,21 +1,17 @@  - netcoreapp2.0;netcoreapp1.1;net47;net462 + netcoreapp2.1;netcoreapp2.0;netcoreapp1.1;net47 Mime-Detective.Benchmarks Mime-Detective.Benchmarks - true - 2.0 - false - false - false exe + latest - 0.10.11 + 0.10.13 diff --git a/test/Mime-Detective.Benchmarks/Program.cs b/test/Mime-Detective.Benchmarks/Program.cs index 53e36d9..a32aa79 100644 --- a/test/Mime-Detective.Benchmarks/Program.cs +++ b/test/Mime-Detective.Benchmarks/Program.cs @@ -12,188 +12,116 @@ using BenchmarkDotNet.Toolchains.CsProj; using System.Linq; using System.Runtime.CompilerServices; +using MimeDetective.Analyzers; namespace Mime_Detective.Benchmarks { - public class MyConfig : ManualConfig - { - public MyConfig() - { - Add(Job.Default.With(Runtime.Clr) - .With(CsProjClassicNetToolchain.Net462) - .With(Jit.RyuJit) - .With(Platform.X64) - .WithId("Net462")); - - Add(Job.Default.With(Runtime.Clr) - .With(CsProjClassicNetToolchain.Net47) - .With(Jit.RyuJit) - .With(Platform.X64) - .WithId("Net47")); - - Add(Job.Default.With(Runtime.Core) - .With(CsProjCoreToolchain.NetCoreApp11) - .With(Platform.X64) - .With(Jit.RyuJit) - .WithId("NetCore1.1")); - - Add(Job.Default.With(Runtime.Core) - .With(CsProjCoreToolchain.NetCoreApp20) - .With(Platform.X64) - .With(Jit.RyuJit) - .WithId("NetCore2.0")); - } - } - - [Config(typeof(MyConfig)), MemoryDiagnoser] - public class TypeLookup - { - const string goodFile = "./data/images/test.jpg"; - - const string goodXmlFile = "./data/Documents/test.docx"; - - const string goodZipFile = "./data/images.zip"; - - const string badFile = "./data/empty.jpg"; - - static byte[] bytes; - - static FileInfo GoodFile, GoodXmlFile, GoodZipFile, BadFile; - - [GlobalSetup] - public void Setup() - { - GoodFile = new FileInfo(goodFile); - //GoodXmlFile = new FileInfo(goodXmlFile); - //GoodZipFile = new FileInfo(goodZipFile); - //BadFile = new FileInfo(badFile); - - - FileType type = MimeTypes.Types[0]; - bytes = new byte[1000]; - using (FileStream file = GoodFile.OpenRead()) - { - file.Read(bytes,0,1000); - } - - GC.Collect(); - } - - //[Benchmark(OperationsPerInvoke = 1000)] - //public void GoodLookup() => DoNTimes(GoodFile); - - //[Benchmark(OperationsPerInvoke = 1000)] - //public void BinaryLookup() => DoNTimesBinary(GoodFile); - - //[Benchmark(OperationsPerInvoke = 1000)] - //public void BadLookup() => DoNTimes(BadFile); - - - //[Benchmark(OperationsPerInvoke = 1000)] - //public void DocxLookup() => DoNTimes(GoodXmlFile); - - //[Benchmark(OperationsPerInvoke = 1000)] - //public void ZipLookup() => DoNTimes(GoodZipFile); - - //[Benchmark(OperationsPerInvoke = 1000)] - - [Benchmark] - public FileType ByteArrayLookupBinary() - { - return bytes.GetFileType(); - } - - /* - //[Benchmark(OperationsPerInvoke = 1000)] - public void IsTextForLoopIter() - { - for (int i = 0; i < 1000; i++) - { - var type = IsTxtForLoop(); - } - } - - - public FileType IsTxtForLoop() - { - for (int i = 0; i < bytes.Length; i++) - { - if (bytes[i] != 0) - return null; - else if (i == bytes.Length - 1) - return MimeTypes.TXT; - } - - return null; - } - - //[Benchmark(OperationsPerInvoke = 1000)] - public void IsTextForeachLoopIter() - { - for (int i = 0; i < 1000; i++) - { - var type = IsTxtForeachLoop(); - } - } - - //[Benchmark(OperationsPerInvoke = 1000)] - public void IsTextWhileForeachLoopIter() - { - for (int i = 0; i < 1000; i++) - { - var type = IsTxtWhileForeachLoop(); - } - } - - public FileType IsTxtWhileForeachLoop() - { - var temp = bytes.AsEnumerable().GetEnumerator(); - - while(temp.MoveNext()) - { - if (temp.Current != 0) - return null; - } - - return MimeTypes.TXT; - } - - public FileType IsTxtForeachLoop() - { - foreach(byte bYte in bytes) - { - if (bYte != 0) - return null; - } - - return MimeTypes.TXT; - } - - //[Benchmark(OperationsPerInvoke = 1000)] - public void IsTextAnyLinqIter() - { - for (int i = 0; i < 1000; i++) - { - var type = IsTextAnyLinq(); - } - } - - - public FileType IsTextAnyLinq() - { - if (!bytes.Any(b => b == 0)) - return MimeTypes.TXT; - else - return null; - } - */ - } - - public class Program - { - public static void Main(string[] args) - { - var summary = BenchmarkRunner.Run(); - } - } + public class MyConfig : ManualConfig + { + public MyConfig() + { + Add(Job.Default.With(Runtime.Clr) + .With(CsProjClassicNetToolchain.Net47) + .With(Jit.RyuJit) + .With(Platform.X64) + .WithId("Net47")); + + Add(Job.Default.With(Runtime.Core) + .With(CsProjCoreToolchain.NetCoreApp11) + .With(Platform.X64) + .With(Jit.RyuJit) + .WithId("NetCore1.1")); + + Add(Job.Default.With(Runtime.Core) + .With(CsProjCoreToolchain.NetCoreApp20) + .With(Platform.X64) + .With(Jit.RyuJit) + .WithId("NetCore2.0")); + + Add(Job.Default.With(Runtime.Core) + .With(CsProjCoreToolchain.NetCoreApp21) + .With(Platform.X64) + .With(Jit.RyuJit) + .WithId("NetCore2.1")); + } + } + + [Config(typeof(MyConfig)), MemoryDiagnoser] + public class TypeLookup + { + static readonly byte[][] files = new byte[][] + { + ReadFile(new FileInfo("./data/Images/test.jpg")), + ReadFile(new FileInfo("./data/Images/test.gif")), + ReadFile(new FileInfo("./data/Documents/DocWord2016.doc")), + ReadFile(new FileInfo("./data/Zip/Images.zip")), + ReadFile(new FileInfo("./data/Assemblies/NativeExe.exe")), + ReadFile(new FileInfo("./data/Audio/wavVLC.wav")) + }; + + const int OpsPerInvoke = 6; + static readonly LinearCountingAnalyzer linear = new LinearCountingAnalyzer(MimeTypes.Types); + static readonly DictionaryBasedTrie trie2 = new DictionaryBasedTrie(MimeTypes.Types); + static readonly ArrayBasedTrie trie5 = new ArrayBasedTrie(MimeTypes.Types); + + static byte[] ReadFile(FileInfo info) + { + byte[] bytes = new byte[MimeTypes.MaxHeaderSize]; + using (FileStream file = info.OpenRead()) + { + file.Read(bytes, 0, MimeTypes.MaxHeaderSize); + } + return bytes; + } + + [Benchmark(OperationsPerInvoke = OpsPerInvoke, Baseline = true)] + public FileType LinearCountingAnalyzer() + { + FileType result = null; + foreach (var array in files) + { + using (ReadResult readResult = new ReadResult(array, MimeTypes.MaxHeaderSize)) + { + result = linear.Search(in readResult); + } + } + return result; + } + + [Benchmark(OperationsPerInvoke = OpsPerInvoke)] + public FileType DictionaryBasedTrie() + { + FileType result = null; + foreach (var array in files) + { + using (ReadResult readResult = new ReadResult(array, MimeTypes.MaxHeaderSize)) + { + result = trie2.Search(in readResult); + } + } + return result; + } + + [Benchmark(OperationsPerInvoke = OpsPerInvoke)] + public FileType ArrayBasedTrie() + { + FileType result = null; + foreach (var array in files) + { + using (ReadResult readResult = new ReadResult(array, MimeTypes.MaxHeaderSize)) + { + result = trie5.Search(in readResult); + } + } + return result; + } + } + + public class Program + { + public static void Main(string[] args) + { + var summary = BenchmarkRunner.Run(); + } + } } \ No newline at end of file diff --git a/test/Mime-Detective.Tests/Data/Assemblies/ManagedDLL.dll b/test/Mime-Detective.Tests/Data/Assemblies/ManagedDLL.dll new file mode 100644 index 0000000..6a0d62d Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Assemblies/ManagedDLL.dll differ diff --git a/test/Mime-Detective.Tests/Data/Assemblies/ManagedExe.exe b/test/Mime-Detective.Tests/Data/Assemblies/ManagedExe.exe new file mode 100644 index 0000000..494dd2d Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Assemblies/ManagedExe.exe differ diff --git a/test/Mime-Detective.Tests/Data/Assemblies/MixedDLL.dll b/test/Mime-Detective.Tests/Data/Assemblies/MixedDLL.dll new file mode 100644 index 0000000..83c688e Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Assemblies/MixedDLL.dll differ diff --git a/test/Mime-Detective.Tests/Data/Assemblies/MixedExe.exe b/test/Mime-Detective.Tests/Data/Assemblies/MixedExe.exe new file mode 100644 index 0000000..7216abd Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Assemblies/MixedExe.exe differ diff --git a/test/Mime-Detective.Tests/Data/Assemblies/NativeDLL.dll b/test/Mime-Detective.Tests/Data/Assemblies/NativeDLL.dll new file mode 100644 index 0000000..b006d25 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Assemblies/NativeDLL.dll differ diff --git a/test/Mime-Detective.Tests/Data/Assemblies/NativeExe.exe b/test/Mime-Detective.Tests/Data/Assemblies/NativeExe.exe new file mode 100644 index 0000000..0c75315 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Assemblies/NativeExe.exe differ diff --git a/test/Mime-Detective.Tests/Data/Audio/flacVLC.flac b/test/Mime-Detective.Tests/Data/Audio/flacVLC.flac new file mode 100644 index 0000000..2fa4d70 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/flacVLC.flac differ diff --git a/test/Mime-Detective.Tests/Data/Audio/mp3ArchiveFrameSync.mp3 b/test/Mime-Detective.Tests/Data/Audio/mp3ArchiveFrameSync.mp3 new file mode 100644 index 0000000..f26c0ca Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/mp3ArchiveFrameSync.mp3 differ diff --git a/test/Mime-Detective.Tests/Data/Audio/mp3ID3Test1.mp3 b/test/Mime-Detective.Tests/Data/Audio/mp3ID3Test1.mp3 new file mode 100644 index 0000000..084a7d1 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/mp3ID3Test1.mp3 differ diff --git a/test/Mime-Detective.Tests/Data/Audio/mp3ID3Test2.mp3 b/test/Mime-Detective.Tests/Data/Audio/mp3ID3Test2.mp3 new file mode 100644 index 0000000..3fd2e43 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/mp3ID3Test2.mp3 differ diff --git a/test/Mime-Detective.Tests/Data/Audio/mp3VLCFrameSync.mp3 b/test/Mime-Detective.Tests/Data/Audio/mp3VLCFrameSync.mp3 new file mode 100644 index 0000000..b7676d1 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/mp3VLCFrameSync.mp3 differ diff --git a/test/Mime-Detective.Tests/Data/Audio/mp4WinVoiceApp.m4a b/test/Mime-Detective.Tests/Data/Audio/mp4WinVoiceApp.m4a new file mode 100644 index 0000000..5f16689 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/mp4WinVoiceApp.m4a differ diff --git a/test/Mime-Detective.Tests/Data/Audio/oggArchive.ogg b/test/Mime-Detective.Tests/Data/Audio/oggArchive.ogg new file mode 100644 index 0000000..c9d1f65 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/oggArchive.ogg differ diff --git a/test/Mime-Detective.Tests/Data/Audio/oggVLC.ogg b/test/Mime-Detective.Tests/Data/Audio/oggVLC.ogg new file mode 100644 index 0000000..19d0df8 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/oggVLC.ogg differ diff --git a/test/Mime-Detective.Tests/Data/Audio/wavVLC.wav b/test/Mime-Detective.Tests/Data/Audio/wavVLC.wav new file mode 100644 index 0000000..e5671d4 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Audio/wavVLC.wav differ diff --git a/test/Mime-Detective.Tests/Data/Documents/DocWord97.doc b/test/Mime-Detective.Tests/Data/Documents/DocWord97.doc new file mode 100644 index 0000000..9cc2e72 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/DocWord97.doc differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeDoc.odt b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeDoc.odt new file mode 100644 index 0000000..3a7704d Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeDoc.odt differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel.xls b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel.xls new file mode 100644 index 0000000..b34cd58 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel.xls differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel50.xls b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel50.xls new file mode 100644 index 0000000..d407cab Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel50.xls differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel95.xls b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel95.xls new file mode 100644 index 0000000..d407cab Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeExcel95.xls differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficePpt.ppt b/test/Mime-Detective.Tests/Data/Documents/OpenOfficePpt.ppt new file mode 100644 index 0000000..77c98bd Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficePpt.ppt differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficePresentation.odp b/test/Mime-Detective.Tests/Data/Documents/OpenOfficePresentation.odp new file mode 100644 index 0000000..21dd115 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficePresentation.odp differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeRtf.rtf b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeRtf.rtf new file mode 100644 index 0000000..3af605f --- /dev/null +++ b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeRtf.rtf @@ -0,0 +1,17 @@ +{\rtf1\ansi\deff0\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\fnil\fprq2\fcharset0 Microsoft YaHei;}{\f4\fnil\fprq2\fcharset0 Arial;}{\f5\fswiss\fprq0\fcharset128 Arial;}} +{\colortbl;\red0\green0\blue0;\red128\green128\blue128;} +{\stylesheet{\s0\snext0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af6\langfe2052\dbch\af4\afs24\lang1081\loch\f0\fs24\lang1033 Default;} +{\s15\sbasedon0\snext16\sb240\sa120\keepn\hich\af3\dbch\af4\afs28\loch\f2\fs28 Heading;} +{\s16\sbasedon0\snext16\sb0\sa120 Text body;} +{\s17\sbasedon16\snext17\sb0\sa120\dbch\af5 List;} +{\s18\sbasedon0\snext18\sb120\sa120\noline\i\dbch\af5\afs24\ai\fs24 Caption;} +{\s19\sbasedon0\snext19\noline\dbch\af5 Index;} +}{\info{\author Josh Clark}{\creatim\yr2018\mo2\dy11\hr16\min13}{\author Josh Clark}{\revtim\yr2018\mo2\dy11\hr16\min17}{\printim\yr0\mo0\dy0\hr0\min0}{\comment OpenOffice}{\vern4150}}\deftab709 + +{\*\pgdsctbl +{\pgdsc0\pgdscuse195\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default;}} +\formshade\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\sectunlocked1\pgndec\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc +\pgndec\pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\hich\af6\langfe2052\dbch\af4\afs24\lang1081\loch\f0\fs24\lang1033{\rtlch \ltrch\loch +Hello, World!} +\par } \ No newline at end of file diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeSpreadsheet.ods b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeSpreadsheet.ods new file mode 100644 index 0000000..5dad989 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeSpreadsheet.ods differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWord6.0Doc.doc b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWord6.0Doc.doc new file mode 100644 index 0000000..a1c8fa7 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWord6.0Doc.doc differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWord95Doc.doc b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWord95Doc.doc new file mode 100644 index 0000000..a1c8fa7 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWord95Doc.doc differ diff --git a/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWordDoc.doc b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWordDoc.doc new file mode 100644 index 0000000..8dc9877 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/OpenOfficeWordDoc.doc differ diff --git a/test/Mime-Detective.Tests/Data/Documents/PubPublisher2016.pub b/test/Mime-Detective.Tests/Data/Documents/PubPublisher2016.pub deleted file mode 100644 index 03fe401..0000000 Binary files a/test/Mime-Detective.Tests/Data/Documents/PubPublisher2016.pub and /dev/null differ diff --git a/test/Mime-Detective.Tests/Data/Documents/XlsExcel2007.xls b/test/Mime-Detective.Tests/Data/Documents/XlsExcel2007.xls new file mode 100644 index 0000000..c53e528 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Documents/XlsExcel2007.xls differ diff --git a/test/Mime-Detective.Tests/Data/Zip/Images7zip.tar b/test/Mime-Detective.Tests/Data/Zip/Images7zip.tar new file mode 100644 index 0000000..f57fa81 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Zip/Images7zip.tar differ diff --git a/test/Mime-Detective.Tests/Data/Zip/TestBlender.rar b/test/Mime-Detective.Tests/Data/Zip/TestBlender.rar new file mode 100644 index 0000000..91152b8 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Zip/TestBlender.rar differ diff --git a/test/Mime-Detective.Tests/Data/Zip/WinRar.rar b/test/Mime-Detective.Tests/Data/Zip/WinRar.rar new file mode 100644 index 0000000..7a52373 Binary files /dev/null and b/test/Mime-Detective.Tests/Data/Zip/WinRar.rar differ diff --git a/test/Mime-Detective.Tests/Mime-Detective.Tests.csproj b/test/Mime-Detective.Tests/Mime-Detective.Tests.csproj index 815a4ab..d147dc2 100644 --- a/test/Mime-Detective.Tests/Mime-Detective.Tests.csproj +++ b/test/Mime-Detective.Tests/Mime-Detective.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0;net452;net462 + netcoreapp2.0;netcoreapp1.1;net45 Mime-Detective.Tests Mime-Detective.Tests true @@ -9,6 +9,8 @@ false false false + latest + MimeDetective @@ -18,17 +20,12 @@ - + - - - - - - + diff --git a/test/Mime-Detective.Tests/Tests/Analyzers/ArrayBasedTrieTests.cs b/test/Mime-Detective.Tests/Tests/Analyzers/ArrayBasedTrieTests.cs new file mode 100644 index 0000000..bdd4886 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Analyzers/ArrayBasedTrieTests.cs @@ -0,0 +1,75 @@ +using MimeDetective.Analyzers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests.Analyzers +{ + public class ArrayBasedTrieTests + { + [Fact] + public void DefaultConstructor() + { + var analyzer = new ArrayBasedTrie(); + + //assertion here just to have + Assert.NotNull(analyzer); + + analyzer.Insert(MimeTypes.ZIP); + } + + [Fact] + public void EnumerableConstructor() + { + var analyzer = new ArrayBasedTrie(MimeTypes.Types); + + //assertion here just to have + Assert.NotNull(analyzer); + Assert.Throws(() => new ArrayBasedTrie(null)); + + analyzer.Insert(MimeTypes.WORD); + } + + [Fact] + public void Insert() + { + var analyzer = new ArrayBasedTrie(); + Assert.Throws(() => analyzer.Insert(null)); + + foreach (var fileType in MimeTypes.Types) + { + analyzer.Insert(fileType); + } + + analyzer.Insert(MimeTypes.WORD); + } + + [Theory] + [InlineData("./Data/Documents/XlsExcel2016.xls", "xls")] + [InlineData("./Data/Documents/PptPowerpoint2016.ppt", "ppt")] + [InlineData("./Data/Documents/DocWord2016.doc", "doc")] + [InlineData("./Data/Documents/PdfWord2016.pdf", "pdf")] + [InlineData("./Data/Zip/empty.zip", "zip")] + [InlineData("./Data/Zip/images.zip", "zip")] + [InlineData("./Data/Zip/imagesBy7zip.zip", "zip")] + [InlineData("./Data/images/test.gif", "gif")] + [InlineData("./Data/Audio/wavVLC.wav", "wav")] + public async Task Search(string path, string ext) + { + var analyzer = new ArrayBasedTrie(MimeTypes.Types); + FileInfo file = new FileInfo(path); + FileType type = null; + + using (ReadResult result = await ReadResult.ReadFileHeaderAsync(file)) + { + type = analyzer.Search(in result); + } + + Assert.NotNull(type); + Assert.Contains(ext, type.Extension); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Analyzers/DictionaryBasedTrieTests.cs b/test/Mime-Detective.Tests/Tests/Analyzers/DictionaryBasedTrieTests.cs new file mode 100644 index 0000000..b3f90f3 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Analyzers/DictionaryBasedTrieTests.cs @@ -0,0 +1,76 @@ +using MimeDetective.Analyzers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests.Analyzers +{ + public class DictionaryBasedTrieTests + { + [Fact] + public void DefaultConstructor() + { + var analyzer = new DictionaryBasedTrie(); + + //assertion here just to have + Assert.NotNull(analyzer); + + analyzer.Insert(MimeTypes.ZIP); + } + + [Fact] + public void EnumerableConstructor() + { + var analyzer = new DictionaryBasedTrie(MimeTypes.Types); + + //assertion here just to have + Assert.NotNull(analyzer); + + Assert.Throws(() => new DictionaryBasedTrie(null)); + + analyzer.Insert(MimeTypes.WORD); + } + + [Fact] + public void Insert() + { + var analyzer = new DictionaryBasedTrie(); + Assert.Throws(() => analyzer.Insert(null)); + + foreach (var fileType in MimeTypes.Types) + { + analyzer.Insert(fileType); + } + + analyzer.Insert(MimeTypes.WORD); + } + + [Theory] + [InlineData("./Data/Documents/XlsExcel2016.xls", "xls")] + [InlineData("./Data/Documents/PptPowerpoint2016.ppt", "ppt")] + [InlineData("./Data/Documents/DocWord2016.doc", "doc")] + [InlineData("./Data/Documents/PdfWord2016.pdf", "pdf")] + [InlineData("./Data/Zip/empty.zip", "zip")] + [InlineData("./Data/Zip/images.zip", "zip")] + [InlineData("./Data/Zip/imagesBy7zip.zip", "zip")] + [InlineData("./Data/images/test.gif", "gif")] + [InlineData("./Data/Audio/wavVLC.wav", "wav")] + public async Task Search(string path, string ext) + { + var analyzer = new DictionaryBasedTrie(MimeTypes.Types); + FileInfo file = new FileInfo(path); + FileType type = null; + + using (ReadResult result = await ReadResult.ReadFileHeaderAsync(file)) + { + type = analyzer.Search(in result); + } + + Assert.NotNull(type); + Assert.Contains(ext, type.Extension); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Analyzers/LinearCountingAnalyzerTests.cs b/test/Mime-Detective.Tests/Tests/Analyzers/LinearCountingAnalyzerTests.cs new file mode 100644 index 0000000..f742ca5 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Analyzers/LinearCountingAnalyzerTests.cs @@ -0,0 +1,75 @@ +using MimeDetective.Analyzers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests.Analyzers +{ + public class LinearCountingAnalyzerTests + { + [Fact] + public void DefaultConstructor() + { + LinearCountingAnalyzer analyzer = new LinearCountingAnalyzer(); + + //assertion here just to have + Assert.NotNull(analyzer); + + analyzer.Insert(MimeTypes.ZIP); + } + + [Fact] + public void EnumerableConstructor() + { + LinearCountingAnalyzer analyzer = new LinearCountingAnalyzer(MimeTypes.Types); + + //assertion here just to have + Assert.NotNull(analyzer); + Assert.Throws(() => new LinearCountingAnalyzer(null)); + + analyzer.Insert(MimeTypes.WORD); + } + + [Fact] + public void Insert() + { + LinearCountingAnalyzer analyzer = new LinearCountingAnalyzer(); + Assert.Throws(() => analyzer.Insert(null)); + + foreach (var fileType in MimeTypes.Types) + { + analyzer.Insert(fileType); + } + + analyzer.Insert(MimeTypes.WORD); + } + + [Theory] + [InlineData("./Data/Documents/XlsExcel2016.xls", "xls")] + [InlineData("./Data/Documents/PptPowerpoint2016.ppt", "ppt")] + [InlineData("./Data/Documents/DocWord2016.doc", "doc")] + [InlineData("./Data/Documents/PdfWord2016.pdf", "pdf")] + [InlineData("./Data/Zip/empty.zip", "zip")] + [InlineData("./Data/Zip/images.zip", "zip")] + [InlineData("./Data/Zip/imagesBy7zip.zip", "zip")] + [InlineData("./Data/images/test.gif", "gif")] + [InlineData("./Data/Audio/wavVLC.wav", "wav")] + public async Task Search(string path, string ext) + { + LinearCountingAnalyzer analyzer = new LinearCountingAnalyzer(MimeTypes.Types); + FileInfo file = new FileInfo(path); + FileType type = null; + + using (ReadResult result = await ReadResult.ReadFileHeaderAsync(file)) + { + type = analyzer.Search(in result); + } + + Assert.NotNull(type); + Assert.Contains(ext, type.Extension); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Analyzers/MSOfficeAnalyzerTests.cs b/test/Mime-Detective.Tests/Tests/Analyzers/MSOfficeAnalyzerTests.cs new file mode 100644 index 0000000..a01fbc0 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Analyzers/MSOfficeAnalyzerTests.cs @@ -0,0 +1,42 @@ +using MimeDetective.Analyzers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests.Analyzers +{ + public class MSOfficeAnalyzerTests + { + [Fact] + public void DefaultConstructor() + { + MsOfficeAnalyzer analyzer = new MsOfficeAnalyzer(); + + //assertion here just to have + Assert.NotNull(analyzer); + } + + + [Theory] + [InlineData("./Data/Documents/XlsExcel2016.xls", "xls")] + [InlineData("./Data/Documents/PptPowerpoint2016.ppt", "ppt")] + [InlineData("./Data/Documents/DocWord2016.doc", "doc")] + public async Task Search(string path, string ext) + { + var analyzer = new MsOfficeAnalyzer(); + FileInfo file = new FileInfo(path); + FileType type = null; + + using (ReadResult result = await ReadResult.ReadFileHeaderAsync(file)) + { + type = analyzer.Search(in result); + } + + Assert.NotNull(type); + Assert.Contains(ext, type.Extension); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Analyzers/MimeAnalyzersTests.cs b/test/Mime-Detective.Tests/Tests/Analyzers/MimeAnalyzersTests.cs new file mode 100644 index 0000000..8278bb8 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Analyzers/MimeAnalyzersTests.cs @@ -0,0 +1,34 @@ +using MimeDetective.Analyzers; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace MimeDetective.Tests.Analyzers +{ + public class MimeAnalyzersTests + { + [Fact] + public void DefaultPrimaryAnalyzerNotNullOrEmpty() + { + Assert.NotNull(MimeAnalyzers.PrimaryAnalyzer); + Assert.IsType(MimeAnalyzers.PrimaryAnalyzer); + } + + [Fact] + public void PrimaryAnalyzerPropertyThrowsWhenAssignedNull() + { + MimeAnalyzers.PrimaryAnalyzer = MimeAnalyzers.PrimaryAnalyzer; + Assert.Throws(() => MimeAnalyzers.PrimaryAnalyzer = null); + } + + [Fact] + public void DefaultSecondaryAnalyzerNotNullOrEmpty() + { + Assert.NotNull(MimeAnalyzers.SecondaryAnalyzers); + Assert.NotEmpty(MimeAnalyzers.SecondaryAnalyzers); + Assert.IsType(MimeAnalyzers.SecondaryAnalyzers[MimeTypes.ZIP]); + Assert.IsType(MimeAnalyzers.SecondaryAnalyzers[MimeTypes.MS_OFFICE]); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Analyzers/ZipFileAnalyzerTestsTests.cs b/test/Mime-Detective.Tests/Tests/Analyzers/ZipFileAnalyzerTestsTests.cs new file mode 100644 index 0000000..2027d1a --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Analyzers/ZipFileAnalyzerTestsTests.cs @@ -0,0 +1,45 @@ +using MimeDetective.Analyzers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests.Analyzers +{ + public class ZipFileAnalyzerTests + { + [Fact] + public void DefaultConstructor() + { + var analyzer = new ZipFileAnalyzer(); + + //assertion here just to have + Assert.NotNull(analyzer); + } + + + [Theory] + [InlineData("./Data/Documents/PptxPowerpoint2016.pptx", "pptx")] + [InlineData("./Data/Documents/StrictOpenXMLWord2016.docx", "docx")] + [InlineData("./Data/Documents/XlsxExcel2016.xlsx", "xlsx")] + [InlineData("./Data/Documents/DocxWord2016.docx", "docx")] + [InlineData("./Data/Zip/Images.zip", "zip")] + [InlineData("./Data/Zip/ImagesBy7zip.zip", "zip")] + public async Task Search(string path, string ext) + { + var analyzer = new ZipFileAnalyzer(); + FileInfo file = new FileInfo(path); + FileType type = null; + + using (ReadResult result = await ReadResult.ReadFileHeaderAsync(file)) + { + type = analyzer.Search(in result); + } + + Assert.NotNull(type); + Assert.Contains(ext, type.Extension); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Assemblies/WindowsFormats.cs b/test/Mime-Detective.Tests/Tests/Assemblies/WindowsFormats.cs new file mode 100644 index 0000000..0d414b4 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Assemblies/WindowsFormats.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static MimeDetective.Utilities.TypeComparisions; + +namespace MimeDetective.Tests.Assemblies +{ + public class WindowsFormats + { + public const String AssembliesPath = "./Data/Assemblies"; + + [Theory] + [InlineData("ManagedExe.exe")] + [InlineData("ManagedDLL.dll")] + [InlineData("MixedDLL.dll")] + [InlineData("MixedExe.exe")] + [InlineData("NativeExe.exe")] + [InlineData("NativeDLL.dll")] + public async Task IsExeOrDLL(string fileName) + { + var info = GetFileInfo(AssembliesPath, fileName); + + Assert.True(info.IsExe()); + + await AssertIsType(info, MimeTypes.DLL_EXE); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Audio/CommonFormats.cs b/test/Mime-Detective.Tests/Tests/Audio/CommonFormats.cs new file mode 100644 index 0000000..b5afb1c --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Audio/CommonFormats.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static MimeDetective.Utilities.TypeComparisions; + +namespace MimeDetective.Tests.Audio +{ + public class CommonFormats + { + public const String AudioPath = "./Data/Audio"; + + [Theory] + [InlineData("mp3ID3Test1.mp3")] + [InlineData("mp3ID3Test2.mp3")] + public async Task IsMP3ID3(string fileName) + { + var info = GetFileInfo(AudioPath, fileName); + + await AssertIsType(info, MimeTypes.Mp3ID3); + } + + [Theory] + [InlineData("flacVLC.flac")] + public async Task IsFLAC(string fileName) + { + var info = GetFileInfo(AudioPath, fileName); + + await AssertIsType(info, MimeTypes.Flac); + } + + + [Theory] + [InlineData("oggVLC.ogg")] + [InlineData("oggArchive.ogg")] + public async Task IsOGG(string fileName) + { + var info = GetFileInfo(AudioPath, fileName); + + await AssertIsType(info, MimeTypes.OGG); + } + + [Theory] + [InlineData("mp4WinVoiceApp.m4a")] + public async Task IsMP4A(string fileName) + { + var info = GetFileInfo(AudioPath, fileName); + + await AssertIsType(info, MimeTypes.Mp4QuickTime); + } + + + [Theory] + [InlineData("wavVLC.wav")] + public async Task IsWAV(string fileName) + { + var info = GetFileInfo(AudioPath, fileName); + + await AssertIsType(info, MimeTypes.Wav); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Documents/CommonFormats.cs b/test/Mime-Detective.Tests/Tests/Documents/CommonFormats.cs new file mode 100644 index 0000000..bf94d80 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Documents/CommonFormats.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static MimeDetective.Utilities.TypeComparisions; + +namespace MimeDetective.Tests.Documents +{ + public class CommonFormats + { + public const string DocsPath = "./Data/Documents/"; + + [Theory] + [InlineData("RichTextWord2016")] + [InlineData("OpenOfficeRtf")] + public async Task IsRTF(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".rtf"); + + Assert.True(info.IsRtf()); + + await AssertIsType(info, MimeTypes.RTF); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Documents/MsOfficeFormats.cs b/test/Mime-Detective.Tests/Tests/Documents/MsOfficeFormats.cs index 82c8b36..81d7bdd 100644 --- a/test/Mime-Detective.Tests/Tests/Documents/MsOfficeFormats.cs +++ b/test/Mime-Detective.Tests/Tests/Documents/MsOfficeFormats.cs @@ -5,104 +5,137 @@ using Xunit; using System.IO; using MimeDetective; -using MimeDetective.Utilities; +using static MimeDetective.Utilities.TypeComparisions; namespace MimeDetective.Tests.Documents { - public class MsOfficeFormats - { - public const string DocsPath = "./Data/Documents/"; - - private static FileInfo GetFileInfo(string file, string ext) - { - return new FileInfo(Path.Combine(DocsPath, file+ext)); - } - - [Theory] - [InlineData("DocWord2016")] - public async Task IsDoc(string filePath) - { - var info = GetFileInfo(filePath, ".doc"); - - Assert.True(info.IsWord()); - - await TypeComparisions.AssertIsType(info, MimeTypes.WORD); - } - - [Theory] - [InlineData("DocxWord2016")] - [InlineData("StrictOpenXMLWord2016")] - public async Task IsDocx(string filePath) - { - var info = GetFileInfo(filePath, ".docx"); - - Assert.True(info.IsWord()); - - await TypeComparisions.AssertIsType(info, MimeTypes.WORDX); - } - - [Theory] - [InlineData("RichTextWord2016")] - public async Task IsRTF(string filePath) - { - var info = GetFileInfo(filePath, ".rtf"); - - Assert.True(info.IsRtf()); - - await TypeComparisions.AssertIsType(info, MimeTypes.RTF); - } - - [Theory] - [InlineData("OpenDocWord2016")] - public async Task IsOpenDoc(string filePath) - { - var info = GetFileInfo(filePath, ".odt"); - - await TypeComparisions.AssertIsType(info, MimeTypes.ODT); - } - - [Theory] - [InlineData("PptPowerpoint2016")] - public async Task IsPowerPoint(string filePath) - { - var info = GetFileInfo(filePath, ".ppt"); - - Assert.True(info.IsPowerPoint()); - - await TypeComparisions.AssertIsType(info, MimeTypes.PPT); - } - - [Theory] - [InlineData("PptxPowerpoint2016")] - public async Task IsPowerPointX(string filePath) - { - var info = GetFileInfo(filePath, ".pptx"); - - Assert.True(info.IsPowerPoint()); - - await TypeComparisions.AssertIsType(info, MimeTypes.PPTX); - } - - [Theory] - [InlineData("XlsExcel2016")] - public async Task IsExcel(string filePath) - { - var info = GetFileInfo(filePath, ".xls"); - - Assert.True(info.IsExcel()); - - await TypeComparisions.AssertIsType(info, MimeTypes.EXCEL); - } - - [Theory] - [InlineData("XlsxExcel2016")] - public async Task IsExcelX(string filePath) - { - var info = GetFileInfo(filePath, ".xlsx"); - - Assert.True(info.IsExcel()); - - await TypeComparisions.AssertIsType(info, MimeTypes.EXCELX); - } - } + public class MsOfficeFormats + { + public const string DocsPath = "./Data/Documents/"; + + //examples of doc which don't match specific sub header but match ms doc header + [Theory] + [InlineData("OpenOfficePpt.ppt")] + [InlineData("OpenOfficeWord6.0Doc.doc")] + [InlineData("OpenOfficeWord95Doc.doc")] + [InlineData("OpenOfficeWordDoc.doc")] + [InlineData("OpenOfficeExcel.xls")] + [InlineData("OpenOfficeExcel50.xls")] + [InlineData("OpenOfficeExcel95.xls")] + [InlineData("XlsExcel2007.xls")] + public async Task IsMSOLEDocType(string fileName) + { + var info = GetFileInfo(DocsPath, fileName); + + await AssertIsType(info, MimeTypes.MS_OFFICE); + } + + [Theory] + [InlineData("DocWord2016.doc")] + [InlineData("DocWord97.doc")] + [InlineData("OpenOfficeWord6.0Doc.doc")] + [InlineData("OpenOfficeWord95Doc.doc")] + [InlineData("OpenOfficeWordDoc.doc")] + [InlineData("DocxWord2016.docx")] + [InlineData("StrictOpenXMLWord2016.docx")] + public void IsDoc(string filePath) + { + var info = GetFileInfo(DocsPath, filePath); + + Assert.True(info.IsWord()); + } + + [Theory] + [InlineData("DocWord2016")] + [InlineData("DocWord97")] + public async Task IsDoc2(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".doc"); + + Assert.True(info.IsWord()); + + await AssertIsType(info, MimeTypes.WORD); + } + + [Theory] + [InlineData("DocxWord2016")] + [InlineData("StrictOpenXMLWord2016")] + public async Task IsDocx(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".docx"); + + Assert.True(info.IsWord()); + + await AssertIsType(info, MimeTypes.WORDX); + } + + [Theory] + [InlineData("PptPowerpoint2016.ppt")] + [InlineData("OpenOfficePpt.ppt")] + [InlineData("PptxPowerpoint2016.pptx")] + public void IsPointPoint(string filePath) + { + var info = GetFileInfo(DocsPath, filePath); + + Assert.True(info.IsPowerPoint()); + } + + [Theory] + [InlineData("PptPowerpoint2016")] + public async Task IsPowerPoint(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".ppt"); + + Assert.True(info.IsPowerPoint()); + + await AssertIsType(info, MimeTypes.PPT); + } + + [Theory] + [InlineData("PptxPowerpoint2016")] + public async Task IsPowerPointX(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".pptx"); + + Assert.True(info.IsPowerPoint()); + + await AssertIsType(info, MimeTypes.PPTX); + } + + [Theory] + [InlineData("XlsExcel2016.xls")] + [InlineData("XlsExcel2007.xls")] + [InlineData("OpenOfficeExcel.xls")] + [InlineData("OpenOfficeExcel50.xls")] + [InlineData("OpenOfficeExcel95.xls")] + [InlineData("XlsxExcel2016.xlsx")] + public void IsExcel(string filePath) + { + var info = GetFileInfo(DocsPath, filePath); + + Assert.True(info.IsExcel()); + } + + [Theory] + [InlineData("XlsExcel2016")] + public async Task IsExcel2(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".xls"); + + Assert.True(info.IsExcel()); + + await AssertIsType(info, MimeTypes.EXCEL); + } + + [Theory] + [InlineData("XlsxExcel2016")] + public async Task IsExcelX(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".xlsx"); + + Assert.True(info.IsExcel()); + + await AssertIsType(info, MimeTypes.EXCELX); + } + } } diff --git a/test/Mime-Detective.Tests/Tests/Documents/OpenDocFormats.cs b/test/Mime-Detective.Tests/Tests/Documents/OpenDocFormats.cs new file mode 100644 index 0000000..492eb91 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Documents/OpenDocFormats.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static MimeDetective.Utilities.TypeComparisions; + +namespace MimeDetective.Tests.Documents +{ + public class OpenDocFormats + { + public const string DocsPath = "./Data/Documents/"; + + [Theory] + [InlineData("OpenDocWord2016")] + [InlineData("OpenOfficeDoc")] + public async Task IsOpenDoc(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".odt"); + + await AssertIsType(info, MimeTypes.ODT); + } + + [Theory] + [InlineData("OpenOfficePresentation")] + public async Task IsOpenPresentation(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".odp"); + + await AssertIsType(info, MimeTypes.ODP); + } + + [Theory] + [InlineData("OpenOfficeSpreadsheet")] + public async Task IsOpenSpreadSheet(string filePath) + { + var info = GetFileInfo(DocsPath, filePath, ".ods"); + + await AssertIsType(info, MimeTypes.ODS); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Documents/PdfFormats.cs b/test/Mime-Detective.Tests/Tests/Documents/PdfFormats.cs index d377aea..21f4b10 100644 --- a/test/Mime-Detective.Tests/Tests/Documents/PdfFormats.cs +++ b/test/Mime-Detective.Tests/Tests/Documents/PdfFormats.cs @@ -1,26 +1,25 @@ using System.IO; using System.Threading.Tasks; using Xunit; +using static MimeDetective.Utilities.TypeComparisions; namespace MimeDetective.Tests.Tests.Documents { - public class PdfFormats - { - public const string DocsPath = "./Data/Documents/"; + public class PdfFormats + { + public const string DocsPath = "./Data/Documents/"; - [Theory] - [InlineData("MicrosoftPrintToPdf.pdf")] - [InlineData("GithubTestPdf2.pdf")] - [InlineData("PdfWord2016.pdf")] - public async Task FileInfoPDF(string testPdf) - { - var info = new FileInfo(Path.Combine(DocsPath, testPdf)); + [Theory] + [InlineData("MicrosoftPrintToPdf")] + [InlineData("GithubTestPdf2")] + [InlineData("PdfWord2016")] + public async Task FileInfoPDF(string testPdf) + { + var info = GetFileInfo(DocsPath, testPdf, ".pdf"); - var a = Directory.GetCurrentDirectory(); + Assert.True(info.IsPdf()); - var fileInfo = await info.GetFileTypeAsync(); - - Assert.Equal(fileInfo, MimeTypes.PDF); - } - } + await AssertIsType(info, MimeTypes.PDF); + } + } } \ No newline at end of file diff --git a/test/Mime-Detective.Tests/Tests/Extensions/ByteArrayExtensionTests.cs b/test/Mime-Detective.Tests/Tests/Extensions/ByteArrayExtensionTests.cs new file mode 100644 index 0000000..025130d --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Extensions/ByteArrayExtensionTests.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MimeDetective.Tests.Extensions +{ + public class ByteArrayExtensionTests + { + } +} diff --git a/test/Mime-Detective.Tests/Tests/Extensions/FileInfoExtensionTests.cs b/test/Mime-Detective.Tests/Tests/Extensions/FileInfoExtensionTests.cs new file mode 100644 index 0000000..48c389a --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Extensions/FileInfoExtensionTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests.Extensions +{ + public class FileInfoExtensionTests + { + const string GoodFile = "./data/Images/test.jpg"; + const string GoodXmlFile = "./data/Documents/DocxWord2016.docx"; + const string GoodZipFile = "./data/Zip/images.zip"; + const string BadFile = "./data/Images/empty.jpg"; + const string NonexistentFile = "./data/nonexistent.jpg"; + const string smallTxtFile = "./data/Text/SuperSmall.txt"; + + //small ascii text files + const string oneByteFile = "./data/Text/oneCharFile.txt"; + const string twoByteFile = "./data/Text/twoCharFile.txt"; + const string threeByteFile = "./data/Text/threeCharFile.txt"; + + + //load from fileinfo + //attempt to load from real file + //attempt to load from nonexistent file /badfile + [Fact] + public async Task FromFileAsync() + { + var fileInfo = new FileInfo(GoodFile); + + var fileType = await fileInfo.GetFileTypeAsync(); + + Assert.True(fileType == MimeTypes.JPEG); + } + + [Fact] + public void FromFile() + { + var fileInfo = new FileInfo(GoodFile); + + Assert.True(fileInfo.IsJpeg()); + } + + //test shouldn't fail, an empty file can be valid input + [Fact] + public async Task FromEmptyFile() + { + var fileInfo = new FileInfo(BadFile); + + var type = await fileInfo.GetFileTypeAsync(); + + //no match so return type is null + Assert.Null(type); + } + + [Fact] + public async Task FromNonExistentFile() + { + var fileInfo = new FileInfo(NonexistentFile); + + await Assert.ThrowsAnyAsync(() => fileInfo.GetFileTypeAsync()); + } + + [Theory] + [InlineData("./data/Zip/images.zip")] + [InlineData("./data/Zip/Empty.zip")] + public void IsZipFile(string fileName) + { + var fileInfo = new FileInfo(fileName); + + Assert.True(fileInfo.IsZip()); + } + + [Theory] + [InlineData("./data/Zip/WinRar.rar")] + public void IsRarFile(string fileName) + { + var fileInfo = new FileInfo(fileName); + + Assert.True(fileInfo.IsRar()); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/Extensions/StreamExtensionTests.cs b/test/Mime-Detective.Tests/Tests/Extensions/StreamExtensionTests.cs new file mode 100644 index 0000000..0bd7652 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Extensions/StreamExtensionTests.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MimeDetective.Tests.Extensions +{ + public class StreamExtensionTests + { + } +} diff --git a/test/Mime-Detective.Tests/Tests/FileHeader.cs b/test/Mime-Detective.Tests/Tests/FileHeader.cs index 4ee0aa7..0f422de 100644 --- a/test/Mime-Detective.Tests/Tests/FileHeader.cs +++ b/test/Mime-Detective.Tests/Tests/FileHeader.cs @@ -5,6 +5,7 @@ using Xunit; using System.Reflection; using static MimeDetective.Tests.ReflectionHelpers; +using MimeDetective.Analyzers; namespace MimeDetective.Tests { @@ -77,10 +78,10 @@ public void VerifyMimesAreNotNullOrEmpty() } [Fact] - public void VerifyAllNonZipFileTypesArePresentInTypesArray() + public void VerifyAllNonExcludedTypesArePresentInTypesArray() { var allTypeValues = GetAllTypeValues(); - var zipFileTypesFilteredOut = allTypeValues.Except(MimeTypes.XmlTypes).Except(new FileType[] { MimeTypes.TXT }); + var zipFileTypesFilteredOut = allTypeValues.Except(MsOfficeAnalyzer.MsDocTypes).Except(MimeTypes.XmlTypes).Except(new FileType[] { MimeTypes.TXT }); Assert.NotNull(zipFileTypesFilteredOut); Assert.NotEmpty(zipFileTypesFilteredOut); diff --git a/test/Mime-Detective.Tests/Tests/Images/CommonFormats.cs b/test/Mime-Detective.Tests/Tests/Images/CommonFormats.cs index a8394d1..70848a9 100644 --- a/test/Mime-Detective.Tests/Tests/Images/CommonFormats.cs +++ b/test/Mime-Detective.Tests/Tests/Images/CommonFormats.cs @@ -6,107 +6,87 @@ using System.IO; using MimeDetective; using MimeDetective.Extensions; +using MimeDetective.Utilities; namespace MimeDetective.Tests.Images { - // This project can output the Class library as a NuGet Package. - // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". - public class CommonFormats - { - public CommonFormats() - { - } + public class CommonFormats + { + public const string ImagePath = "./Data/Images/"; - public const string ImagePath = "./Data/Images/"; + [Fact] + public async Task IsJpeg() + { + var info = new FileInfo(ImagePath + "test.jpg"); - private readonly string BmpMime = MimeTypes.BMP.Mime; + Assert.True(info.IsJpeg()); - private readonly string IcoMime = MimeTypes.ICO.Mime; + //false assertions + Assert.False(info.IsGif()); - [Fact] - public void IsJpeg() - { - var info = new FileInfo(ImagePath + "test.jpg"); + Assert.False(info.IsPng()); - Assert.True(info.IsJpeg()); + await TypeComparisions.AssertIsType(info, MimeTypes.JPEG); + } - //false assertions - Assert.False(info.IsGif()); + [Fact] + public async Task IsBitmap() + { + var info = new FileInfo(ImagePath + "test.bmp"); - Assert.False(info.IsPng()); + //false assertions + Assert.False(info.IsJpeg()); - Assert.False(info.GetFileType().Mime == BmpMime); + Assert.False(info.IsGif()); - Assert.False(info.GetFileType().Mime == IcoMime); - } + Assert.False(info.IsPng()); - [Fact] - public void IsBitmap() - { - var info = new FileInfo(ImagePath + "test.bmp"); + await TypeComparisions.AssertIsType(info, MimeTypes.BMP); + } - Assert.True(info.GetFileType().Mime == BmpMime); + [Fact] + public async Task IsPng() + { + var info = new FileInfo(ImagePath + "test.png"); - //false assertions - Assert.False(info.IsJpeg()); + Assert.True(info.IsPng()); - Assert.False(info.IsGif()); + //false assertions + Assert.False(info.IsGif()); - Assert.False(info.IsPng()); + Assert.False(info.IsJpeg()); - Assert.False(info.GetFileType().Mime == IcoMime); - } + await TypeComparisions.AssertIsType(info, MimeTypes.PNG); + } - [Fact] - public void IsPng() - { - var info = new FileInfo(ImagePath + "test.png"); + [Fact] + public async Task IsGif() + { + var info = new FileInfo(ImagePath + "test.gif"); - Assert.True(info.IsPng()); + Assert.True(info.IsGif()); - //false assertions - Assert.False(info.IsGif()); + //false assertions + Assert.False(info.IsPng()); - Assert.False(info.IsJpeg()); + Assert.False(info.IsJpeg()); - Assert.False(info.GetFileType().Mime == BmpMime); + await TypeComparisions.AssertIsType(info, MimeTypes.GIF); + } - Assert.False(info.GetFileType().Mime == IcoMime); - } + [Fact] + public async Task IsIco() + { + var info = new FileInfo(ImagePath + "test.ico"); - [Fact] - public void IsGif() - { - var info = new FileInfo(ImagePath + "test.gif"); + //false assertions + Assert.False(info.IsPng()); - Assert.True(info.IsGif()); + Assert.False(info.IsGif()); - //false assertions - Assert.False(info.IsPng()); + Assert.False(info.IsJpeg()); - Assert.False(info.IsJpeg()); - - Assert.False(info.GetFileType().Mime == BmpMime); - - Assert.False(info.GetFileType().Mime == IcoMime); - } - - [Fact] - public void IsIco() - { - var info = new FileInfo(ImagePath + "test.ico"); - - Assert.True(info.GetFileType().Mime == IcoMime); - - //false assertions - Assert.False(info.IsPng()); - - Assert.False(info.IsGif()); - - Assert.False(info.IsJpeg()); - - Assert.False(info.GetFileType().Mime == BmpMime); - } - - } + await TypeComparisions.AssertIsType(info, MimeTypes.ICO); + } + } } diff --git a/test/Mime-Detective.Tests/Tests/ReadResult.cs b/test/Mime-Detective.Tests/Tests/ReadResult.cs new file mode 100644 index 0000000..afbe70f --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/ReadResult.cs @@ -0,0 +1,226 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Tests +{ + public class ReadResultTests + { + [Theory] + [InlineData(100, 50)] + [InlineData(560, 560)] + [InlineData(1024, 1024)] + [InlineData(1024, 560)] + public void CreateFromArray(int arraySize, int readLength) + { + ReadResult readResult = new ReadResult(new byte[arraySize], readLength); + + Assert.NotNull(readResult.Array); + Assert.NotEmpty(readResult.Array); + Assert.StrictEqual(arraySize, readResult.Array.Length); + Assert.StrictEqual(readLength, readResult.ReadLength); + Assert.False(readResult.IsArrayRented); + Assert.False(readResult.ShouldDisposeStream); + Assert.False(readResult.ShouldResetStreamPosition); + Assert.Null(readResult.Source); + } + + [Fact] + public void CreateFromNullArrayThrows() + { + Assert.Throws(() => new ReadResult(null, 560)); + } + + [Theory] + [InlineData(-1)] + [InlineData(-10)] + [InlineData(561)] + [InlineData(1024)] + public void CreateFromArrayReadLengthOutOfBoundsThrows(int readLength) + { + Assert.Throws(() => new ReadResult(new byte[560], readLength)); + } + + [Fact] + public void CreateFromArrayDispose() + { + ReadResult readResult = new ReadResult(new byte[560], 560); + + readResult.Dispose(); + + Assert.Throws(() => ArrayPool.Shared.Return(readResult.Array)); + } + + public const String testFile = "./Data/Images/test.jpg"; + public const String nonExistingFile = "./Data/Images/supertest.jpg"; + + [Fact] + public void CreateFromFile() + { + ReadResult readResult = ReadResult.ReadFileHeader(new FileInfo(testFile)); + Assert.NotNull(readResult.Array); + Assert.NotNull(readResult.Source); + Assert.InRange(readResult.ReadLength, 1, readResult.Array.Length); + Assert.True(readResult.IsArrayRented); + Assert.True(readResult.ShouldDisposeStream); + Assert.False(readResult.ShouldResetStreamPosition); + } + + [Fact] + public async Task CreateFromFileAsync() + { + ReadResult readResultAsync = await ReadResult.ReadFileHeaderAsync(new FileInfo(testFile)); + Assert.NotNull(readResultAsync.Array); + Assert.NotNull(readResultAsync.Source); + Assert.InRange(readResultAsync.ReadLength, 1, readResultAsync.Array.Length); + Assert.True(readResultAsync.IsArrayRented); + Assert.True(readResultAsync.ShouldDisposeStream); + Assert.False(readResultAsync.ShouldResetStreamPosition); + } + + [Fact] + public async Task CreateFromNullFileThrows() + { + Assert.Throws(() => ReadResult.ReadFileHeader(null)); + await Assert.ThrowsAsync(async () => await ReadResult.ReadFileHeaderAsync(null)); + } + + [Fact] + public async Task CreateFromFileThatDoesNotExistThrows() + { + Assert.Throws(() => ReadResult.ReadFileHeader(new FileInfo(nonExistingFile))); + await Assert.ThrowsAsync(async () => await ReadResult.ReadFileHeaderAsync(new FileInfo(nonExistingFile))); + } + + [Fact] + public void CreateFromFileDispose() + { + ReadResult readResult = ReadResult.ReadFileHeader(new FileInfo(testFile)); + readResult.Dispose(); + + Assert.Throws(() => readResult.Source.ReadByte()); + } + + [Fact] + public async Task CreateFromFileDisposeAsync() + { + ReadResult readResult = await ReadResult.ReadFileHeaderAsync(new FileInfo(testFile)); + readResult.Dispose(); + + Assert.Throws(() => readResult.Source.ReadByte()); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void CreateFromStream(bool shouldDisposeStream, bool resetStreamPos) + { + FileInfo file = new FileInfo(testFile); + + using (FileStream stream = file.OpenRead()) + { + ReadResult readResult = ReadResult.ReadHeaderFromStream(stream, shouldDisposeStream, resetStreamPos); + Assert.NotNull(readResult.Array); + Assert.NotNull(readResult.Source); + Assert.InRange(readResult.ReadLength, 1, readResult.Array.Length); + Assert.True(readResult.IsArrayRented); + Assert.StrictEqual(shouldDisposeStream, readResult.ShouldDisposeStream); + Assert.StrictEqual(resetStreamPos, readResult.ShouldResetStreamPosition); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task CreateFromStreamAsync(bool shouldDisposeStream, bool resetStreamPos) + { + FileInfo file = new FileInfo(testFile); + + using (FileStream stream = file.OpenRead()) + { + ReadResult readResult = await ReadResult.ReadHeaderFromStreamAsync(stream, shouldDisposeStream, resetStreamPos); + Assert.NotNull(readResult.Array); + Assert.NotNull(readResult.Source); + Assert.InRange(readResult.ReadLength, 1, readResult.Array.Length); + Assert.True(readResult.IsArrayRented); + Assert.StrictEqual(shouldDisposeStream, readResult.ShouldDisposeStream); + Assert.StrictEqual(resetStreamPos, readResult.ShouldResetStreamPosition); + } + } + + [Fact] + public async Task CreateFromNullStreamThrows() + { + Assert.Throws(() => ReadResult.ReadHeaderFromStream(null)); + await Assert.ThrowsAsync(async () => await ReadResult.ReadHeaderFromStreamAsync(null)); + } + + [Fact] + public async Task CreateFromUnreadableStreamThrows() + { + Stream closedStream = new FileInfo(testFile).OpenRead(); + closedStream.Dispose(); + Assert.Throws(() => ReadResult.ReadHeaderFromStream(closedStream)); + await Assert.ThrowsAsync(async () => await ReadResult.ReadHeaderFromStreamAsync(closedStream)); + } + + //Stream should not be disposed and should be reset back to zero + [Fact] + public void CreateFromStreamDispose() + { + Stream stream = new FileInfo(testFile).OpenRead(); + ReadResult readResult = ReadResult.ReadHeaderFromStream(stream); + + readResult.Dispose(); + + Assert.StrictEqual(0, readResult.Source.Position); + + int testRead = readResult.Source.ReadByte(); + readResult.Source.Dispose(); + } + + [Fact] + public void CreateFromStreamDisposeShouldDisposeStream() + { + Stream stream = new FileInfo(testFile).OpenRead(); + ReadResult readResult = ReadResult.ReadHeaderFromStream(stream, shouldDisposeStream: true); + + readResult.Dispose(); + + Assert.Throws(() => readResult.Source.ReadByte()); + } + + [Fact] + public async Task CreateFromStreamDisposeAsync() + { + Stream stream = new FileInfo(testFile).OpenRead(); + ReadResult readResult = await ReadResult.ReadHeaderFromStreamAsync(stream); + + readResult.Dispose(); + + Assert.StrictEqual(0, readResult.Source.Position); + + int testRead = readResult.Source.ReadByte(); + readResult.Source.Dispose(); + } + + [Fact] + public async Task CreateFromStreamAsyncDisposeShouldDisposeStream() + { + Stream stream = new FileInfo(testFile).OpenRead(); + ReadResult readResult = await ReadResult.ReadHeaderFromStreamAsync(stream, shouldDisposeStream: true); + + readResult.Dispose(); + + Assert.Throws(() => readResult.Source.ReadByte()); + } + } +} diff --git a/test/Mime-Detective.Tests/Tests/ReflectionHelpers.cs b/test/Mime-Detective.Tests/Tests/ReflectionHelpers.cs index 4bd0d1d..3ba4815 100644 --- a/test/Mime-Detective.Tests/Tests/ReflectionHelpers.cs +++ b/test/Mime-Detective.Tests/Tests/ReflectionHelpers.cs @@ -2,26 +2,25 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; namespace MimeDetective.Tests { public static class ReflectionHelpers { - public static IEnumerable GetAllTypeValues() - { - var mimeTypes = typeof(MimeTypes); - var fields = mimeTypes.GetFields(); - var filteredFields = fields.Where(x => x.FieldType == typeof(FileType)); + public static IEnumerable GetAllTypeValues() + { + var mimeTypes = typeof(MimeTypes); + var fields = mimeTypes.GetFields(); + var filteredFields = fields.Where(x => x.FieldType == typeof(FileType)); - return filteredFields.Select(x => (FileType)x.GetValue(null)); - } + return filteredFields.Select(x => (FileType)x.GetValue(null)); + } - public static IEnumerable GetAllTypeFields() - { - var mimeTypes = typeof(MimeTypes); - var fields = mimeTypes.GetFields(); - return fields.Where(x => x.FieldType == typeof(FileType)); - } - } + public static IEnumerable GetAllTypeFields() + { + var mimeTypes = typeof(MimeTypes); + var fields = mimeTypes.GetFields(); + return fields.Where(x => x.FieldType == typeof(FileType)); + } + } } diff --git a/test/Mime-Detective.Tests/Tests/Text/CommonFormats.cs b/test/Mime-Detective.Tests/Tests/Text/CommonFormats.cs index c414ed0..e84e7c9 100644 --- a/test/Mime-Detective.Tests/Tests/Text/CommonFormats.cs +++ b/test/Mime-Detective.Tests/Tests/Text/CommonFormats.cs @@ -5,23 +5,24 @@ using MimeDetective; using Xunit; using System.IO; +using MimeDetective.Utilities; namespace MimeDetective.Tests.Text { - public class TextTests - { - public const string TextPath = "./Data/Text/"; + public class TextTests + { + public const string TextPath = "./Data/Text/"; - public const string TextFile = "test.txt"; + public const string TextFile = "test.txt"; - private readonly static string TxtMime = MimeTypes.TXT.Mime; + [Fact] + public async Task IsTxt() + { + var info = new FileInfo(TextPath + TextFile); - [Fact] - public void IsTxt() - { - var info = new FileInfo(TextPath + TextFile); + var fileType = await info.GetFileTypeAsync(); - Assert.True(info.GetFileType().Mime == TxtMime); - } - } + Assert.Equal(fileType.Extension, MimeTypes.TXT.Extension); + } + } } diff --git a/test/Mime-Detective.Tests/Tests/TypeComparisions.cs b/test/Mime-Detective.Tests/Tests/TypeComparisions.cs new file mode 100644 index 0000000..750f751 --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/TypeComparisions.cs @@ -0,0 +1,36 @@ +using MimeDetective; +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace MimeDetective.Utilities +{ + public static class TypeComparisions + { + public static FileInfo GetFileInfo(string dataPath, string file) + { + return new FileInfo(Path.Combine(dataPath, file)); + } + + public static FileInfo GetFileInfo(string dataPath, string file, string ext) + { + return new FileInfo(Path.Combine(dataPath, file + ext)); + } + + public static async Task AssertIsType(FileInfo info, FileType type) + { + Assert.Equal(type, await info.GetFileTypeAsync()); + + Assert.Equal(type, info.GetFileType()); + + Assert.True(info.IsType(type)); + + Assert.True(info.GetFileType().Equals(type)); + + Assert.True(info.GetFileType() == type); + + Assert.False(info.GetFileType() != type); + } + } +} \ No newline at end of file diff --git a/test/Mime-Detective.Tests/Tests/TypeExtensions.cs b/test/Mime-Detective.Tests/Tests/TypeExtensions.cs index ee1854e..17cfedd 100644 --- a/test/Mime-Detective.Tests/Tests/TypeExtensions.cs +++ b/test/Mime-Detective.Tests/Tests/TypeExtensions.cs @@ -9,258 +9,341 @@ namespace MimeDetective.Tests { - public class TypeExtensions - { - const string GoodFile = "./data/Images/test.jpg"; + public class TypeExtensions + { + const string GoodFile = "./data/Images/test.jpg"; - const string GoodXmlFile = "./data/Documents/DocxWord2016.docx"; + const string GoodXmlFile = "./data/Documents/DocxWord2016.docx"; - const string GoodZipFile = "./data/Zip/images.zip"; - - const string GoodXLSFile = "./data/Documents/XlsxExcel2016.xlsx"; - - const string GoodPptxFile = "./data/Documents/PptxPowerpoint2016.pptx"; + const string GoodZipFile = "./data/Zip/images.zip"; const string BadFile = "./data/Images/empty.jpg"; - const string NonexistentFile = "./data/nonexistent.jpg"; - - const string smallTxtFile = "./data/Text/SuperSmall.txt"; - - //small ascii text files - const string oneByteFile = "./data/Text/oneCharFile.txt"; - const string twoByteFile = "./data/Text/twoCharFile.txt"; - const string threeByteFile = "./data/Text/threeCharFile.txt"; - - - //load from fileinfo - //attempt to load from real file - //attempt to load from nonexistent file /badfile - [Fact] - public async Task FromFile() - { - var fileInfo = new FileInfo(GoodFile); - - var fileType = await fileInfo.GetFileTypeAsync(); - - Assert.True(fileType == MimeTypes.JPEG); - } - - [Fact] - public void FromFileSync() - { - var fileInfo = new FileInfo(GoodFile); - - Assert.True(fileInfo.IsJpeg()); - } + const string NonexistentFile = "./data/nonexistent.jpg"; - //test shouldn't fail, an empty file can be valid input - [Fact] - public async Task FromEmptyFile() - { - var fileInfo = new FileInfo(BadFile); + const string smallTxtFile = "./data/Text/SuperSmall.txt"; - var type = await fileInfo.GetFileTypeAsync(); + //small ascii text files + const string oneByteFile = "./data/Text/oneCharFile.txt"; + const string twoByteFile = "./data/Text/twoCharFile.txt"; + const string threeByteFile = "./data/Text/threeCharFile.txt"; - //no match so return type is null - Assert.Null(type); - } - [Fact] - public async Task FromNonExistentFile() - { - var fileInfo = new FileInfo(NonexistentFile); + //load from fileinfo + //attempt to load from real file + //attempt to load from nonexistent file /badfile + [Fact] + public async Task FromFileAsync() + { + var fileInfo = new FileInfo(GoodFile); - await Assert.ThrowsAnyAsync(() => fileInfo.GetFileTypeAsync()); - } + var fileType = await fileInfo.GetFileTypeAsync(); - //load from stream - //attempt to load from good stream - //attempt to load from empty stream - //stream shouldn't be closed + Assert.True(fileType == MimeTypes.JPEG); + } - [Fact] - public async Task FromStream() - { - var fileInfo = new FileInfo(GoodFile); + [Fact] + public void FromFile() + { + var fileInfo = new FileInfo(GoodFile); - using (var fileStream = fileInfo.OpenRead()) - { - var fileType = await fileStream.GetFileTypeAsync(); + Assert.True(fileInfo.IsJpeg()); + } - Assert.NotNull(fileType); + //test shouldn't fail, an empty file can be valid input + [Fact] + public async Task FromEmptyFile() + { + var fileInfo = new FileInfo(BadFile); - Assert.Equal(MimeTypes.JPEG, fileType); - } - } + var type = await fileInfo.GetFileTypeAsync(); - [Fact] - public void FromStreamSync() - { - var fileInfo = new FileInfo(GoodFile); + //no match so return type is null + Assert.Null(type); + } - using (var fileStream = fileInfo.OpenRead()) - { - var fileType = fileStream.GetFileType(); + [Fact] + public async Task FromNonExistentFile() + { + var fileInfo = new FileInfo(NonexistentFile); - Assert.NotNull(fileType); + await Assert.ThrowsAnyAsync(() => fileInfo.GetFileTypeAsync()); + } - Assert.Equal(MimeTypes.JPEG, fileType); - } - } + //load from stream + //attempt to load from good stream + //attempt to load from empty stream + //stream shouldn't be closed - [Fact] - public async Task FromEmptyStream() - { - var emptyStream = System.IO.Stream.Null; + [Fact] + public async Task FromStreamAsync() + { + var fileInfo = new FileInfo(GoodFile); - var nullReturn = await emptyStream.GetFileTypeAsync(); + using (var fileStream = fileInfo.OpenRead()) + { + var fileType = await fileStream.GetFileTypeAsync(); - Assert.Null(nullReturn); - } + Assert.NotNull(fileType); + Assert.Equal(MimeTypes.JPEG, fileType); + } + } - [Theory] - [InlineData(GoodFile, "jpg")] - [InlineData(GoodXmlFile, "docx")] - [InlineData(GoodZipFile, "zip")] - public async Task StreamShouldStillBeOpen(string path, string ext) - { - var fileInfo = new FileInfo(path); + [Fact] + public void FromStream() + { + var fileInfo = new FileInfo(GoodFile); - var fileStream = fileInfo.OpenRead(); - - var fileType = await fileStream.GetFileTypeAsync(); + using (var fileStream = fileInfo.OpenRead()) + { + var fileType = fileStream.GetFileType(); - Assert.NotNull(fileType); + Assert.NotNull(fileType); + Assert.Equal(MimeTypes.JPEG, fileType); + } + } - Assert.Equal(ext, fileType.Extension); + [Fact] + public async Task FromEmptyStream() + { + var emptyStream = System.IO.Stream.Null; - Assert.True(fileStream.CanRead); + var nullReturn = await emptyStream.GetFileTypeAsync(); - Assert.True(fileStream.CanSeek); + Assert.Null(nullReturn); + } - Assert.False(fileStream.CanWrite); - fileStream.Dispose(); - - Assert.False(fileStream.CanRead); - - Assert.False(fileStream.CanSeek); - - Assert.False(fileStream.CanWrite); - } - - [Theory] - [InlineData(GoodFile, "jpg")] - [InlineData(GoodXmlFile, "docx")] - [InlineData(GoodZipFile, "zip")] - public async Task StreamShouldBeDisposed(string path, string ext) - { - var fileInfo = new FileInfo(path); - - var fileStream = fileInfo.OpenRead(); - - var fileType = await fileStream.GetFileTypeAsync(shouldDisposeStream: true); + [Theory] + [InlineData(GoodFile, "jpg")] + [InlineData(GoodXmlFile, "docx")] + [InlineData(GoodZipFile, "zip")] + public void StreamShouldStillBeOpenAndPositionReset(string path, string ext) + { + var fileInfo = new FileInfo(path); + var fileStream = fileInfo.OpenRead(); + var fileType = fileStream.GetFileType(); - Assert.NotNull(fileType); + Assert.NotNull(fileType); - Assert.Equal(ext, fileType.Extension); + Assert.Equal(ext, fileType.Extension); + Assert.True(fileStream.CanRead); + Assert.True(fileStream.CanSeek); + Assert.False(fileStream.CanWrite); + Assert.Equal(0, fileStream.Position); - Assert.NotNull(fileStream); + fileStream.Dispose(); + } - Assert.False(fileStream.CanRead); + [Theory] + [InlineData(GoodFile, "jpg")] + [InlineData(GoodXmlFile, "docx")] + [InlineData(GoodZipFile, "zip")] + public async Task StreamShouldStillBeOpenAndPositionResetAsync(string path, string ext) + { + var fileInfo = new FileInfo(path); + var fileStream = fileInfo.OpenRead(); + var fileType = await fileStream.GetFileTypeAsync(); - Assert.False(fileStream.CanSeek); + Assert.NotNull(fileType); - Assert.False(fileStream.CanWrite); - } + Assert.Equal(ext, fileType.Extension); + Assert.True(fileStream.CanRead); + Assert.True(fileStream.CanSeek); + Assert.False(fileStream.CanWrite); + Assert.Equal(0, fileStream.Position); - [Theory] - [InlineData(GoodFile, "jpg")] - [InlineData(GoodXmlFile, "docx")] - [InlineData(GoodZipFile, "zip")] - public void StreamShouldBeDisposedSync(string path, string ext) - { - var fileInfo = new FileInfo(path); + fileStream.Dispose(); + } - var fileStream = fileInfo.OpenRead(); + [Theory] + [InlineData(GoodFile, "jpg")] + [InlineData(GoodXmlFile, "docx")] + [InlineData(GoodZipFile, "zip")] + public void StreamShouldBeDisposed(string path, string ext) + { + var fileInfo = new FileInfo(path); + var fileStream = fileInfo.OpenRead(); + var fileType = fileStream.GetFileType(shouldDisposeStream: true); + + Assert.NotNull(fileType); + Assert.Equal(ext, fileType.Extension); + Assert.NotNull(fileStream); + Assert.False(fileStream.CanRead); + Assert.False(fileStream.CanSeek); + Assert.Throws(() => fileStream.ReadByte()); + } + + [Theory] + [InlineData(GoodFile, "jpg")] + [InlineData(GoodXmlFile, "docx")] + [InlineData(GoodZipFile, "zip")] + public async Task StreamShouldBeDisposedAsync(string path, string ext) + { + var fileInfo = new FileInfo(path); + var fileStream = fileInfo.OpenRead(); + var fileType = await fileStream.GetFileTypeAsync(shouldDisposeStream: true); + + Assert.NotNull(fileType); + Assert.Equal(ext, fileType.Extension); + Assert.NotNull(fileStream); + Assert.False(fileStream.CanRead); + Assert.False(fileStream.CanSeek); + Assert.Throws(() => fileStream.ReadByte()); + } + + [Theory] + [InlineData(GoodFile, "jpg")] + [InlineData(GoodXmlFile, "docx")] + [InlineData(GoodZipFile, "zip")] + public void StreamShouldBeReset(string path, string ext) + { + var fileInfo = new FileInfo(path); + var fileStream = fileInfo.OpenRead(); + var fileType = fileStream.GetFileType(shouldResetStreamPosition: true); + + Assert.NotNull(fileType); + Assert.Equal(ext, fileType.Extension); + Assert.NotNull(fileStream); + Assert.True(fileStream.CanRead); + Assert.True(fileStream.CanSeek); + Assert.Equal(0, fileStream.Position); + fileStream.ReadByte(); + fileStream.Dispose(); + } + + [Theory] + [InlineData(GoodFile, "jpg")] + [InlineData(GoodXmlFile, "docx")] + [InlineData(GoodZipFile, "zip")] + public async Task StreamShouldBeResetAsync(string path, string ext) + { + var fileInfo = new FileInfo(path); + var fileStream = fileInfo.OpenRead(); + var fileType = await fileStream.GetFileTypeAsync(shouldResetStreamPosition: true); + + Assert.NotNull(fileType); + Assert.Equal(ext, fileType.Extension); + Assert.NotNull(fileStream); + Assert.True(fileStream.CanRead); + Assert.True(fileStream.CanSeek); + Assert.Equal(0, fileStream.Position); + fileStream.ReadByte(); + fileStream.Dispose(); + } + + //load from byte array + //load from good byte array + //attempt to load from empty byte array + [Fact] + public async Task FromByteArray() + { + var fileInfo = new FileInfo(GoodFile); + + //560 is the max file header size + byte[] byteArray = new byte[560]; + + using (var fileStream = fileInfo.OpenRead()) + { + await fileStream.ReadAsync(byteArray, 0, 560); + } - var fileType = fileStream.GetFileType(shouldDisposeStream: true); + var mimeType = byteArray.GetFileType(); - Assert.NotNull(fileType); + Assert.NotNull(mimeType); - Assert.Equal(ext, fileType.Extension); + Assert.Equal(MimeTypes.JPEG, mimeType); + } - Assert.NotNull(fileStream); + [Fact] + public void FromEmptyByteArray() + { + var zerodByteArray = new byte[560]; - Assert.False(fileStream.CanRead); + var zerodResult = zerodByteArray.GetFileType(); - Assert.False(fileStream.CanSeek); + Assert.Null(zerodResult); - Assert.False(fileStream.CanWrite); - } + var emptyBtyeArray = new byte[0]; - //load from byte array - //load from good byte array - //attempt to load from empty byte array - [Theory] - [InlineData(GoodFile, "jpg")] - [InlineData(GoodXmlFile, "docx")] - [InlineData(GoodZipFile, "zip")] - [InlineData(GoodXLSFile, "xlsx")] - [InlineData(GoodPptxFile, "pptx")] - public async Task FromByteArray(string filePath, string ext) - { - var byteArray = File.ReadAllBytes(filePath); + Assert.Null(emptyBtyeArray.GetFileType()); + } - var mimeType = byteArray.GetFileType(); + [Theory] + [InlineData(new byte[] { 0 })] + [InlineData(new byte[] { 0, 0 })] + [InlineData(new byte[] { 0, 0, 0, 0 })] + [InlineData(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 1 })] + [InlineData(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 1 })] + public void NoMatchingFileDef(byte[] array) + { + var almostZerodResult = array.GetFileType(); - Assert.NotNull(mimeType); + Assert.Null(almostZerodResult); + } - Assert.Equal(ext, mimeType.Extension); - } + [Theory] + [InlineData(smallTxtFile)] + [InlineData(oneByteFile, Skip = "Planned in text detector for 1.0.0")] + [InlineData(twoByteFile, Skip = "Planned in text detector for 1.0.0")] + [InlineData(threeByteFile, Skip = "Planned in text detector for 1.0.0")] + public async Task FromSmallTxtFile(string file) + { + FileInfo smallTxt = new FileInfo(file); - [Fact] - public void FromEmptyByteArray() - { - var zerodByteArray = new byte[560]; + using (var stream = smallTxt.OpenRead()) + { + byte[] bytes = new byte[smallTxt.Length]; - var zerodResult = zerodByteArray.GetFileType(); + await stream.ReadAsync(bytes, 0, bytes.Length); - Assert.Null(zerodResult); + stream.Seek(0, SeekOrigin.Begin); - var emptyBtyeArray = new byte[0]; + var result = await smallTxt.GetFileTypeAsync(); - Assert.Null(emptyBtyeArray.GetFileType()); + Assert.Equal(MimeTypes.TXT_UTF8, result); + Assert.Equal(MimeTypes.TXT_UTF8, await stream.GetFileTypeAsync()); + Assert.Equal(MimeTypes.TXT_UTF8, bytes.GetFileType()); + } + } - //Assert.ThrowsAny(() => emptyBtyeArray.GetFileType()); - } + [Fact] + public void StreamIsNotDisposedAfterReadingZipFileType() + { + var fileInfo = new FileInfo(GoodZipFile); + var fileStream = fileInfo.OpenRead(); + var fileType = fileStream.GetFileType(shouldResetStreamPosition: true); - [Theory] - [InlineData(smallTxtFile)] - [InlineData(oneByteFile, Skip = "Planned in text detector for 1.0.0")] - [InlineData(twoByteFile, Skip = "Planned in text detector for 1.0.0")] - [InlineData(threeByteFile, Skip = "Planned in text detector for 1.0.0")] - public async Task FromSmallTxtFile(string file) - { - FileInfo smallTxt = new FileInfo(file); + Assert.NotNull(fileType); + fileStream.ReadByte(); + fileStream.Seek(0, SeekOrigin.Begin); - using (var stream = smallTxt.OpenRead()) - { - byte[] bytes = new byte[smallTxt.Length]; + var result = fileStream.GetFileType(); + Assert.NotNull(result); + fileStream.ReadByte(); + fileStream.Seek(0, SeekOrigin.Begin); - await stream.ReadAsync(bytes, 0, bytes.Length); + fileStream.Dispose(); + } - stream.Seek(0, SeekOrigin.Begin); - var result = await smallTxt.GetFileTypeAsync(); + [Fact] + public async Task StreamIsNotDisposedAfterReadingZipFileTypeAsync() + { + var fileInfo = new FileInfo(GoodZipFile); + var fileStream = fileInfo.OpenRead(); + var fileType = await fileStream.GetFileTypeAsync(shouldResetStreamPosition: true); - Assert.Equal(MimeTypes.TXT_UTF8, result); + Assert.NotNull(fileType); + fileStream.ReadByte(); + fileStream.Seek(0, SeekOrigin.Begin); - Assert.Equal(MimeTypes.TXT_UTF8, await stream.GetFileTypeAsync()); + var result = fileStream.GetFileType(); + Assert.NotNull(result); + fileStream.ReadByte(); + fileStream.Seek(0, SeekOrigin.Begin); - Assert.Equal(MimeTypes.TXT_UTF8, bytes.GetFileType()); - } - } - } + fileStream.Dispose(); + } + } } diff --git a/test/Mime-Detective.Tests/Tests/Zip/CommonFormats.cs b/test/Mime-Detective.Tests/Tests/Zip/CommonFormats.cs new file mode 100644 index 0000000..a4640ec --- /dev/null +++ b/test/Mime-Detective.Tests/Tests/Zip/CommonFormats.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static MimeDetective.Utilities.TypeComparisions; + +namespace MimeDetective.Tests.Zip +{ + public class CommonFormats + { + public const string dataPath = "./Data/Zip"; + + [Theory] + [InlineData("Empty")] + [InlineData("Images")] + [InlineData("ImagesBy7zip")] + public async Task IsZip(string file) + { + var fileInfo = GetFileInfo(dataPath, file, ".zip"); + + Assert.True(fileInfo.IsZip()); + + await AssertIsType(fileInfo, MimeTypes.ZIP); + } + + [Fact] + public async Task Is7zip() + { + var fileInfo = GetFileInfo(dataPath, "Images", ".7z"); + + await AssertIsType(fileInfo, MimeTypes.ZIP_7z); + } + + [Fact] + public async Task IsRar() + { + var fileInfo = GetFileInfo(dataPath, "TestBlender", ".rar"); + + await AssertIsType(fileInfo, MimeTypes.RAR); + } + + /* + [Fact] + public async Task IsTar() + { + var fileInfo = GetFileInfo(dataPath, "Images7zip", ".tar"); + + await AssertIsType(fileInfo, MimeTypes.TAR_ZV); + } + */ + } +} diff --git a/test/Mime-Detective.Tests/Utilities/TypeComparisions.cs b/test/Mime-Detective.Tests/Utilities/TypeComparisions.cs deleted file mode 100644 index 84b87ee..0000000 --- a/test/Mime-Detective.Tests/Utilities/TypeComparisions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using MimeDetective; -using System; -using System.IO; -using System.Threading.Tasks; -using Xunit; - -namespace MimeDetective.Utilities -{ - public static class TypeComparisions - { - public static async Task AssertIsType(FileInfo info, FileType type) - { - Assert.Equal(await info.GetFileTypeAsync(), type); - - Assert.Equal(info.GetFileType(), type); - - Assert.True(info.IsType(type)); - - Assert.True(info.GetFileType().Equals(type)); - - Assert.True(info.GetFileType() == type); - - Assert.False(info.GetFileType() != type); - } - } -} \ No newline at end of file