2015-03-23 1 views
8

Comment identifier doc, docx, pdf, xls et xlsx en fonction de l'en-tête du fichier en C#? Je ne veux pas compter sur les extensions de fichier ni MimeMapping.GetMimeMapping pour cela que l'un des deux peut être manipulé.Comment identifier doc, docx, pdf, xls et xlsx en fonction de l'en-tête du fichier

Je sais lire l'en-tête mais je ne sais pas quelle combinaison d'octets peut dire si un fichier est un doc, docx, pdf, xls ou xlsx. Des pensées?

+0

* Je sais lire l'en-tête * - si vous savez que pour tous ces formats, alors vous avez déjà réussi à les distinguer. Si non, alors c'est exactement ce que vous faites: lire les spécifications de chaque format, construire quelque chose capable de reconnaître chaque type individuellement, les combiner en une seule solution. – Sinatr

+0

Découvrez ce post: http: // stackoverflow.com/questions/58510/using-net-comment-pouvez-vous-trouver-le-mime-type-d'-un-fichier-sur-le-fichier-signature, je vais poster la section pertinente ci-dessous dans la réponse section – Jaco

+3

réponse étonnamment arrogant de Sinatr – lekso

Répondre

6

Cette question contient un exemple d'utilisation des premiers octets d'un fichier pour déterminer le type de fichier: Using .NET, how can you find the mime type of a file based on the file signature not the extension

Il est un poste très longtemps, donc je suis annonce la réponse pertinente ci-dessous:

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

Dans l'ensemble assez bien, mais ne pourrait-on pas tromper en renommant un fichier (tel que JavaFile.jar ou ExcelFile.xlsx) à une extension de fichier différente (comme JavaFile.docx ou ExcelFile.docx)? C'est peut-être hors de portée de cette question, mais le PO ne voulait pas se fier à l'extension de fichier (vraisemblablement du tout). Existe-t-il une meilleure méthode de détection pour les types de fichiers basés sur ZIP? – Tophandour

+0

Je vais enquêter, il faut une seconde étape pour vérifier les fichiers après décompression. – Jaco

+0

En fait, je viens de faire quelques tests avec des morceaux de logiciels réputés et largement utilisés et il ne semble pas que vérifier un fichier avec autant de détails (en se référant à mon commentaire précédent) est très commun. Je suppose que si vous le deviez, vous pourriez ouvrir le fichier comme s'il s'agissait d'un fichier zip et parcourir ses répertoires et vérifier les noms de répertoires et les types de fichiers à l'intérieur pour s'assurer que tout est ce que vous attendiez. Là encore, je suis sûr qu'une personne dévouée et bien informée pourrait encore concevoir quelque chose pour tromper la solution que vous venez de proposer. Eh, rendements décroissants, je suppose. – Tophandour

4

L'utilisation de signatures de fichiers n'est pas possible (puisque les nouveaux formats Office sont des fichiers ZIP et les anciens fichiers Office sont des conteneurs OLE CF/OLE SS), mais vous pouvez utiliser le code C# pour les lire et déterminer leur nature.

Pour nouveaux formats Office, vous pouvez lire le (DOCX/PPTX/XLSX/...) Fichier ZIP en utilisant System.IO.Packaging: https://msdn.microsoft.com/en-us/library/ms568187(v=vs.110).aspx Faire cela, vous pouvez trouver le ContentType de la première partie du document et en déduire l'utilisation que.

Pour les fichiers Office anciens (Office 2003), vous pouvez utiliser cette bibliothèque pour les distinguer en fonction de leur contenu (notez que les fichiers MSI et MSG utilisent également ce format de fichier): http://sourceforge.net/projects/openmcdf/

Par exemple, voici la contenu d'un fichier XLS: XLS file internals

J'espère que cela aide! :)

Cela m'aurait certainement aidé, si j'avais trouvé cette réponse plus tôt. ;)

0

La réponse de user2173353 est la plus correcte, étant donné que l'OP a spécifiquement mentionné les formats de fichiers Office. Cependant, je n'ai pas aimé l'idée d'ajouter une bibliothèque entière (OpenMCDF) juste pour identifier les formats Office hérités, j'ai donc écrit ma propre routine pour faire cela.

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} 
    }; 

Des identifiants de formats supplémentaires peuvent être ajoutés si nécessaire.

J'ai essayé ceci sur .doc et .xls, et cela a fonctionné correctement. I n'ont pas testé sur les fichiers CFB en utilisant 4096 octets de taille de secteur, car je ne sais même pas où trouver ceux-ci.

Le code est basé sur des informations des documents suivants: