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

Posted

技术标签:

【中文标题】替代 Urlmon.dll 中的 FindMimeFromData 方法,该方法具有更多 MIME 类型【英文标题】:Alternative to FindMimeFromData method in Urlmon.dll one which has more MIME types 【发布时间】:2013-02-24 09:15:00 【问题描述】:

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

但是在阅读了它的文档之后,我被带到了MIME Type Detection in Windows Internet Explorer,在那里我可以找到这个方法能够识别的 MIME 类型。见list。如您所见,此方法仅限于 26 种 MIME 类型。

所以我想知道是否有人可以向我指出另一个具有更多 MIME 类型的方法,或者另一个方法/类是否能够包含我认为合适的 MIME 类型。

【问题讨论】:

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

更新:@GetoX 已采用此代码并将其包装在 .net 核心的 NuGet 包中!见下文,干杯!!

所以我想知道是否有人可以向我指出另一种方法 更多 MIME 类型,或者我会选择其他方法/类 能够包含我认为合适的 MIME 类型。

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

Winista MIME 检测

假设有人用 jpg 扩展名重命名 exe,您仍然可以使用二进制分析确定“真实”文件格式。它不检测 swf 或 flv,但几乎可以检测所有其他众所周知的格式 + 你可以获取一个十六进制编辑器并添加更多它可以检测到的文件。

文件魔术

Winista 使用 XML 文件“mime-type.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 link to the Winista utility from netomatix。 AFAIK,他们在开源 Nutch 爬虫系统中发现了一些“mime reader 实用程序类”,并且他们在 2000 年代初期进行了 C# 重写。

我已经使用 Winista 托管了我的 MimeDetect 项目,并且 URLMon 回退到这里(请使用十六进制编辑器提供新的文件类型): https://github.com/MeaningOfLights/MimeDetect

您也可以使用 Paul Zahra 链接到的 this post 中提到的 Registry 方法或 .Net 4.5 method,但 Winista 是最好的恕我直言。

享受了解您系统上的文件 他们声称是而不是laden with malware!


更新:

对于桌面应用程序,您可能会发现 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);

【讨论】:

谢谢杰里米。我喜欢你的回答,但是当谈到依赖 Urlmon.dll 中的 FindMimeFromData 方法时,我会非常小心,因为我已经读过(如果我没记错的话)它可能会返回不正确的 MIME 类型,以防适当的 MIME 类型不是' t 在 Windows 注册表的某个给定位置定义,此外,这些值也可能被篡改,这在将其发送给最终用户时会产生问题。考虑到这一点,我将只依赖类似于您在 Winista 中展示的检测方法,您在答案中展示了这种方法...... ...现在想...也许我也可以组合一个小型控制台应用程序,它可以通过正确的扩展名(和已知的 MIME)嗅探我自己制作的大量文件,并在每个具有相同扩展名的文件的前 256 个字节上搜索相似之处。这样我就可以建立一个相当大的 MIME 类型列表。好吧,我的业余时间。谢谢杰里米。 尝试了最后一个 GetFilePropertyItemTypeTextValueFromShellFile,在我的情况下,它总是返回一种 'File' 类型,而不是我想要的(如 Microsoft Word 文档),如果文件路径具有扩展名 - 那么我会得到所需的结果- 但是没有扩展这是没用的 Winista 和 WindowsAPICodePack 似乎都消失了。 :( @Zoomzoom 正如我在我的回答中提到的我将它托管在 GitHub 上作为后备:github.com/MeaningOfLights/MimeDetect - 另请注意,WindowsAPICodePack 并没有完全消失:@987654327 @ - 将来您能否在发布 cmets 之前阅读答案并进行研究。我无缘无故地浪费了 15 分钟。【参考方案2】:

经过几个小时寻找弹性解决方案。我采用了@JeremyThompson 解决方案,将其适应框架 .net core/.net 4.5 并将其放入nuget package。

   //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 security.stackexchange.com/q/81677/10505【参考方案3】:

in this SO post 有多种可能的解决方案,它们至少会给您一些思考的余地。

似乎唯一真正的方法是以二进制形式读取它,然后进行比较,无论 MIME 类型是以某种方式声明的硬编码,还是您依赖机器自己的可用 MIME 类型/注册表。

【讨论】:

+1 感谢您的链接,如果我决定构建自己的 FindMineFromData 替代方案,发布到它的答案 ***.com/a/13614746/114298 应该会很有用。【参考方案4】:

刚刚找到FileSignatures。它实际上是一个很好的替代方案,在面向 Linux 的应用程序上也能正常运行。

上下文

Urlmon.dll 不适合 Linux - 因此不适用于多平台应用程序。 我在 Microsoft Docs 中找到了this article。它引用了 File Signature Database,这是一个很好的文件类型参考(在我写这篇文章时是 518 个)。

再挖掘一下,我发现了这个非常好的项目:FileSignatures nuget here。它还具有很强的可扩展性,例如,您可以从 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);

更多详情在project's Github

【讨论】:

可以确认这适用于 docx 和 xlxs 文件 - 检测正确的 MIME 类型,这与 urlmon 的 FindMimeFromData 不同。 @Noobie3001 是的。查看/formats directory in the project source code,您将看到默认支持的格式。刚刚使用 .xlsm 进行了测试,它返回了"mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"。您还可以定义任何自定义格式,自述文件会告诉您如何做到这一点。

以上是关于替代 Urlmon.dll 中的 FindMimeFromData 方法,该方法具有更多 MIME 类型的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Urlmon.dll 中的 FindMimeFromData 函数为许多文件类型返回 MIME 类型“application/octet-stream”?

为啥 Urlmon.dll 中的 FindMimeFromData 函数为许多文件类型返回 MIME 类型“application/octet-stream”?

为啥 Urlmon.dll 中的 FindMimeFromData 函数为许多文件类型返回 MIME 类型“application/octet-stream”?

为啥 Urlmon.dll 中的 FindMimeFromData 函数为许多文件类型返回 MIME 类型“application/octet-stream”?

urlmon.dll FindMimeFromData() 在 64 位桌面/控制台上完美运行,但在 ASP.NET 上生成错误

urlmon.dll FindMimeFromData() 在 64 位桌面/控制台上完美运行,但在 ASP.NET 上生成错误