2015-03-23 7 views
8

Come identificare doc, docx, pdf, xls e xlsx in base all'intestazione del file in C#? Non voglio fare affidamento sulle estensioni dei file né su MimeMapping.GetMimeMapping per questo poiché uno dei due può essere modificato.Come identificare doc, docx, pdf, xls e xlsx in base all'intestazione del file

So leggere l'intestazione ma non so quale combinazione di byte può dire se un file è un documento, docx, pdf, xls o xlsx. Qualche idea?

+0

* so come leggere l'intestazione * - se si sa che per tutti questi formati, allora sei già in grado di distinguerli. In caso contrario, questo è esattamente il modo in cui lo fai: leggi le specifiche di ogni formato, costruisci qualcosa in grado di riconoscere ogni tipo singolarmente, combinale in un'unica soluzione. – Sinatr

+0

Dai un'occhiata a questo post: http: // StackOverflow.it/questions/58510/using-net-how-can-you-find-the-mime-type-of-a-file-based-on-the-file-signature, posterò la sezione pertinente qui sotto nella risposta sezione – Jaco

+3

risposta sorprendentemente arrogante da Sinatr – lekso

risposta

6

Questa domanda contiene un esempio di utilizzo dei primi byte di un file per determinare il tipo di file: Using .NET, how can you find the mime type of a file based on the file signature not the extension

E 'un lungo post, quindi mi distacco la risposta specifico in basso:

public class MimeType 
{ 
    private static readonly byte[] BMP = { 66, 77 }; 
    private static readonly byte[] DOC = { 208, 207, 17, 224, 161, 177, 26, 225 }; 
    private static readonly byte[] EXE_DLL = { 77, 90 }; 
    private static readonly byte[] GIF = { 71, 73, 70, 56 }; 
    private static readonly byte[] ICO = { 0, 0, 1, 0 }; 
    private static readonly byte[] JPG = { 255, 216, 255 }; 
    private static readonly byte[] MP3 = { 255, 251, 48 }; 
    private static readonly byte[] OGG = { 79, 103, 103, 83, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0 }; 
    private static readonly byte[] PDF = { 37, 80, 68, 70, 45, 49, 46 }; 
    private static readonly byte[] PNG = { 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82 }; 
    private static readonly byte[] RAR = { 82, 97, 114, 33, 26, 7, 0 }; 
    private static readonly byte[] SWF = { 70, 87, 83 }; 
    private static readonly byte[] TIFF = { 73, 73, 42, 0 }; 
    private static readonly byte[] TORRENT = { 100, 56, 58, 97, 110, 110, 111, 117, 110, 99, 101 }; 
    private static readonly byte[] TTF = { 0, 1, 0, 0, 0 }; 
    private static readonly byte[] WAV_AVI = { 82, 73, 70, 70 }; 
    private static readonly byte[] WMV_WMA = { 48, 38, 178, 117, 142, 102, 207, 17, 166, 217, 0, 170, 0, 98, 206, 108 }; 
    private static readonly byte[] ZIP_DOCX = { 80, 75, 3, 4 }; 

