Web 系統有時會需要使用者上傳檔案,我們通常會去卡檔案的附檔名,例如只限制圖檔可以上傳,但是如果將有害的 exe 改成了 jpg ,一樣可以上傳到 Server 上。
目前找到的方式大多是透過檔案的 signatures (俗稱的 magic numbers) 來判斷,詳細可以參考「List of file signatures」。
所以如果只是要判斷是否為圖檔的話,可參考「Validate uploaded image content in ASP.NET」,判斷檔案前幾個 Byte 來判斷它。
但是如果我們還要判斷其他檔案呢? 例如 PDF , 文字檔 等等的要怎麼辦呢?
這時可以參考「Validating MIME of a File Before Uploading in ASP.Net」這篇文章,使用 urlmon.dll 中的 FindMimeFromData 這個 Method,
FindMimeFromData 是依檔案前 256 bytes 來判斷檔案的 MIME Type,它所對應的 MIME Type 可以參考 MIME Type Detection in Windows Internet Explorer 。
所以我們建立一個 ASPX 網頁來做簡單的測試,
<asp:FileUpload ID="FileUpload1″ runat="server" />
<asp:Button ID="btnUload" runat="server" Text="Upload" <asp:Button ID="btnUload" runat="server" Text="Upload" OnClick="btnUload_OnClick"/>
<asp:Label ID="lblMessage" runat="server" Text="mime type"></asp:Label>
ASPX.CS 中
1.加入 namespace
using System.Runtime.InteropServices;
2.在 Page Class 中加入使用 FindMimeFromData 的宣告
[DllImport("urlmon.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false)]
static extern int FindMimeFromData(IntPtr pBC,
[MarshalAs(UnmanagedType.LPWStr)] string pwzUrl,
[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.I1, SizeParamIndex=3)]
byte[] pBuffer,
int cbSize,
[MarshalAs(UnmanagedType.LPWStr)] string pwzMimeProposed,
int dwMimeFlags,
out IntPtr ppwzMimeOut,
int dwReserved);
3.在 Upload 的 Button 中呼叫 FindMimeFromData 來判斷上傳檔案的 MIME Type
if (FileUpload1.HasFile){
var byteSize = 255;
var bbyteSize = byteSize + 1;
var ambiguousMimeType = “application/octet-stream";
var file = FileUpload1.PostedFile;
var fileStreamLength = file.ContentLength;
var buffer = new byte[bbyteSize];
var fileStreamIsLessThanBByteSize = fileStreamLength < byteSize; file.InputStream.Read(buffer, 0, fileStreamIsLessThanBByteSize ? fileStreamLength : bbyteSize);
IntPtr mimeTypePtr;
FindMimeFromData(IntPtr.Zero, null, buffer, bbyteSize, null, 0, out mimeTypePtr, 0);
var mime = Marshal.PtrToStringUni(mimeTypePtr);
if (mime != null && mime.Equals(ambiguousMimeType) && fileStreamIsLessThanBByteSize && fileStreamLength > 0) {
//txt少於 256 會判斷成 octet-stream
var currentBuffer = buffer.Take(fileStreamLength);
var repeatCount = (int)(bbyteSize / fileStreamLength) + 1;
var bBuferLit = new List<byte>();
while (repeatCount > 0) {
bBuferLit.AddRange(currentBuffer);
–repeatCount;
}
var bbuffer = bBuferLit.Take(bbyteSize).ToArray();
FindMimeFromData(IntPtr.Zero, null, bbuffer, bbyteSize, null, 0, out mimeTypePtr, 0); mime = Marshal.PtrToStringUni(mimeTypePtr);
}
Marshal.FreeCoTaskMem(mimeTypePtr); lblMessage.Text = mime;
}
請注意, FindMimeFromData 的宣告,請使用本文的宣告方式,不然在 x64 OS 中程式會 Crash 的哦!
詳細可以參考「Application pool crashes with URLMoniker urlmon.dll during MIME type checking」。
如果您覺得麻煩的話,也可以從 nuget 安裝 MimeTypeDetective 這個套件試試!
註: 因為文字檔的內容如果少於 256 個 bytes 會被判斷為 application/octet-stream ,所以程式中會判斷如果內容少於 256 而且第一次判斷出來是 octet-stream 時,會將內容補足到 256 bytes,這樣子判斷就會是正確的哦!
後來測試發現 Unicode 的文字檔,它 check 也會有問題。
所以直接用以下 github 的 MimeTypeDetection.cs 就可以了哦!
https://github.com/icsharpcode/SharpDevelop/blob/master/src/Main/Base/Project/Util/MimeTypeDetection.cs
參考資料
List of file signatures
Validate uploaded image content in ASP.NET
Validating MIME of a File Before Uploading in ASP.Net
FindMimeFromData
Application pool crashes with URLMoniker urlmon.dll during MIME type checking
Using .NET, how can you find the mime type of a file based on the file signature not the extension