2015-03-23 1 views
8

Как идентифицировать doc, docx, pdf, xls и xlsx на основе заголовка файла на C#? Я не хочу полагаться на расширения файлов и MimeMapping.GetMimeMapping для этого, поскольку любой из них можно манипулировать.Как идентифицировать doc, docx, pdf, xls и xlsx на основе заголовка файла

Я знаю, как читать заголовок, но не знаю, какую комбинацию байтов можно сказать, если файл является документом, docx, pdf, xls или xlsx. Любые мысли?

+0

* Я знаю, как прочитать заголовок * - если вы знаете, что для всех этих форматов, то вы уже можете отличить их. Если нет, то это именно то, как вы это делаете: читайте спецификации каждого формата, создавайте что-то, способное распознавать каждый тип отдельно, объединять их в одно решение. – Sinatr

+0

Отправляй сообщение: http: // stackoverflow.com/questions/58510/using-net-how-can-you-find-the-mime-a-file-based-on-the-file-signature, я опубликую соответствующий раздел ниже в ответе section – Jaco

+3

удивительно высокомерный ответ от Sinatr – lekso

ответ

6

Этот вопрос содержит пример использования первых байт файла, чтобы определить тип файла: Using .NET, how can you find the mime type of a file based on the file signature not the extension

Это очень длинный пост, поэтому я отправляю соответствующий ответ ниже:

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

В целом неплохо, но нельзя ли это обмануть, переименовав файл (например, JavaFile.jar или ExcelFile.xlsx) в другое расширение файла (например, JavaFile.docx или ExcelFile.docx)? Возможно, это выходит за рамки этого вопроса, но OP не хотел полагаться на расширение файла (предположительно вообще). Есть ли лучший способ обнаружения типов файлов на основе ZIP? – Tophandour

+0

Я буду исследовать, он требует второго шага проверки файлов после декомпрессии. – Jaco

+0

На самом деле, я просто сделал некоторые тесты с некоторыми авторитетными и широко используемыми частями программного обеспечения, и похоже, что проверка файла в этой детали (со ссылкой на мой предыдущий комментарий) очень распространена. Я полагаю, что если бы вам пришлось это сделать, вы могли бы открыть файл, как если бы он был zip и просматривать его каталоги и проверять имена каталогов и типы файлов внутри него, чтобы убедиться, что все так, как вы ожидаете. Опять же, я уверен, что преданный и знающий человек все еще может создать что-то, чтобы обмануть любое решение, которое вы придумали. Eh, уменьшая отдачу, я полагаю. – Tophandour

4

Использование подписи файлов не так возможно (поскольку новые форматы офиса являются ZIP-файлами, а старые файлы Office - контейнерами OLE CF/OLE SS), но вы можете использовать код C# для их чтения и выяснить, каковы они.

Для новых форматов Office, вы можете прочитать (DOCX/PPTX/XLSX/...) ZIP файл, используя System.IO.Packaging: https://msdn.microsoft.com/en-us/library/ms568187(v=vs.110).aspx Doing что, вы можете найти ContentType первой части документа и вывод с помощью этого.

Для старых файлов Office (Office 2003), вы можете использовать эту библиотеку, чтобы отличить их на основе их содержания (обратите внимание, что MSI и MSG файлов также используют этот формат файла): http://sourceforge.net/projects/openmcdf/

Например, здесь являются содержимое файла XLS: XLS file internals

Надеюсь, это поможет! :)

Это наверняка помогло бы мне, если бы я нашел этот ответ раньше. ;)

0

Ответ от user2173353's является наиболее правильным, учитывая, что OP конкретно упоминает форматы файлов Office. Однако мне не понравилась идея добавления целой библиотеки (OpenMCDF) только для определения устаревших форматов Office, поэтому я написал свою собственную процедуру для этого.

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

При необходимости могут быть добавлены дополнительные идентификаторы форматов.

Я пробовал это на .doc и .xls, и он отлично работал. I не провели в файлах CFB, используя размер сектора 4096 байт, поскольку я даже не знаю, где их найти.

Код основывается на информации из следующих документов: