Feltöltött fájl típusának meghatározása

Nem ritka feladat, hogy egy weboldalon szeretnénk megszűrni, hogy a felhasználók milyen típusú fájlokat tölthetnek fel. Fontos, hogy a felhasználó fényképe valóban egy böngészőben megjeleníthető képfájl legyen, vagy hogy a feltöltött XLSX fájl valóban Excel fájl legyen, hiszen különben nem tudjuk belőle kiolvasni az adatokat.

Erre a problémára a klasszikus megoldás a feltöltött állomány kiterjesztésének vizsgálata. Azonban egy percre se felejtsük el, hogy a vizsgált fájlnevet és vele együtt a kiterjesztést is a HTTP kérés részeként kapja meg a weboldal, azaz pontosan annyira bízhatunk meg benne, mint a HTTP kérés bármelyik más részében. Semennyire.

Egy kicsivel jobb megoldás, ha beleolvasunk a fájlba, és megkeressük azokat a bájtokat, amik egy adott fájltípusra jellemzőek. Ezeket a bájtokat hívják “file (type) signature”-nek. Gary Kessler weboldalán elég sok ilyen signature-t összegyűjtött, sőt ott további weboldalakra, adatbázisokra, eszközökre is találhatók hivatkozások.

Számunkra most az a nagy kérdés, hogy .NET kódból hogyan valósítjuk meg ezt a legegyszerűbben. Nyilván tökéletesen működő megoldás, ha beletekerünk a fájlba a megadott offsetig, onnan kiolvassuk a szükséges számú bájtot, majd összehasonlítjuk őket a várt bájtokkal. Ez jól fog működni addig, amíg valamelyik gyártó nem változtat egy kicsit a fájl formátumon, akkor ugyanis frissítetünk kell a kódunkat.

Egy kicsit “hivatalosabb” megoldás, ha a FindMimeFromData Win32 API függvényt használjuk, amit egyébként az Internet Explorer használ MIME type sniffingre. Némi P/Invoke trükközéssel meg is hívhatjuk .NET-ből, ahogy az alábbi példa is mutatja:

using System;
using System.Runtime.InteropServices;

/// <summary>
/// Helper class to detect the MIME type based on the file header signature.
/// </summary>
public static class MimeSniffer
{
    /// <summary>
    /// Internet Explorer 9. Returns image/png and image/jpeg instead of 
///
image/x-png and image/pjpeg. /// </summary> private const uint FMFD_RETURNUPDATEDIMGMIMES = 0x20; /// <summary> /// The zero (0) value for Reserved parameters. /// </summary> private const uint RESERVED = 0; /// <summary> /// The value that is returned when the MIME type cannot be recognized. /// </summary> private const string UNKNOWN = "unknown/unknown"; /// <summary> /// The return value which indicates that the operation completed successfully. /// </summary> private const uint S_OK = 0; /// <summary> /// Determines the MIME type from the data provided. /// </summary> /// <param name="pBC">A pointer to the IBindCtx interface. Can be set to NULL.</param> /// <param name="pwzUrl">A pointer to a string value that contains the URL of the data. Can be set to NULL if <paramref name="pBuffer"/> contains the data to be sniffed.</param> /// <param name="pBuffer">A pointer to the buffer that contains the data to be sniffed. Can be set to NULL if <paramref name="pwzUrl"/> contains a valid URL.</param> /// <param name="cbSize">An unsigned long integer value that contains the size of the buffer.</param> /// <param name="pwzMimeProposed">A pointer to a string value that contains the proposed MIME type. This value is authoritative if type cannot be determined from the data. If the proposed type contains a semi-colon (;) it is removed. This parameter can be set to NULL.</param> /// <param name="dwMimeFlags">The flags which modifies the behavior of the function.</param> /// <param name="ppwzMimeOut">The address of a string value that receives the suggested MIME type.</param> /// <param name="dwReserverd">Reserved. Must be set to 0.</param> /// <returns>S_OK, E_FAIL, E_INVALIDARG or E_OUTOFMEMORY.</returns> /// <remarks> /// Read more: http://msdn.microsoft.com/en-us/library/ms775107(v=vs.85).aspx /// </remarks> [DllImport( @"urlmon.dll", CharSet = CharSet.Auto )] private extern static uint FindMimeFromData( uint pBC, [MarshalAs( UnmanagedType.LPStr )] string pwzUrl, [MarshalAs( UnmanagedType.LPArray )] byte[] pBuffer, uint cbSize, [MarshalAs( UnmanagedType.LPStr )] string pwzMimeProposed, uint dwMimeFlags, out uint ppwzMimeOut, uint dwReserverd ); /// <summary> /// Returns the MIME type for the specified file header. /// </summary> /// <param name="header">The header to examine.</param> /// <returns>The MIME type or "unknown/unknown" if the type cannot be recognized.</returns> /// <remarks> /// NOTE: This method recognizes only 26 types used by IE. /// http://msdn.microsoft.com/en-us/library/ms775147(VS.85).aspx#Known_MimeTypes /// </remarks> public static string GetMime( byte[] header ) { try { uint mimetype; uint result = FindMimeFromData( 0,
null,
header,
(
uint) header.Length,
null,
FMFD_RETURNUPDATEDIMGMIMES,
out mimetype,
RESERVED );
if( result != S_OK ) { return UNKNOWN; } IntPtr mimeTypePtr = new IntPtr( mimetype ); string mime = Marshal.PtrToStringUni( mimeTypePtr ); Marshal.FreeCoTaskMem( mimeTypePtr ); return mime; } catch { return UNKNOWN; } } }

Ez a függvény megbízhatóan működik, azonban csak a 26 leggyakoribb fájltípust képes felismerni. Ami egyébként nem kevés, a fájó pont inkább az, hogy az Office formátumokat ZIP-ként ismeri fel.

Némi keresgéléssel ráakadhatunk a neten más projektekre is, például a CodePlexen található FileTypeDetective kevesebb fájl típust ismer, de pontosabban azonosítja az Office állományokat. Ráadásul a lényeget megtaláljuk a forráskódjában.

Akármelyik módszert választjuk is, ne felejtsük el, hogy bevezettünk egy külső függést a rendszerbe, ráadásul azt sem tudhatjuk, hogy mennyire időtálló a megoldásunk.

 

Technorati-címkék: ,,

2 thoughts on “Feltöltött fájl típusának meghatározása

  1. buherator

    Security szempontból hozzátenném, hogy még a fájltartalom részletes elemzése sem garantálhatja, hogy a feltöltött állomány nem tartalmaz rosszindulatú kódot. Sok képformátum pl. megengedi tetszőleges komment elhelyezését a fejlécben (+EXIF), de hallottunk olyat is, hogy egy byte-sorozat egyszerre működő Perl program és érvényes ZIP fájl egyszerre 🙂

    Reply

Leave a comment