Add first magic value detector.
This commit is contained in:
parent
1060d7d73c
commit
e6f2a74819
58
ReMime/ContentResolvers/Image/ImageMagicValues.cs
Normal file
58
ReMime/ContentResolvers/Image/ImageMagicValues.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReMime.ContentResolvers.Image
|
||||
{
|
||||
public static class ImageMagicValues
|
||||
{
|
||||
private static readonly MediaType Tiff = new MediaType("image/tiff", new string[] { "nif", "tif", "tiff"});
|
||||
private static readonly MediaType Jpeg = new MediaType("image/jpeg", new string[] { "jpg", "jpeg" });
|
||||
|
||||
public static readonly IReadOnlyList<MagicValueMediaType> List = new List<MagicValueMediaType>() {
|
||||
new MagicValueMediaType(new MagicValue("BM"), new MediaType("image/bmp")),
|
||||
new MagicValueMediaType(new MagicValue("GIF8"), new MediaType("image/gif")),
|
||||
new MagicValueMediaType(new MagicValue("IIN1"), Tiff),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0x4d, 0x4d, 0x00, 0x2a }), Tiff),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0x49, 0x49, 0x2a, 0x00 }), Tiff),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0x89, 0x50, 0x4e, 0x47 }), new MediaType("image/png")),
|
||||
|
||||
/* Yes this is how we are doing JPEG, I don't want to modify my thing to allow for magic values to be defined in terms of bits. */
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe0 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe1 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe2 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe3 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe4 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe5 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe6 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe7 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe8 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xe9 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xea }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xeb }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xec }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xed }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xee }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xef }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf0 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf1 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf2 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf3 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf4 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf5 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf6 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf7 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf8 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xf9 }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xfa }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xfb }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xfc }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xfd }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xfe }), Jpeg),
|
||||
new MagicValueMediaType(new MagicValue(new byte[] { 0xff, 0xd8, 0xff, 0xff }), Jpeg),
|
||||
}.AsReadOnly();
|
||||
|
||||
public static void AddToMagicResolver(MagicContentResolver resolver)
|
||||
{
|
||||
resolver.AddMagicValues(List);
|
||||
}
|
||||
}
|
||||
}
|
132
ReMime/ContentResolvers/MagicResolver.cs
Normal file
132
ReMime/ContentResolvers/MagicResolver.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace ReMime.ContentResolvers
|
||||
{
|
||||
public record MagicValueMediaType(MagicValue Magic, MediaType MediaType);
|
||||
|
||||
public class MagicContentResolver : IMediaContentResolver
|
||||
{
|
||||
private readonly List<MediaType> _mediaTypes = new List<MediaType>();
|
||||
private readonly Dictionary<string, MediaType> _extensions = new Dictionary<string, MediaType>();
|
||||
private readonly Tree _tree = new Tree();
|
||||
private int _maxBytes = 0;
|
||||
|
||||
public MagicContentResolver(IEnumerable<MagicValueMediaType> values) : this()
|
||||
{
|
||||
AddMagicValues(values);
|
||||
}
|
||||
|
||||
public MagicContentResolver()
|
||||
{
|
||||
Image.ImageMagicValues.AddToMagicResolver(this);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<MediaType> MediaTypes => _mediaTypes.AsReadOnly();
|
||||
|
||||
public void AddMagicValue(MagicValueMediaType value)
|
||||
{
|
||||
_maxBytes = Math.Max(_maxBytes, value.Magic.Value.Length);
|
||||
_mediaTypes.Add(value.MediaType);
|
||||
_tree.Add(value);
|
||||
|
||||
foreach (string extension in value.MediaType.Extensions)
|
||||
{
|
||||
_extensions[extension] = value.MediaType;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddMagicValues(IEnumerable<MagicValueMediaType> values)
|
||||
{
|
||||
foreach (MagicValueMediaType value in values)
|
||||
{
|
||||
AddMagicValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryResolve(Stream str, [NotNullWhen(true)] out MediaType? mediaType)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[_maxBytes];
|
||||
str.Read(bytes);
|
||||
return TryResolve(bytes, out mediaType);
|
||||
}
|
||||
|
||||
public bool TryResolve(ReadOnlySpan<byte> content, [NotNullWhen(true)] out MediaType? mediaType)
|
||||
{
|
||||
MagicValueMediaType? type = _tree[content];
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
mediaType = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaType = type.MediaType;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryResolve(string extension, out MediaType? mediaType)
|
||||
{
|
||||
return _extensions.TryGetValue(extension, out mediaType);
|
||||
}
|
||||
|
||||
private class Tree
|
||||
{
|
||||
public MagicValueMediaType? Node { get; private set; }
|
||||
public Dictionary<byte, Tree>? Children { get; private set; }
|
||||
|
||||
public MagicValueMediaType? this[ReadOnlySpan<byte> bytes]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (bytes.Length == 0)
|
||||
return Node;
|
||||
|
||||
if (Children == null)
|
||||
return null;
|
||||
|
||||
byte b = bytes[0];
|
||||
|
||||
if (!Children.TryGetValue(b, out Tree? subtree))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return subtree[bytes.Slice(1)];
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInternal(MagicValueMediaType magic, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
if (bytes.Length == 0)
|
||||
{
|
||||
Node = magic;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Children == null)
|
||||
{
|
||||
Children = new Dictionary<byte, Tree>();
|
||||
}
|
||||
|
||||
if (!Children.TryGetValue(bytes[0], out Tree? tree))
|
||||
{
|
||||
tree = new Tree();
|
||||
Children[bytes[0]] = tree;
|
||||
}
|
||||
|
||||
tree.AddInternal(magic, bytes.Slice(1));
|
||||
}
|
||||
|
||||
public void Add(MagicValueMediaType magic)
|
||||
{
|
||||
ReadOnlySpan<byte> bytes = magic.Magic.Value;
|
||||
AddInternal(magic, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
ReMime/ContentResolvers/MagicValue.cs
Normal file
41
ReMime/ContentResolvers/MagicValue.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace ReMime.ContentResolvers
|
||||
{
|
||||
public record struct MagicValue(byte[] Value)
|
||||
{
|
||||
public MagicValue(int value) : this(BitConverter.GetBytes(value)) { }
|
||||
public MagicValue(short value) : this(BitConverter.GetBytes(value)) { }
|
||||
public MagicValue(string value, Encoding? encoding = null)
|
||||
: this((encoding ?? Encoding.ASCII).GetBytes(value)) { }
|
||||
public MagicValue(ReadOnlySpan<byte> bytes) : this(bytes.ToArray()) { }
|
||||
|
||||
public bool Matches(ReadOnlySpan<byte> haystack)
|
||||
{
|
||||
for (int i = 0; i < haystack.Length && i < Value.Length; i++)
|
||||
{
|
||||
if (haystack[i] != Value[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// Uses the FVN-1A algorithm in 32-bit mode.
|
||||
const int PRIME = 0x01000193;
|
||||
const int BASIS = unchecked((int)0x811c9dc5);
|
||||
|
||||
int hash = BASIS;
|
||||
for (int i = 0; i < Value.Length; i++)
|
||||
{
|
||||
hash ^= Value[i];
|
||||
hash *= PRIME;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ReMime.ContentResolvers;
|
||||
using ReMime.Platform;
|
||||
|
||||
namespace ReMime
|
||||
@ -40,6 +41,8 @@ namespace ReMime
|
||||
|
||||
static MediaTypeResolver()
|
||||
{
|
||||
AddResolver(new MagicContentResolver(), 9998);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
AddResolver(new Win32MediaTypeResolver());
|
||||
|
Loading…
Reference in New Issue
Block a user