    public static string GetMimeType(byte[] file, string fileName) 
    { 

     string mime = "application/octet-stream"; //DEFAULT UNKNOWN MIME TYPE 

     //Ensure that the filename isn't empty or null 
     if (string.IsNullOrWhiteSpace(fileName)) 
     { 
      return mime; 
     } 

     //Get the file extension 
     string extension = Path.GetExtension(fileName) == null 
           ? string.Empty 
           : Path.GetExtension(fileName).ToUpper(); 

     //Get the MIME Type 
     if (file.Take(2).SequenceEqual(BMP)) 
     { 
      mime = "image/bmp"; 
     } 
     else if (file.Take(8).SequenceEqual(DOC)) 
     { 
      mime = "application/msword"; 
     } 
     else if (file.Take(2).SequenceEqual(EXE_DLL)) 
     { 
      mime = "application/x-msdownload"; //both use same mime type 
     } 
     else if (file.Take(4).SequenceEqual(GIF)) 
     { 
      mime = "image/gif"; 
     } 
     else if (file.Take(4).SequenceEqual(ICO)) 
     { 
      mime = "image/x-icon"; 
     } 
     else if (file.Take(3).SequenceEqual(JPG)) 
     { 
      mime = "image/jpeg"; 
     } 
     else if (file.Take(3).SequenceEqual(MP3)) 
     { 
      mime = "audio/mpeg"; 
     } 
     else if (file.Take(14).SequenceEqual(OGG)) 
     { 
      if (extension == ".OGX") 
      { 
       mime = "application/ogg"; 
      } 
      else if (extension == ".OGA") 
      { 
       mime = "audio/ogg"; 
      } 
      else 
      { 
       mime = "video/ogg"; 
      } 
     } 
     else if (file.Take(7).SequenceEqual(PDF)) 
     { 
      mime = "application/pdf"; 
     } 
     else if (file.Take(16).SequenceEqual(PNG)) 
     { 
      mime = "image/png"; 
     } 
     else if (file.Take(7).SequenceEqual(RAR)) 
     { 
      mime = "application/x-rar-compressed"; 
     } 
     else if (file.Take(3).SequenceEqual(SWF)) 
     { 
      mime = "application/x-shockwave-flash"; 
     } 
     else if (file.Take(4).SequenceEqual(TIFF)) 
     { 
      mime = "image/tiff"; 
     } 
     else if (file.Take(11).SequenceEqual(TORRENT)) 
     { 
      mime = "application/x-bittorrent"; 
     } 
     else if (file.Take(5).SequenceEqual(TTF)) 
     { 
      mime = "application/x-font-ttf"; 
     } 
     else if (file.Take(4).SequenceEqual(WAV_AVI)) 
     { 
      mime = extension == ".AVI" ? "video/x-msvideo" : "audio/x-wav"; 
     } 
     else if (file.Take(16).SequenceEqual(WMV_WMA)) 
     { 
      mime = extension == ".WMA" ? "audio/x-ms-wma" : "video/x-ms-wmv"; 
     } 
     else if (file.Take(4).SequenceEqual(ZIP_DOCX)) 
     { 
      mime = extension == ".DOCX" ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/x-zip-compressed"; 
     } 

     return mime; 
    } 
+0

Nel complesso abbastanza buono, ma non è possibile che questo sia ingannato rinominando un file (come JavaFile.jar o ExcelFile.xlsx) in una diversa estensione di file (come JavaFile.docx o ExcelFile.docx)? Forse questo è fuori dalla portata di questa domanda, ma l'OP non voleva affidarsi all'estensione del file (presumibilmente per niente). Esiste un metodo di rilevamento migliore per i tipi di file basati su ZIP? – Tophandour

+0

Indagherò, richiede una seconda fase di controllo dei file dopo la decompressione. – Jaco

+0

In realtà, ho appena fatto alcuni test con alcuni software affidabili e ampiamente utilizzati e non sembra che controllare un file in questi dettagli (riferendosi al mio commento precedente) sia molto comune. Suppongo che se fosse necessario, è possibile aprire il file come se fosse un file zip e sfogliare le sue directory e controllare i nomi delle directory e i tipi di file al suo interno per assicurarsi che tutto sia quello che ci si aspetta. Poi di nuovo, sono sicuro che una persona dedita e ben informata potrebbe ancora creare qualcosa per ingannare qualsiasi soluzione tu possa inventare. Eh, rendimenti decrescenti, suppongo. – Tophandour

4

L'utilizzo delle firme dei file non è così fattibile (poiché i nuovi formati Office sono file ZIP ei vecchi file Office sono contenitori OLE CF/OLE SS), ma è possibile utilizzare il codice C# per leggerli e capire quali sono.

Per nuovi formati di Office, è possibile leggere il (DOCX/PPTX/XLSX/...) file ZIP utilizzando System.IO.Packaging: https://msdn.microsoft.com/en-us/library/ms568187(v=vs.110).aspx Facendo questo, si può trovare il ContentType della prima parte del documento e dedurre utilizzando tale.

Per i file di Office precedenti (Office 2003) è possibile utilizzare questa libreria per distinguerli in base ai loro contenuti (si noti che i file MSI e MSG sono anche utilizzando questo formato di file): http://sourceforge.net/projects/openmcdf/

Ad esempio, ecco i contenuto di un file XLS: XLS file internals

Spero che questo aiuti! :)

