score:21

Accepted answer

update: @getox has taken this code and wrapped it in a nuget package for .net core! see below, cheers!!

so i was wondering if anyone could point me to another method with more mime types, or alternatively another method / class were i would be able to include the mime types i see fit.

i use a hybrid of winista and urlmon to detect the real format of files uploaded..

winista mime detection

say someone renames a exe with a jpg extension, you can still determine the "real" file format using binary analysis. it doesn't detect swf's or flv's but does pretty much every other well known format + you can get a hex editor and add more files it can detect.

file magic

winista detects the real mime type using an xml file "mime-type.xml" that contains information about file types and the signatures used to identify the content type.eg:

<!--
 !   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>

when winista fail's to detect the real file format, i've resorted back to the urlmon method:

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";
    }
}
}

from inside the winista method, i fall back on the urlmon here:

   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 link to the winista utility from netomatix. afaik they found some "mime reader utility classes in open source nutch crawler system" and they did a c# rewrite in the early 2000's.

i've hosted my mimedetect project using winista and the urlmon fall back here (please contribute new file types using a hex editor): https://github.com/meaningoflights/mimedetect

you could also use the registry method or .net 4.5 method mentioned in this post linked to by paul zahra, but winista is the best imho.

enjoy knowing files on your systems are what they claim to be and not laden with malware!


update:

for desktop applications you may find the windowsapicodepack works better:

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);
}

score:2

there are multiple possible solutions in this so post which will at the very least give you some food for thought.

it seems that the only real way to do it is to read it in binary and then do a comparison, whether the mime types are declared hard-coded in some fashion or you rely on the machines own available mime types / registry.

score:2

just found filesignatures. it is actually a good alternative, that runs fine also on linux-targeted applications.

context

urlmon.dll is not suitable for linux - therefore ain't going to work for multi-platform applications. i found this article in microsoft docs. it makes a reference to a file signature database which is a quite good reference for file types (518 by the time i'm writing this).

digging a little more i found this pretty good project: filesignatures nuget here. it is also quite extensible, so you can, for example, get all the types you need from filesignatures.net and create your own type models.

usage

you can either check for any defined type

var format = inspector.determinefileformat(stream);

if(format is pdf) {
  // just matches pdf
}

if(format is officeopenxml) {
  // matches word, excel, powerpoint
}

if(format is image) {
  // matches any image format
}

or use some of the metadata it brings, based on matched file type

var fileformat = _fileformatinspector.determinefileformat(stream);
var mime = fileformat?.mediatype;

extensibility

you can define any number of types that inherits from fileformat and configure a fileformatlocator to load them when needed

var assembly = typeof(customfileformat).gettypeinfo().assembly;

// just the formats defined in the assembly containing customfileformat
var customformats = fileformatlocator.getformats(assembly);

// formats defined in the assembly and all the defaults
var allformats = fileformatlocator.getformats(assembly, true);

more details in the project's github

score:3

after few hours of looking for elastic solution. i took @jeremythompson solution, adapted it to frameworks .net core/.net 4.5 and put it into nuget package.

   //init
   var mimetypes = new mimetypes();

   //usage by filepath
   var mimetype1 = mimetypes.getmimetypefromfile(filepath);

   //usage by bytearray
   var mimetype2 = mimetypes.getmimetypefromfile(bytes);

Related Query

More Query from same tag