Alternativ till metoden FindMimeFromData i Urlmon.dll en som har fler MIME-typer

Alternativ till metoden FindMimeFromData i Urlmon.dll en som har fler MIME-typer

UPPDATERING:@GetoX har tagit den här koden och packat in den i ett NuGet-paket för .net core! Se nedan, heja!!

Jag använder en hybrid av Winista och URLMon för att upptäcka den verkligt format för uppladdade filer.

Winista MIME-detektering

Säg att någon byter namn på en exe med filtillägget jpg, du kan fortfarande bestämma det "riktiga" filformatet med hjälp av binär analys. Den upptäcker inte swf- eller flv-filer men gör i stort sett alla andra välkända format + du kan skaffa en hex-redigerare och lägga till fler filer som den kan upptäcka.

File Magic

Winista upptäcker den verkliga MIME-typen med hjälp av en XML-fil "mime-type.xml" som innehåller information om filtyper och signaturerna som används för att identifiera innehållstypen. t.ex.:

<!--
 !   Audio primary type
 ! -->

<mime-type name="audio/basic"
           description="uLaw/AU Audio File">
    <ext>au</ext><ext>snd</ext>
    <magic offset="0" type="byte" value="2e736e64000000"/>
</mime-type>

<mime-type name="audio/midi"
           description="Musical Instrument Digital Interface MIDI-sequention Sound">
    <ext>mid</ext><ext>midi</ext><ext>kar</ext>
    <magic offset="0" value="MThd"/>
</mime-type>

<mime-type name="audio/mpeg"
           description="MPEG Audio Stream, Layer III">
    <ext>mp3</ext><ext>mp2</ext><ext>mpga</ext>
    <magic offset="0" value="ID3"/>
</mime-type>

När Winista misslyckas med att upptäcka det riktiga filformatet, har jag tillgripit URLMon-metoden:

public class urlmonMimeDetect
{
    [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
    private extern static System.UInt32 FindMimeFromData(
        System.UInt32 pBC,
        [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl,
        [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
        System.UInt32 cbSize,
        [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed,
        System.UInt32 dwMimeFlags,
        out System.UInt32 ppwzMimeOut,
        System.UInt32 dwReserverd
    );

public string GetMimeFromFile(string filename)
{
    if (!File.Exists(filename))
        throw new FileNotFoundException(filename + " not found");

    byte[] buffer = new byte[256];
    using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
    {
        if (fs.Length >= 256)
            fs.Read(buffer, 0, 256);
        else
            fs.Read(buffer, 0, (int)fs.Length);
    }
    try
    {
        System.UInt32 mimetype;
        FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0);
        System.IntPtr mimeTypePtr = new IntPtr(mimetype);
        string mime = Marshal.PtrToStringUni(mimeTypePtr);
        Marshal.FreeCoTaskMem(mimeTypePtr);
        return mime;
    }
    catch (Exception e)
    {
        return "unknown/unknown";
    }
}
}

Inifrån Winista-metoden faller jag tillbaka på URLMon här:

   public MimeType GetMimeTypeFromFile(string filePath)
    {
        sbyte[] fileData = null;
        using (FileStream srcFile = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        {
            byte[] data = new byte[srcFile.Length];
            srcFile.Read(data, 0, (Int32)srcFile.Length);
            fileData = Winista.Mime.SupportUtil.ToSByteArray(data);
        }

        MimeType oMimeType = GetMimeType(fileData);
        if (oMimeType != null) return oMimeType;

        //We haven't found the file using Magic (eg a text/plain file)
        //so instead use URLMon to try and get the files format
        Winista.MimeDetect.URLMONMimeDetect.urlmonMimeDetect urlmonMimeDetect = new Winista.MimeDetect.URLMONMimeDetect.urlmonMimeDetect();
        string urlmonMimeType = urlmonMimeDetect.GetMimeFromFile(filePath);
        if (!string.IsNullOrEmpty(urlmonMimeType))
        {
            foreach (MimeType mimeType in types)
            {
                if (mimeType.Name == urlmonMimeType)
                {
                    return mimeType;
                }
            }
        }

        return oMimeType;
    }

Wayback Machine-länk till Winista-verktyget från netomatix. AFAIK hittade de några "mime reader utility classes in open source Nutch crawler system" och de gjorde en C#-omskrivning i början av 2000-talet.

Jag har varit värd för mitt MimeDetect-projekt med Winista och URLMon faller tillbaka här (vänligen bidra med nya filtyper med hjälp av en Hex-redigerare):https://github.com/MeaningOfLights/MimeDetect

Du kan också använda Registry-metoden eller .Net 4.5-metoden som nämns i det här inlägget länkat till av Paul Zahra, men Winista är den bästa IMHO.

Njut av att veta att filer på dina system är vad de påstår sig vara och inte laddad med skadlig programvara!

UPPDATERING:

För skrivbordsprogram kanske du tycker att WindowsAPICodePack fungerar bättre:

using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;

private static string GetFilePropertyItemTypeTextValueFromShellFile(string filePathWithExtension)
{
   var shellFile = ShellFile.FromFilePath(filePathWithExtension);
   var prop = shellFile.Properties.GetProperty(PItemTypeTextCanonical);
   return prop.FormatForDisplay(PropertyDescriptionFormatOptions.None);
}

Efter några timmars letande efter elastisk lösning. Jag tog @JeremyThompson-lösningen, anpassade den till frameworks .net core/.net 4.5 och la den i nuget-paketet.

   //init
   var mimeTypes = new MimeTypes();

   //usage by filepath
   var mimeType1 = mimeTypes.GetMimeTypeFromFile(filePath);

   //usage by bytearray
   var mimeType2 = mimeTypes.GetMimeTypeFromFile(bytes);

Det finns flera möjliga lösningar i det här SO-inlägget som åtminstone ger dig en tankeställare.

Det verkar som att det enda riktiga sättet att göra det på är att läsa det binärt och sedan göra en jämförelse, oavsett om MIME-typerna deklareras hårdkodade på något sätt eller om du litar på maskinens egna tillgängliga MIME-typer/registret.