Urlmon.dll中FindMimeFromData方法的替代方法,具有更多的MIME类型。

8

通过 Windows DLL Urlmon.dll 可访问的 FindMimeFromData 方法能够确定存储在内存中的给定数据的 MIME 类型,考虑到存储该数据的字节数组的前256个字节。

然而,在阅读其文档后,我被引导到 Windows Internet Explorer 中的 MIME 类型检测,在那里我可以找到此方法能够识别的 MIME 类型。请参见列表。正如您所看到的,该方法仅限于 26 种 MIME 类型。

因此,我想知道是否有人可以指引我使用另一种具有更多 MIME 类型的方法,或者是其他方法/类,我可以在其中包含我认为合适的 MIME 类型。


我不确定这是否符合您的要求,但您可以从IIS获取主要MIME-TYPES列表。 - warrior
但是FindMimeFromData方法硬编码了26种MIME类型,我无法修改它以接受更多的MIME类型。 - Fábio Antunes
在这种情况下,您可能会找到另一种方法来完成任务。如果您可以找到要读取的数据类型的“扩展名”,则可能更有机会确定MIME类型;如果您只想通过读取二进制数据来了解MIME类型,则据我所知,您必须限制使用FindMimeFromData方法。 - warrior
1
这是一个安全敏感问题(因此有固定的26种硬编码检测)。实际上,这个MIME检测可以根据操作系统版本和各种配置进行禁用(Microsoft过去曾经遇到过真正的问题)。我不认为你会在Windows API中找到替代方案。你可以重写自己的代码。这个链接可以给你一些灵感: https://developer.mozilla.org/en-US/docs/How_Mozilla_determines_MIME_Types - Simon Mourier
2
@SimonMourier +1。这解答了为什么微软会限制自己的MIME检测。我也不相信我会找到另一个Windows API的替代方案,猜想唯一的方法就是编写自己的API。但我会等待并查看是否有人知道Microsoft API的任何替代方案。 - Fábio Antunes
4个回答

20

更新:@GetoX已将此代码封装成适用于.net core的NuGet包!请参见下文,谢谢!

因此,我想知道是否有人能够指导我使用更多MIME类型的另一种方法,或者是另一种方法/类,我可以在其中包含我认为合适的MIME类型。

我使用Winista和URLMon的混合方式来检测上传文件的真实格式。

Winista MIME检测

比如说,有人将exe重命名为jpg扩展名,你仍然可以使用二进制分析确定“真实”的文件格式。它无法检测swf或flv,但几乎可以检测出所有其他众所周知的格式,而且你还可以获取十六进制编辑器并添加更多它可以检测到的文件。

File Magic

Winista使用一个名为“mime-type.xml”的XML文件来检测真正的MIME类型,该文件包含有关文件类型以及用于识别内容类型的签名的信息。例如:

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

当Winista无法检测到真实的文件格式时,我会回到使用URLMon方法:
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";
    }
}
}

在Winista方法内部,我使用URLMon作为后备:

   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链接到netomatix的Winista实用工具。据我所知,他们在开源Nutch爬虫系统中找到了一些“mime reader utility classes”,并在2000年代初进行了C#重写。

我使用Winista和URLMon回退托管了我的MimeDetect项目(请使用十六进制编辑器贡献新的文件类型):https://github.com/MeaningOfLights/MimeDetect

您还可以使用注册表方法或.Net 4.5方法,这些方法在Paul Zahra链接的此帖子中提到,但是在我看来,Winista是最好的选择。

享受知道您系统上的文件确实是它们所声称的,而不是被恶意软件感染的乐趣!


更新:

对于桌面应用程序,您可能会发现WindowsAPICodePack更好:

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

