确定文件类型:不同技术的演示






4.76/5 (8投票s)
分析用于确定文件 MIME 类型和可执行状态的各种方法。
引言
有几种常用的方法可以确定文件是否可执行,以及其他用于确定其Mime类型的方法。本文基于我的C#类库,该类库封装了其中一些方法。在演示解决方案中,我提供了Windows Forms和WPF应用程序,演示了如何使用该库。虽然本文没有专门讨论,但该库也应该适用于基于Windows的Web服务器。
背景
在本文中,我将描述主要功能及其工作原理,并在适当的地方提供一些简短的代表性代码示例。请参阅上面链接中提供的代码以获取其余的源代码。
注意:类方法返回带有通常人类可读信息的字符串值。如果您希望在生产环境中使用该库,建议从GetExecutableType
、GetBinaryType
和GetPEInformation
返回枚举值而不是字符串。
解决方案文件是在Visual Studio 2013中创建的,但应该可以在2010及更高版本中使用。如果不能,您可以更改解决方案文件顶部的版本信息以匹配您的特定版本,或者使用您的Visual Studio版本将这三个项目文件导入新解决方案。
下面列出了这三个项目文件及其最低要求。我尽量将平台需求保持在最低限度,并尽可能避免使用2.0版以上的C#功能。
- FileTypesLib 类库 (.NET 2.0)
- FileTypesDemoWinForms (.NET 2.0)
- FileTypesDemoWPF (.NET 3.5)
Win32 函数
名为NativeMethods的类包含几个Win32函数调用及其相关的结构和枚举。我已对所有方法采用最佳实践,包括数据类型、调用约定和函数激活。在适当的情况下,例如SHGetFileInfo函数,演示应用程序中提供了线程和非线程示例。
一、SHGetFileInfo
收集有关文件系统对象的信息。需要一个SHFILEINFO结构和标志来告诉函数所需的操作。请注意在DllImport
属性中使用CharSet.Auto
。这使得该函数可以在Ansi或Unicode环境中工作。
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SHGetFileInfo(
[MarshalAs(UnmanagedType.LPWStr)]
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbSizeFileInfo,
uint uFlags);
二、GetBinaryType
检测文件是否可执行。不幸的是,此函数不可靠。在64位计算机上调用时,它不能正确识别32位和64位应用程序。对于64位应用程序,该函数返回false并保留默认的二进制类型值(SCS_32BIT_BINARY
),而对于32位应用程序,它返回true但将二进制类型设置为64位。请参阅演示图像以了解这种异常行为的示例。
三、FindMimeFromData
通过检查提供的数据的前256个字节来检测文件是否是26种不同Mime类型中的一种。此函数有时与其他Mime检测技术结合使用,如FileTypes类库的GetContentType
方法所示,以允许检测更多类型。
FileTypes 类库方法
FileTypes类库包含几种方法,可用于确定文件的Mime类型或文件是否可执行。下面方法描述中讨论了每种方法的各种限制。
一、GetShellInfo
使用shell32.dll的SHGetFileInfo
函数获取有关提交文件信息。SHGetFileInfo
返回文件类型描述。在文件资源管理器(以前称为Windows资源管理器)中,这是在“类型”列下看到的字符串值,例如“应用程序”。
GetShellInfo
库方法创建一个SHFILEINFO结构,设置必要的标志,然后从NativeMethods调用SHGetFileInfo
。友好的类型名称作为字符串返回。
注意:由于设置了SHGIF_USEFILEATTRIBUTES标志,因此不读取实际文件。相反,根据文件扩展名和调用函数时提供的属性,检索如果文件存在则可用的值。这当然比读取实际文件更快。
public string GetShellInfo(string fileName)
{
if (String.IsNullOrEmpty(fileName)) return "Invalid file name";
// Create the SHFILEINFO structure and assign strings to the string pointers
SHFILEINFO info = new SHFILEINFO();
info.szDisplayName = String.Empty;
info.szTypeName = String.Empty;
// Set the file attribute flags
uint dwFileAttributes = (uint)SHGetFileInfoAttributes.FILE_ATTRIBUTE_NORMAL;
// Set the operation flags for retrieving the desired information
uint uFlags = (uint)(SHGetFileInfoFlags.SHGFI_USEFILEATTRIBUTES |
SHGetFileInfoFlags.SHGFI_DISPLAYNAME |
SHGetFileInfoFlags.SHGFI_TYPENAME);
// Call the Win32 function
NativeMethods.SHGetFileInfo(fileName, dwFileAttributes, ref info, (uint)Marshal.SizeOf(info), uFlags);
// Return the type name as a string
return info.szTypeName;
}
二、GetExecutableType
同样使用shell32.dll的SHGetFileInfo
函数,但设置了SHGFI_EXETYPE
标志,以便它将确定可执行文件的类型。此检测相当原始,仅限于DOS、NT和OS2(这也是早期的Windows)。我向枚举添加了两个值,用于指示文件何时是WinNT控制台应用程序以及文件何时根本不是可执行文件。
此外,当检测到的文件类型为WinNT可执行文件时,返回值包含操作系统版本。
三、GetBinaryType
请参阅上面Win32方法下的讨论。我向枚举添加了一个值,以便将不可执行文件报告为不可执行文件。
四、GetContentType
结合了对包含数百种Mime类型的嵌入式资源文本文件的字典查找,以及对urlmon.dll函数FindMimeFromData
的调用,以确定文件的Mime类型。此代码是Tim Schmelter在stackoverflow上的代码的增强版本。我将Mime类型列表从源代码中的硬编码更改为以名称-值对(例如:afl,video/animaflex)的形式放入嵌入式资源文本文件中,以便可以轻松更新类型。我还在这里和那里稍微调整了代码。
五、GetMimeType
引用一组只读字节数组,其中包含各种文件类型文件头中包含的“魔术”标识字节,以确定Mime类型。此方法改编自ROFLwTIME在stackoverflow上的帖子。该例程检测18种不同的Mime类型,但可以通过添加额外的魔术字节和Mime类型进行扩展,尽管我建议从硬编码转向某种查找技术。
六、GetPEInformation
使用Mono中略微修改的PEReader
类来收集提交检查的可执行文件的相关信息。该方法从文件头读取可移植可执行(PE)字节,并提供各种信息。如上面的演示图像所示,这些信息包括文件运行的机器类型、文件是可执行文件还是DLL、是否是32位应用程序以及文件创建日期时间等。
我的修改非常小,主要涉及防止在传入非可执行文件时崩溃。FileTypes中的GetPEInformation
方法使用一个switch和一些if语句将PEReader
提供的数据解析为人类可读的形式。
关注点
根据SHGetFileInfo
文档,"您应该从后台线程调用此函数。否则可能导致UI停止响应。" 由于这个免责声明,我提供了线程和非线程版本。例如,GetShellInfo
和GetShellInfoThread
。WPF演示中使用线程版本,而Windows Forms演示中使用非线程版本。对于易于访问的本地文件,非线程版本应该没问题,但对于网络文件或其他访问缓慢的文件,请使用线程版本(并且可能添加更强大的错误检查)。
演示表单上显示的图标是通过调用带有适当标志的SHGetFileInfo
获得的。如前所述,提供了线程和非线程版本。
将Windows Forms应用程序转换为WPF应用程序并不特别困难。对于我的演示应用程序,主要变化在于图像的处理方式和控件调用方式。以下是简要示例,说明了将图标加载到WinForms PictureBox和WPF Image之间的区别,以及如何调用线程化的WinForms和WPF控件。
WinForms PictureBox(非线程)
IntPtr handle = fileUtilities.GetLargeIcon(file);
if (handle != IntPtr.Zero)
{
// Convert the icon to a bitmap and load it to the control
pictureBox1.Image = Bitmap.FromHicon(handle);
}
WPF Image(非线程)
IntPtr handle = fileUtilities.GetLargeIcon(file);
if (handle != IntPtr.Zero)
{
// Convert the icon to a bitmap and load it to the control
image1.Source = Imaging.CreateBitmapSourceFromHIcon(
handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
WinForms TextBox 线程回调
if (textBox1.InvokeRequired)
{
SetTextBoxCallback d = new SetTextBoxCallback(TextBoxCallback);
this.Invoke(d, new object[] { sender, eventArgs });
}
else { textBox1.Text = eventArgs.SHFileInfo; }
WPF TextBox 线程回调
if (!textBox1.Dispatcher.CheckAccess())
{
SetTextBoxCallback d = new SetTextBoxCallback(TextBoxCallback);
Dispatcher.Invoke(d, new object[] { sender, eventArgs });
}
else { textBox1.Text = eventArgs.SHFileInfo; }
历史
1.0 版于 2014/12/1 发布。CPOL 许可证。