diff --git a/ReMime.ReFile/Program.cs b/ReMime.ReFile/Program.cs index 551caaf..4499e43 100644 --- a/ReMime.ReFile/Program.cs +++ b/ReMime.ReFile/Program.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; +using Microsoft.VisualBasic; namespace ReMime.Cli { @@ -47,7 +49,10 @@ namespace ReMime.Cli [DoesNotReturn] private static void ListTypes() { - foreach (MediaType type in MediaTypeResolver.KnownTypes) + var list = MediaTypeResolver.KnownTypes.ToList(); + list.Sort(); + + foreach (MediaType type in list) { Console.WriteLine("{0}\t{1}", type.FullTypeNoParameters, string.Join(' ', type.Extensions)); } diff --git a/ReMime.ReFile/ReMime.ReFile.csproj b/ReMime.ReFile/ReMime.ReFile.csproj index 054f5c4..e80f971 100644 --- a/ReMime.ReFile/ReMime.ReFile.csproj +++ b/ReMime.ReFile/ReMime.ReFile.csproj @@ -9,7 +9,7 @@ True ReFuel.ReMime.ReFile - 0.1.0 + 0.1.1 H. Utku Maden ReFuel README.md @@ -34,7 +34,7 @@ - + diff --git a/ReMime.Tests/MediaTypesByExtension.cs b/ReMime.Tests/MediaTypesByExtension.cs index d40e781..d1ded79 100644 --- a/ReMime.Tests/MediaTypesByExtension.cs +++ b/ReMime.Tests/MediaTypesByExtension.cs @@ -3,21 +3,9 @@ using ReMime.Platform; namespace ReMime.Tests { - public abstract class MediaTypesByExtension where T : IMediaTypeResolver, new() + [TestClass] + public class MediaTypesByExtension { - private T CIT; - - protected MediaTypesByExtension() - { - Unsafe.SkipInit(out CIT); - } - - [TestInitialize] - public virtual void Initialize() - { - CIT = new T(); - } - readonly (string extension, string type)[] ExampleMimeTypes = new (string, string)[] { ("png", "image/png"), ("gif", "image/gif"), @@ -34,44 +22,10 @@ namespace ReMime.Tests { foreach (var(ext, type) in ExampleMimeTypes) { - Assert.IsTrue(CIT.TryResolve(ext, out MediaType? result)); + Assert.IsTrue(MediaTypeResolver.TryResolve(ext, out MediaType? result)); Assert.AreEqual(result!.FullType, type); Assert.IsTrue(result.Extensions.Contains(ext)); } } } - - [TestClass] - public class UnixMediaTypes : MediaTypesByExtension - { - [TestInitialize] - public override void Initialize() - { - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD()) - { - base.Initialize(); - } - else - { - Assert.Inconclusive("Cannot test this in this platform."); - } - } - } - - [TestClass] - public class Win32MediaTypes : MediaTypesByExtension - { - [TestInitialize] - public override void Initialize() - { - if (OperatingSystem.IsWindows()) - { - base.Initialize(); - } - else - { - Assert.Inconclusive("Cannot test this in this platform."); - } - } - } } diff --git a/ReMime/ContentResolvers/IMagicValueResolver.cs b/ReMime/ContentResolvers/IMagicValueResolver.cs new file mode 100644 index 0000000..b8affb9 --- /dev/null +++ b/ReMime/ContentResolvers/IMagicValueResolver.cs @@ -0,0 +1,18 @@ + +using System.Collections.Generic; + +namespace ReMime.ContentResolvers +{ + public interface IMagicValueResolver + { + void AddMagicValue(MagicValueMediaType value); + + void AddMagicValues(IEnumerable values) + { + foreach (MagicValueMediaType value in values) + { + AddMagicValue(value); + } + } + } +} diff --git a/ReMime/ContentResolvers/MagEx.cs b/ReMime/ContentResolvers/MagEx.cs new file mode 100644 index 0000000..59b84da --- /dev/null +++ b/ReMime/ContentResolvers/MagEx.cs @@ -0,0 +1,86 @@ + +using System; + +namespace ReMime.ContentResolvers +{ + /* You've heard of regular expressions, now prepare for magical expressions :sparkles:. */ + /// + /// Bit pattern detecting state machine inspired by text regular expressions. + /// + public class MagEx + { + /** + * 0 1 2 3 4 5 6 7 8 9 a b c d e f 4-bit patterns to match. + * l h Single bit pattern. + * * Any bit pattern. + * ? Any 4-bit pattern. + * 'pattern' ASCII pattern with no terminator. Implies @. + * @ Align to 8-bits. + * % Align to 4-bits. + */ + + public string Pattern { get; } + + public MagEx(string pattern) + { + Pattern = pattern; + } + + public bool Match(ReadOnlySpan bytes) + { + byte current; + int needle; + int haystack; + int bits; + int pi = 0; + ReadOnlySpan ascii = ReadOnlySpan.Empty; + for (int i = 0; i < bytes.Length; i++) + { + current = bytes[i]; + bits = 8; + + while (bits > 0) + { + char pat = Pattern[pi]; + switch (pat) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': case 'a': case 'A': + case 'b': case 'B': case 'c': case 'C': + case 'd': case 'D': case 'e': case 'E': + case 'f': case 'F': + haystack = current & 0xF; + current >>= 4; + bits -= 4; + + if (pat >= '0' && pat <= '9') + { + needle = pat - '0'; + } + else if (pat >= 'a' && pat <= 'f') + { + needle = pat - 'a'; + } + else + { + needle = pat - 'A'; + } + + if (haystack == needle) + { + pi++; + } + else + { + + } + break; + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/ReMime/ContentResolvers/MagicResolver.cs b/ReMime/ContentResolvers/MagicContentResolver.cs similarity index 90% rename from ReMime/ContentResolvers/MagicResolver.cs rename to ReMime/ContentResolvers/MagicContentResolver.cs index 31330ce..774304e 100644 --- a/ReMime/ContentResolvers/MagicResolver.cs +++ b/ReMime/ContentResolvers/MagicContentResolver.cs @@ -6,30 +6,28 @@ using System.Linq; namespace ReMime.ContentResolvers { - public record MagicValueMediaType(MediaType MediaType, MagicValue[] MagicValues, string[] Extensions); + public record MagicValueMediaType(MediaType MediaType, MagicValue[] MagicValues) + { + public IReadOnlyCollection Extensions { get; } = MediaType.Extensions; + } - public class MagicContentResolver : IMediaContentResolver + public class MagicContentResolver : IMediaContentResolver, IMagicValueResolver { private readonly List _mediaTypes = new List(); private readonly Dictionary _extensions = new Dictionary(); private readonly Tree _tree = new Tree(); private int _maxBytes = 0; - public MagicContentResolver(IEnumerable values) : this() + private MagicContentResolver() { - AddMagicValues(values); - } - - public MagicContentResolver() - { - List entries; + IEnumerable entries; using (Stream str = typeof(MagicContentResolver).Assembly.GetManifestResourceStream("ReMime.ContentResolvers.database.jsonc")!) { entries = MagicValueDatabaseEntry.GetEntries(str); } - AddMagicValues(entries.Select(x => (MagicValueMediaType)x)); + AddMagicValues(entries); } public IReadOnlyCollection MediaTypes => _mediaTypes.AsReadOnly(); @@ -86,6 +84,8 @@ namespace ReMime.ContentResolvers return _extensions.TryGetValue(extension, out mediaType); } + public static MagicContentResolver Instance { get; } = new MagicContentResolver(); + private class Tree { public MagicValueMediaType? Node { get; private set; } diff --git a/ReMime/ContentResolvers/MagicValueDatabaseEntry.cs b/ReMime/ContentResolvers/MagicValueDatabaseEntry.cs index bb5262d..f23924f 100644 --- a/ReMime/ContentResolvers/MagicValueDatabaseEntry.cs +++ b/ReMime/ContentResolvers/MagicValueDatabaseEntry.cs @@ -18,24 +18,24 @@ namespace ReMime.ContentResolvers [JsonPropertyName("extensions")] public List Extensions { get; set; } = new List(); - public static List GetEntries(Stream str) + public static IEnumerable GetEntries(Stream str) { return JsonSerializer.Deserialize>(str, new JsonSerializerOptions() { AllowTrailingCommas = true, ReadCommentHandling = JsonCommentHandling.Skip - }) ?? throw new Exception(); + })?.Select(x => (MagicValueMediaType)x) + ?? throw new Exception(); } public static explicit operator MagicValueMediaType(MagicValueDatabaseEntry entry) { return new MagicValueMediaType( - new MediaType(entry.Type), + new MediaType(entry.Type, entry.Extensions), entry.Magic.Select(x => (MagicValue.TryParse(x, out var value), value)) .Where(x => x.Item1) .Select(x => (MagicValue)x.value!) - .ToArray(), - entry.Extensions.ToArray() + .ToArray() ); } } diff --git a/ReMime/ContentResolvers/RiffResolver.cs b/ReMime/ContentResolvers/RiffResolver.cs index ea949fe..85a0cb7 100644 --- a/ReMime/ContentResolvers/RiffResolver.cs +++ b/ReMime/ContentResolvers/RiffResolver.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; namespace ReMime.ContentResolvers { - public class RiffResolver : IMediaTypeResolver, IMediaContentResolver + public class RiffResolver : IMediaContentResolver, IMagicValueResolver { public readonly List _mediaTypes = new List(); public readonly Dictionary _extensions = new Dictionary(); @@ -15,27 +15,26 @@ namespace ReMime.ContentResolvers public IReadOnlyCollection MediaTypes { get; } - public RiffResolver() + private RiffResolver() { MediaTypes = _mediaTypes.AsReadOnly(); - List entries; + IEnumerable entries; using (Stream str = typeof(MagicContentResolver).Assembly.GetManifestResourceStream("ReMime.ContentResolvers.riff.jsonc")!) { entries = MagicValueDatabaseEntry.GetEntries(str); } - foreach (var entry in entries) - { - AddRiffType((MagicValueMediaType)entry); - } + AddMagicValues(entries); } - public RiffResolver(IEnumerable values) : this() + public void AddMagicValues(IEnumerable entries) { - foreach (MagicValueMediaType value in values) - AddRiffType(value); + foreach (var entry in entries) + { + AddMagicValue(entry); + } } public bool TryResolve(Stream str, [NotNullWhen(true)] out MediaType? mediaType) @@ -69,7 +68,7 @@ namespace ReMime.ContentResolvers /// Add a RIFF sub-magic value to this resolver. /// /// - public void AddRiffType(MagicValueMediaType type) + public void AddMagicValue(MagicValueMediaType type) { if (type.MagicValues.Length == 0) throw new ArgumentException("Expected at least one media type."); @@ -92,6 +91,8 @@ namespace ReMime.ContentResolvers } } + public static RiffResolver Instance { get; } = new RiffResolver(); + [StructLayout(LayoutKind.Auto, Size = 12)] private struct RiffChunk { diff --git a/ReMime/MediaTypeResolver.cs b/ReMime/MediaTypeResolver.cs index e116737..5bff010 100644 --- a/ReMime/MediaTypeResolver.cs +++ b/ReMime/MediaTypeResolver.cs @@ -50,22 +50,13 @@ namespace ReMime static MediaTypeResolver() { - AddResolver(new RiffResolver(), 9997); - AddResolver(new MagicContentResolver(), 9998); + AddResolver(RiffResolver.Instance, 1000); + AddResolver(MagicContentResolver.Instance, 1001); - if (OperatingSystem.IsWindows()) - { - AddResolver(new Win32MediaTypeResolver()); - } - else if (OperatingSystem.IsLinux()) - { - AddResolver(new UnixMediaTypeResolver()); - // TODO: add freedesktop mime type database. - } - else if (OperatingSystem.IsMacOS()) - { - AddResolver(new UnixMediaTypeResolver()); //? - } + if (Win32MediaTypeResolver.Instance != null) + AddResolver(Win32MediaTypeResolver.Instance, 1002); + if (UnixMediaTypeResolver.Instance != null) + AddResolver(UnixMediaTypeResolver.Instance, 1002); } /// diff --git a/ReMime/Platform/UnixMediaTypeResolver.cs b/ReMime/Platform/UnixMediaTypeResolver.cs index d50f7fb..3c1902f 100644 --- a/ReMime/Platform/UnixMediaTypeResolver.cs +++ b/ReMime/Platform/UnixMediaTypeResolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; namespace ReMime.Platform @@ -13,7 +14,7 @@ namespace ReMime.Platform private readonly Dictionary _extensionsMap = new Dictionary(); public IReadOnlyCollection MediaTypes { get; } - public UnixMediaTypeResolver() + private UnixMediaTypeResolver() { { bool valid = OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD(); @@ -53,6 +54,20 @@ namespace ReMime.Platform return _extensionsMap.TryGetValue(extension, out mediaType); } + public static UnixMediaTypeResolver? Instance { get; } = null; + + static UnixMediaTypeResolver() + { + try + { + Instance = new UnixMediaTypeResolver(); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + private static readonly char[] s_delimeters = new char[] { '\t', ' ' }; private static void DigestMimeDatabase(TextReader reader, List types) diff --git a/ReMime/Platform/Win32MediaTypeResolver.cs b/ReMime/Platform/Win32MediaTypeResolver.cs index 9cd0bfb..4daa635 100644 --- a/ReMime/Platform/Win32MediaTypeResolver.cs +++ b/ReMime/Platform/Win32MediaTypeResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Microsoft.Win32; @@ -13,7 +14,7 @@ namespace ReMime.Platform private readonly Dictionary _extensionsMap = new Dictionary(); public IReadOnlyCollection MediaTypes { get; } - public Win32MediaTypeResolver() + private Win32MediaTypeResolver() { if (!OperatingSystem.IsWindows()) throw new PlatformNotSupportedException(); @@ -57,5 +58,19 @@ namespace ReMime.Platform { return _extensionsMap.TryGetValue(extension, out mediaType); } + + public static Win32MediaTypeResolver? Instance { get; } = null; + + static Win32MediaTypeResolver() + { + try + { + Instance = new Win32MediaTypeResolver(); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } } } \ No newline at end of file diff --git a/ReMime/ReMime.csproj b/ReMime/ReMime.csproj index 7c64d3f..78dcaa9 100644 --- a/ReMime/ReMime.csproj +++ b/ReMime/ReMime.csproj @@ -8,7 +8,7 @@ True ReFuel.ReMime - 0.1.0 + 0.1.1 H. Utku Maden ReFuel README.md