谢谢Jeremy。我喜欢你的回答,但是当涉及到依赖于Urlmon.dll中的FindMimeFromData方法时,我会非常小心,因为我读过(如果我没记错的话)它可能返回不正确的MIME类型,如果适当的MIME类型没有在Windows注册表的某个给定位置中定义,此外这些值也可能被篡改,这在向最终用户发货时会造成问题。考虑到这一点,我只会依赖于类似于你在回答中展示的Winista的检测方法... - Fábio Antunes
1
现在正在思考...也许我可以编写一个小型控制台应用程序,它可以嗅探大量具有正确扩展名(和已知MIME类型)的自己制作的文件,并在每个相同扩展名的文件的前256个字节中搜索相似之处。这样我就可以建立一个相当大的MIME类型列表。好吧,这是我的业余时间做的事情。谢谢Jeremy。 - Fábio Antunes
尝试了最后一个GetFilePropertyItemTypeTextValueFromShellFile,在我的情况下,它总是返回一个“文件”类型,从未返回我想要的类型(如Microsoft Word文档),如果文件路径有扩展名,那么我可以得到期望的结果,但是没有扩展名就没用了。 - Nutshell
Winista和WindowsAPICodePack似乎都消失了。 :( - Zoomzoom
1
@Zoomzoom,正如我在回答中提到的那样,我将其作为备选项托管在GitHub上:https://github.com/MeaningOfLights/MimeDetect - 还要注意的是,WindowsAPICodePack并没有完全消失:https://dev59.com/TGAf5IYBdhLWcg3w_G2r#24420985 - 未来,请在发表评论之前阅读答案并进行研究。我浪费了15分钟,没有任何好处。 - Jeremy Thompson

3

在寻找弹性解决方案几个小时后,我采用了@JeremyThompson的解决方案,并将其适配到了.NET Core/.NET 4.5框架,并将其放入nuget包中。

   //init
   var mimeTypes = new MimeTypes();

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

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

我一定会去看看。不过,我很好奇为什么没有官方的包可以用于这个目的。这显然是每个“关注安全”的开发人员都需要的要求。 - brainoverflow98
就连微软也不能预料到所有的事情。文件规范变了,他们使用了UrlMon和WindowsAPICodePack。如果你搞错了,还会有很大的诉讼后果,每个开发者都可以用未知扩展名欺骗人们,比如pif。请看我在这里关于此问题的QA https://security.stackexchange.com/q/81677/10505 - Jeremy Thompson

2

在这个stackoverflow的帖子中,有多种可能的解决方案(请点击),它们至少会让您思考。

看起来唯一真正的方法是以二进制方式读取并进行比较,不管MIME类型是否以某种方式硬编码,或者您依赖于机器可用的MIME类型/注册表。


1
感谢提供的链接,其中发布的答案https://dev59.com/tXVD5IYBdhLWcg3wL4iM#13614746在我决定构建自己的FindMineFromData替代方案时应该是有用的。 - Fábio Antunes

2

我刚刚发现了FileSignatures。它是一个不错的替代品,也可以在Linux应用程序上运行。

背景

Urlmon.dll不适用于Linux,因此不能用于多平台应用程序。 我在Microsoft Docs中找到了这篇文章。它引用了一个文件签名数据库,这是一个相当不错的文件类型参考(截至我写这篇文章时共有518个)。

进一步搜索后,我发现了这个非常好的项目:FileSignatures nuget在这里。它也非常可扩展,因此您可以从filesignatures.net获取所需的所有类型,并创建自己的类型模型。

用法

您可以检查任何定义的类型。

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
}

或者使用它所提供的一些元数据,根据匹配的文件类型。
var fileFormat = _fileFormatInspector.DetermineFileFormat(stream);
var mime = fileFormat?.MediaType;

可扩展性

您可以定义任意数量的类型,这些类型都继承自FileFormat,并配置一个FileFormatLocator以在需要时加载它们。

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

更多细节请查看该项目的Github页面


可以确认这适用于docx和xlxs文件 - 与urlmon的FindMimeFromData不同,它可以检测到正确的MIME类型。 - Noobie3001
@Noobie3001 是的,它可以。查看项目源代码中的/formats目录,您将看到默认支持的格式。刚刚测试了一个.xlsm文件,返回了"mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"。您还可以定义任何自定义格式,自述文件会告诉您如何操作。 - tgarcia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接