Mi avrebbe certamente aiutato, se avessi trovato questa risposta prima. ;)

0

La risposta da parte dell'utente2173353 è la più corretta, dato che l'OP ha specificamente menzionato i formati di file di Office. Tuttavia, non mi piaceva l'idea di aggiungere un'intera libreria (OpenMCDF) solo per identificare i formati legacy di Office, così ho scritto la mia routine per fare proprio questo.

public static CfbFileFormat GetCfbFileFormat(Stream fileData) 
    { 
     if (!fileData.CanSeek) 
      throw new ArgumentException("Data stream must be seekable.", nameof(fileData)); 

     try 
     { 
      // Notice that values in a CFB files are always little-endian. Fortunately BinaryReader.ReadUInt16/ReadUInt32 reads with little-endian. 
      // If using .net < 4.5 this BinaryReader constructor is not available. Use a simpler one but remember to also remove the 'using' statement. 
      using (BinaryReader reader = new BinaryReader(fileData, Encoding.Unicode, true)) 
      { 
       // Check that data has the CFB file header 
       var header = reader.ReadBytes(8); 
       if (!header.SequenceEqual(new byte[] {0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1})) 
        return CfbFileFormat.Unknown; 

       // Get sector size (2 byte uint) at offset 30 in the header 
       // Value at 1C specifies this as the power of two. The only valid values are 9 or 12, which gives 512 or 4096 byte sector size. 
       fileData.Position = 30; 
       ushort readUInt16 = reader.ReadUInt16(); 
       int sectorSize = 1 << readUInt16; 

       // Get first directory sector index at offset 48 in the header 
       fileData.Position = 48; 
       var rootDirectoryIndex = reader.ReadUInt32(); 

       // File header is one sector wide. After that we can address the sector directly using the sector index 
       var rootDirectoryAddress = sectorSize + (rootDirectoryIndex * sectorSize); 

       // Object type field is offset 80 bytes into the directory sector. It is a 128 bit GUID, encoded as "DWORD, WORD, WORD, BYTE[8]". 
       fileData.Position = rootDirectoryAddress + 80; 
       var bits127_96 = reader.ReadInt32(); 
       var bits95_80 = reader.ReadInt16(); 
       var bits79_64 = reader.ReadInt16(); 
       var bits63_0 = reader.ReadBytes(8); 

       var guid = new Guid(bits127_96, bits95_80, bits79_64, bits63_0); 

       // Compare to known file format GUIDs 

       CfbFileFormat result; 
       return Formats.TryGetValue(guid, out result) ? result : CfbFileFormat.Unknown; 
      } 
     } 
     catch (IOException) 
     { 
      return CfbFileFormat.Unknown; 
     } 
     catch (OverflowException) 
     { 
      return CfbFileFormat.Unknown; 
     } 
    } 

    public enum CfbFileFormat 
    { 
     Doc, 
     Xls, 
     Msi, 
     Ppt, 
     Unknown 
    } 

    private static readonly Dictionary<Guid, CfbFileFormat> Formats = new Dictionary<Guid, CfbFileFormat> 
    { 
     {Guid.Parse("{00020810-0000-0000-c000-000000000046}"), CfbFileFormat.Xls}, 
     {Guid.Parse("{00020820-0000-0000-c000-000000000046}"), CfbFileFormat.Xls}, 
     {Guid.Parse("{00020906-0000-0000-c000-000000000046}"), CfbFileFormat.Doc}, 
     {Guid.Parse("{000c1084-0000-0000-c000-000000000046}"), CfbFileFormat.Msi}, 
     {Guid.Parse("{64818d10-4f9b-11cf-86ea-00aa00b929e8}"), CfbFileFormat.Ppt} 
    }; 

formati aggiuntivi identificatori possono essere aggiunti, se necessario.

Ho provato questo su .doc e .xls, e ha funzionato bene. I non hanno provato su file CFB utilizzando dimensioni del settore 4096 byte, in quanto non so nemmeno dove trovarli.

Il codice si basa sulle informazioni provenienti dai seguenti documenti: