Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

12 changed files with 97 additions and 182 deletions

View File

@ -2,8 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using Microsoft.VisualBasic;
namespace ReMime.Cli namespace ReMime.Cli
{ {
@ -49,10 +47,7 @@ namespace ReMime.Cli
[DoesNotReturn] [DoesNotReturn]
private static void ListTypes() private static void ListTypes()
{ {
var list = MediaTypeResolver.KnownTypes.ToList(); foreach (MediaType type in MediaTypeResolver.KnownTypes)
list.Sort((a,b) => StringComparer.InvariantCulture.Compare(a.FullType, b.FullType));
foreach (MediaType type in list)
{ {
Console.WriteLine("{0}\t{1}", type.FullTypeNoParameters, string.Join(' ', type.Extensions)); Console.WriteLine("{0}\t{1}", type.FullTypeNoParameters, string.Join(' ', type.Extensions));
} }

View File

@ -9,13 +9,13 @@
<!--NuGet--> <!--NuGet-->
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>ReFuel.ReMime.ReFile</PackageId> <PackageId>ReFuel.ReMime.ReFile</PackageId>
<Version>0.1.2</Version> <Version>0.1.0</Version>
<Authors>H. Utku Maden</Authors> <Authors>H. Utku Maden</Authors>
<Company>ReFuel</Company> <Company>ReFuel</Company>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile> <PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageIcon>images\icon.png</PackageIcon> <PackageIcon>images\icon.png</PackageIcon>
<PackageProjectUrl>https://refuel.mixedup.dev/docs/ReFile.html</PackageProjectUrl> <PackageProjectUrl>https://refuel.mixedup.dev/docs/ReMime.html</PackageProjectUrl>
<RepositoryUrl>https://git.mixedup.dev/ReFuel/ReMime</RepositoryUrl> <RepositoryUrl>https://git.mixedup.dev/ReFuel/ReMime</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageTags>detection; detector; type; file; mime; mime-type; media; media-type; analysis; tool; refile</PackageTags> <PackageTags>detection; detector; type; file; mime; mime-type; media; media-type; analysis; tool; refile</PackageTags>
@ -34,7 +34,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ReFuel.ReMime" Version="0.1.1" /> <PackageReference Include="ReFuel.ReMime" Version="0.1.0" />
<Content Include="README.md" Pack="true" PackagePath="/" /> <Content Include="README.md" Pack="true" PackagePath="/" />
<Content Include="../LICENSE.md" Pack="true" PackagePath="/" /> <Content Include="../LICENSE.md" Pack="true" PackagePath="/" />

View File

@ -3,9 +3,21 @@ using ReMime.Platform;
namespace ReMime.Tests namespace ReMime.Tests
{ {
[TestClass] public abstract class MediaTypesByExtension<T> where T : IMediaTypeResolver, new()
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)[] { readonly (string extension, string type)[] ExampleMimeTypes = new (string, string)[] {
("png", "image/png"), ("png", "image/png"),
("gif", "image/gif"), ("gif", "image/gif"),
@ -22,10 +34,44 @@ namespace ReMime.Tests
{ {
foreach (var(ext, type) in ExampleMimeTypes) foreach (var(ext, type) in ExampleMimeTypes)
{ {
Assert.IsTrue(MediaTypeResolver.TryResolve(ext, out MediaType? result)); Assert.IsTrue(CIT.TryResolve(ext, out MediaType? result));
Assert.AreEqual(result!.FullType, type); Assert.AreEqual(result!.FullType, type);
Assert.IsTrue(result.Extensions.Contains(ext)); Assert.IsTrue(result.Extensions.Contains(ext));
} }
} }
} }
[TestClass]
public class UnixMediaTypes : MediaTypesByExtension<UnixMediaTypeResolver>
{
[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<Win32MediaTypeResolver>
{
[TestInitialize]
public override void Initialize()
{
if (OperatingSystem.IsWindows())
{
base.Initialize();
}
else
{
Assert.Inconclusive("Cannot test this in this platform.");
}
}
}
} }

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
namespace ReMime.ContentResolvers
{
public interface IMagicValueResolver
{
void AddMagicValue(MagicValueMediaType value);
void AddMagicValues(IEnumerable<MagicValueMediaType> values)
{
foreach (MagicValueMediaType value in values)
{
AddMagicValue(value);
}
}
}
}

View File

@ -1,86 +0,0 @@
using System;
namespace ReMime.ContentResolvers
{
/* You've heard of regular expressions, now prepare for magical expressions :sparkles:. */
/// <summary>
/// Bit pattern detecting state machine inspired by text regular expressions.
/// </summary>
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<byte> bytes)
{
byte current;
int needle;
int haystack;
int bits;
int pi = 0;
ReadOnlySpan<char> ascii = ReadOnlySpan<char>.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;
}
}
}

View File

@ -6,28 +6,30 @@ using System.Linq;
namespace ReMime.ContentResolvers namespace ReMime.ContentResolvers
{ {
public record MagicValueMediaType(MediaType MediaType, MagicValue[] MagicValues) public record MagicValueMediaType(MediaType MediaType, MagicValue[] MagicValues, string[] Extensions);
{
public IReadOnlyCollection<string> Extensions { get; } = MediaType.Extensions;
}
public class MagicContentResolver : IMediaContentResolver, IMagicValueResolver public class MagicContentResolver : IMediaContentResolver
{ {
private readonly List<MediaType> _mediaTypes = new List<MediaType>(); private readonly List<MediaType> _mediaTypes = new List<MediaType>();
private readonly Dictionary<string, MediaType> _extensions = new Dictionary<string, MediaType>(); private readonly Dictionary<string, MediaType> _extensions = new Dictionary<string, MediaType>();
private readonly Tree _tree = new Tree(); private readonly Tree _tree = new Tree();
private int _maxBytes = 0; private int _maxBytes = 0;
private MagicContentResolver() public MagicContentResolver(IEnumerable<MagicValueMediaType> values) : this()
{ {
IEnumerable<MagicValueMediaType> entries; AddMagicValues(values);
}
public MagicContentResolver()
{
List<MagicValueDatabaseEntry> entries;
using (Stream str = typeof(MagicContentResolver).Assembly.GetManifestResourceStream("ReMime.ContentResolvers.database.jsonc")!) using (Stream str = typeof(MagicContentResolver).Assembly.GetManifestResourceStream("ReMime.ContentResolvers.database.jsonc")!)
{ {
entries = MagicValueDatabaseEntry.GetEntries(str); entries = MagicValueDatabaseEntry.GetEntries(str);
} }
AddMagicValues(entries); AddMagicValues(entries.Select(x => (MagicValueMediaType)x));
} }
public IReadOnlyCollection<MediaType> MediaTypes => _mediaTypes.AsReadOnly(); public IReadOnlyCollection<MediaType> MediaTypes => _mediaTypes.AsReadOnly();
@ -84,8 +86,6 @@ namespace ReMime.ContentResolvers
return _extensions.TryGetValue(extension, out mediaType); return _extensions.TryGetValue(extension, out mediaType);
} }
public static MagicContentResolver Instance { get; } = new MagicContentResolver();
private class Tree private class Tree
{ {
public MagicValueMediaType? Node { get; private set; } public MagicValueMediaType? Node { get; private set; }

View File

@ -18,24 +18,24 @@ namespace ReMime.ContentResolvers
[JsonPropertyName("extensions")] [JsonPropertyName("extensions")]
public List<string> Extensions { get; set; } = new List<string>(); public List<string> Extensions { get; set; } = new List<string>();
public static IEnumerable<MagicValueMediaType> GetEntries(Stream str) public static List<MagicValueDatabaseEntry> GetEntries(Stream str)
{ {
return JsonSerializer.Deserialize<List<MagicValueDatabaseEntry>>(str, new JsonSerializerOptions() return JsonSerializer.Deserialize<List<MagicValueDatabaseEntry>>(str, new JsonSerializerOptions()
{ {
AllowTrailingCommas = true, AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip ReadCommentHandling = JsonCommentHandling.Skip
})?.Select(x => (MagicValueMediaType)x) }) ?? throw new Exception();
?? throw new Exception();
} }
public static explicit operator MagicValueMediaType(MagicValueDatabaseEntry entry) public static explicit operator MagicValueMediaType(MagicValueDatabaseEntry entry)
{ {
return new MagicValueMediaType( return new MagicValueMediaType(
new MediaType(entry.Type, entry.Extensions), new MediaType(entry.Type),
entry.Magic.Select(x => (MagicValue.TryParse(x, out var value), value)) entry.Magic.Select(x => (MagicValue.TryParse(x, out var value), value))
.Where(x => x.Item1) .Where(x => x.Item1)
.Select(x => (MagicValue)x.value!) .Select(x => (MagicValue)x.value!)
.ToArray() .ToArray(),
entry.Extensions.ToArray()
); );
} }
} }

View File

@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
namespace ReMime.ContentResolvers namespace ReMime.ContentResolvers
{ {
public class RiffResolver : IMediaContentResolver, IMagicValueResolver public class RiffResolver : IMediaTypeResolver, IMediaContentResolver
{ {
public readonly List<MediaType> _mediaTypes = new List<MediaType>(); public readonly List<MediaType> _mediaTypes = new List<MediaType>();
public readonly Dictionary<string, MediaType> _extensions = new Dictionary<string, MediaType>(); public readonly Dictionary<string, MediaType> _extensions = new Dictionary<string, MediaType>();
@ -15,28 +15,29 @@ namespace ReMime.ContentResolvers
public IReadOnlyCollection<MediaType> MediaTypes { get; } public IReadOnlyCollection<MediaType> MediaTypes { get; }
private RiffResolver() public RiffResolver()
{ {
MediaTypes = _mediaTypes.AsReadOnly(); MediaTypes = _mediaTypes.AsReadOnly();
IEnumerable<MagicValueMediaType> entries; List<MagicValueDatabaseEntry> entries;
using (Stream str = typeof(MagicContentResolver).Assembly.GetManifestResourceStream("ReMime.ContentResolvers.riff.jsonc")!) using (Stream str = typeof(MagicContentResolver).Assembly.GetManifestResourceStream("ReMime.ContentResolvers.riff.jsonc")!)
{ {
entries = MagicValueDatabaseEntry.GetEntries(str); entries = MagicValueDatabaseEntry.GetEntries(str);
} }
AddMagicValues(entries);
}
public void AddMagicValues(IEnumerable<MagicValueMediaType> entries)
{
foreach (var entry in entries) foreach (var entry in entries)
{ {
AddMagicValue(entry); AddRiffType((MagicValueMediaType)entry);
} }
} }
public RiffResolver(IEnumerable<MagicValueMediaType> values) : this()
{
foreach (MagicValueMediaType value in values)
AddRiffType(value);
}
public bool TryResolve(Stream str, [NotNullWhen(true)] out MediaType? mediaType) public bool TryResolve(Stream str, [NotNullWhen(true)] out MediaType? mediaType)
{ {
Span<byte> content = stackalloc byte[Unsafe.SizeOf<RiffChunk>()]; Span<byte> content = stackalloc byte[Unsafe.SizeOf<RiffChunk>()];
@ -68,7 +69,7 @@ namespace ReMime.ContentResolvers
/// Add a RIFF sub-magic value to this resolver. /// Add a RIFF sub-magic value to this resolver.
/// </summary> /// </summary>
/// <param name="type"></param> /// <param name="type"></param>
public void AddMagicValue(MagicValueMediaType type) public void AddRiffType(MagicValueMediaType type)
{ {
if (type.MagicValues.Length == 0) if (type.MagicValues.Length == 0)
throw new ArgumentException("Expected at least one media type."); throw new ArgumentException("Expected at least one media type.");
@ -91,8 +92,6 @@ namespace ReMime.ContentResolvers
} }
} }
public static RiffResolver Instance { get; } = new RiffResolver();
[StructLayout(LayoutKind.Auto, Size = 12)] [StructLayout(LayoutKind.Auto, Size = 12)]
private struct RiffChunk private struct RiffChunk
{ {

View File

@ -50,13 +50,22 @@ namespace ReMime
static MediaTypeResolver() static MediaTypeResolver()
{ {
AddResolver(RiffResolver.Instance, 1000); AddResolver(new RiffResolver(), 9997);
AddResolver(MagicContentResolver.Instance, 1001); AddResolver(new MagicContentResolver(), 9998);
if (Win32MediaTypeResolver.Instance != null) if (OperatingSystem.IsWindows())
AddResolver(Win32MediaTypeResolver.Instance, 1002); {
if (UnixMediaTypeResolver.Instance != null) AddResolver(new Win32MediaTypeResolver());
AddResolver(UnixMediaTypeResolver.Instance, 1002); }
else if (OperatingSystem.IsLinux())
{
AddResolver(new UnixMediaTypeResolver());
// TODO: add freedesktop mime type database.
}
else if (OperatingSystem.IsMacOS())
{
AddResolver(new UnixMediaTypeResolver()); //?
}
} }
/// <summary> /// <summary>

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics;
using System.IO; using System.IO;
namespace ReMime.Platform namespace ReMime.Platform
@ -14,7 +13,7 @@ namespace ReMime.Platform
private readonly Dictionary<string, MediaType> _extensionsMap = new Dictionary<string, MediaType>(); private readonly Dictionary<string, MediaType> _extensionsMap = new Dictionary<string, MediaType>();
public IReadOnlyCollection<MediaType> MediaTypes { get; } public IReadOnlyCollection<MediaType> MediaTypes { get; }
private UnixMediaTypeResolver() public UnixMediaTypeResolver()
{ {
{ {
bool valid = OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD(); bool valid = OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD();
@ -54,20 +53,6 @@ namespace ReMime.Platform
return _extensionsMap.TryGetValue(extension, out mediaType); 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 readonly char[] s_delimeters = new char[] { '\t', ' ' };
private static void DigestMimeDatabase(TextReader reader, List<MediaType> types) private static void DigestMimeDatabase(TextReader reader, List<MediaType> types)

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.Win32; using Microsoft.Win32;
@ -14,7 +13,7 @@ namespace ReMime.Platform
private readonly Dictionary<string, MediaType> _extensionsMap = new Dictionary<string, MediaType>(); private readonly Dictionary<string, MediaType> _extensionsMap = new Dictionary<string, MediaType>();
public IReadOnlyCollection<MediaType> MediaTypes { get; } public IReadOnlyCollection<MediaType> MediaTypes { get; }
private Win32MediaTypeResolver() public Win32MediaTypeResolver()
{ {
if (!OperatingSystem.IsWindows()) if (!OperatingSystem.IsWindows())
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
@ -58,19 +57,5 @@ namespace ReMime.Platform
{ {
return _extensionsMap.TryGetValue(extension, out mediaType); return _extensionsMap.TryGetValue(extension, out mediaType);
} }
public static Win32MediaTypeResolver? Instance { get; } = null;
static Win32MediaTypeResolver()
{
try
{
Instance = new Win32MediaTypeResolver();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
} }
} }

View File

@ -8,7 +8,7 @@
<!--NuGet--> <!--NuGet-->
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageId>ReFuel.ReMime</PackageId> <PackageId>ReFuel.ReMime</PackageId>
<Version>0.1.1</Version> <Version>0.1.0</Version>
<Authors>H. Utku Maden</Authors> <Authors>H. Utku Maden</Authors>
<Company>ReFuel</Company> <Company>ReFuel</Company>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>