如何检测文件是 PDF 还是 TIFF?

Posted

技术标签:

【中文标题】如何检测文件是 PDF 还是 TIFF?【英文标题】:How to detect if a file is PDF or TIFF? 【发布时间】:2011-02-13 11:39:45 【问题描述】:

请耐心等待,因为我在不了解所有背景的情况下被卷入了这个项目的中间。如果你有 WTF 问题,相信我,我也有。

以下是场景:我有一堆文件驻留在 IIS 服务器上。他们没有文件扩展名。只是名称为“asda-2342-sd3rs-asd24-ut57”等的裸文件。没有什么直观的。

问题是我需要在 ASP.NET (2.0) 页面上提供文件并将 tiff 文件显示为 tiff 并将 PDF 文件显示为 PDF。不幸的是,我不知道哪个是哪个,我需要能够以各自的格式适当地显示它们。

例如,假设我需要显示 2 个文件,一个是 tiff,一个是 PDF。该页面应该显示一个 tiff 图像,也许还有一个可以在新选项卡/窗口中打开 PDF 的链接。

问题:

由于这些文件都没有扩展名,我不得不强制 IIS 将所有内容都作为 TIFF 提供。但如果我这样做,PDF 文件将不会显示。对于未知的文件扩展名,我可以更改 IIS 以强制 MIME 类型为 PDF,但我会遇到相反的问题。

http://support.microsoft.com/kb/326965

这个问题是比我想象的更容易还是和我预期的一样糟糕?

【问题讨论】:

【参考方案1】:

好的,有足够多的人弄错了,我将发布一些我必须识别 TIFF 的代码:

private const int kTiffTagLength = 12;
private const int kHeaderSize = 2;
private const int kMinimumTiffSize = 8;
private const byte kIntelMark = 0x49;
private const byte kMotorolaMark = 0x4d;
private const ushort kTiffMagicNumber = 42;


private bool IsTiff(Stream stm)

    stm.Seek(0);
    if (stm.Length < kMinimumTiffSize)
        return false;
    byte[] header = new byte[kHeaderSize];

    stm.Read(header, 0, header.Length);

    if (header[0] != header[1] || (header[0] != kIntelMark && header[0] != kMotorolaMark))
        return false;
    bool isIntel = header[0] == kIntelMark;

    ushort magicNumber = ReadShort(stm, isIntel);
    if (magicNumber != kTiffMagicNumber)
        return false;
    return true;


private ushort ReadShort(Stream stm, bool isIntel)

    byte[] b = new byte[2];
    _stm.Read(b, 0, b.Length);
    return ToShort(_isIntel, b[0], b[1]);


private static ushort ToShort(bool isIntel, byte b0, byte b1)

    if (isIntel)
    
        return (ushort)(((int)b1 << 8) | (int)b0);
    
    else
    
        return (ushort)(((int)b0 << 8) | (int)b1);
    

我破解了一些更通用的代码来得到这个。

对于 PDF,我的代码如下所示:

public bool IsPdf(Stream stm)

    stm.Seek(0, SeekOrigin.Begin);
    PdfToken token;
    while ((token = GetToken(stm)) != null) 
    
        if (token.TokenType == MLPdfTokenType.Comment) 
        
            if (token.Text.StartsWith("%PDF-1.")) 
                return true;
        
        if (stm.Position > 1024)
            break;
    
    return false;

现在,GetToken() 是对扫描仪的调用,它将流标记为 PDF 令牌。这很重要,所以我不打算在这里粘贴它。我正在使用标记器而不是查看子字符串来避免这样的问题:

% the following is a PostScript file, NOT a PDF file
% you'll note that in our previous version, it started with %PDF-1.3,
% incorrectly marking it as a PDF
%
clippath stroke showpage

上面的代码 sn-p 将这段代码标记为不是 PDF,而更简单的代码块会错误地将其标记为 PDF。

我还应该指出,当前的 ISO 规范没有 Adob​​e 以前拥有的规范中的实施说明。最重要的是来自 PDF 参考,1.6 版:

Acrobat viewers require only that the header appear somewhere within
the first 1024 bytes of the file.

【讨论】:

stm.Seek(0);对我来说失败了,不能编译。我正在使用 vs 2008,.net 3.5。 对不起 - 我讨厌必须将 SeekOrigin.Begin 放在最常见的情况下 - 这是一种扩展方法。 什么是 GetToken() 和 token.TokenType == MLPdfTokenType.Comment ?有可用的代码吗?哪个库属于该代码?【参考方案2】:

可以通过查看第一个字节http://local.wasp.uwa.edu.au/~pbourke/dataformats/tiff/ 来检测 TIFF

前 8 个字节构成标题。 其中的前两个字节是 "II" 用于小端字节排序 或“MM”表示大端字节序。

关于PDF:http://www.adobe.com/devnet/livecycle/articles/lc_pdf_overview_format.pdf

标题只包含一行 标识 PDF 的版本。 示例:%PDF-1.6

【讨论】:

来自 adobe 的文档并不是完全准确的规范。 %PDF-1.x,其中 x 是一个数字,可能出现在文件前 1K 内的任何位置。 好的,这是最完整的规格adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf 它是>30 Mb【参考方案3】:

阅读每种文件格式的规范将告诉您如何识别该格式的文件。

TIFF 文件 - 检查字节 1 和 2 的 0x4D4D 或 0x4949 字节 2-3 的值“42”。

规范第 13 页内容如下:

TIFF 文件以 8 字节开头 图像文件头,包含 以下信息: 字节 0-1: 文件中使用的字节顺序。合法的 值为:“II”(4949.H)“MM” (4D4D.H) 在“II”格式中,字节 顺序总是从最小的 最重要的字节 有效字节,对于 16 位和 32 位整数 这称为 little-endian 字节顺序。在“MM” 格式,字节顺序总是从最 显着到最不显着,对于 16 位和 32 位整数。这 称为大端字节序。字节 2-3 任意但精心挑选的 编号 (42) 进一步确定 文件作为 TIFF 文件。字节 顺序取决于 Bytes 的值 0-1。

PDF 文件以 PDF 版本开头,后跟几个二进制字节。 (我认为您现在必须购买当前版本的 ISO 规范。)

第 7.5.2 节

PDF 文件的第一行应为 由 5 个组成的标题 字符 %PDF– 后跟一个版本 1.N 形式的数,其中 N 是 0 到 7 之间的数字。符合 读者应接受任何文件 以下标题:%PDF–1.0, %PDF–1.1、%PDF–1.2、%PDF–1.3、%PDF–1.4、 %PDF–1.5、%PDF–1.6、%PDF–1.7 开始 对于 PDF 1.4,版本条目 文档的目录字典(位于 通过文件的根条目 拖车,如 7.5.5 中所述,“文件 拖车”),如果存在,应使用 而不是中指定的版本 标题。

如果 PDF 文件包含二进制数据,如 大多数都这样做(见 7.2,“词汇 公约”),标题行应 立即发表评论 包含至少四个二进制文件的行 字符——即,其字符 代码为 128 或更大。这确保 文件传输的正确行为 检查附近数据的应用程序 确定文件的开头 是否处理文件内容 作为文本或二进制。

当然,您可以通过检查更多文件特定项来对每个文件进行“更深入”的检查。

【讨论】:

【参考方案4】:

Gary Kessler 提供了一份非常有用的文件签名列表,即“幻数”http://www.garykessler.net/library/file_sigs.html

【讨论】:

【参考方案5】:

在内部,文件头信息应该会有所帮助。如果您打开低级文件,例如 StreamReader() 或 FOPEN(),请查看文件中的前两个字符...几乎每种文件类型都有自己的签名。

PDF always starts with "%P" (but more specifically would have like %PDF)
TIFF appears to start with "II"
Bitmap files with "BM"
Executable files with "MZ"

我过去也曾处理过这个问题...也是为了帮助防止不需要的文件被上传到给定的站点并在检查后立即中止它。

编辑 -- 发布示例代码以读取和测试文件头类型

String fn = "Example.pdf";

StreamReader sr = new StreamReader( fn );
char[] buf = new char[5];
sr.Read( buf, 0, 4);
sr.Close();
String Hdr = buf[0].ToString()
    + buf[1].ToString()
    + buf[2].ToString()
    + buf[3].ToString()
    + buf[4].ToString();

String WhatType;
if (Hdr.StartsWith("%PDF"))
   WhatType = "PDF";
else if (Hdr.StartsWith("MZ"))
   WhatType = "EXE or DLL";
else if (Hdr.StartsWith("BM"))
   WhatType = "BMP";
else if (Hdr.StartsWith("?_"))
   WhatType = "HLP (help file)";
else if (Hdr.StartsWith("\0\0\1"))
   WhatType = "Icon (.ico)";
else if (Hdr.StartsWith("\0\0\2"))
   WhatType = "Cursor (.cur)";
else
   WhatType = "Unknown";

【讨论】:

不应该在答案的关键部分写“似乎开始于”!根据规范,TIFF 文件以 2 个字节的 ASCII“II”或“MM”开头,然后是 (II) Intel little-endian 或 (MM) Motorola big-endian 字节顺序中的 2 个字节,形成整数 42。跨度> 【参考方案6】:

如果你去here,你会看到TIFF通常以“幻数”开头0x49 0x49 0x2A 0x00(也给出了一些其他定义),也就是文件的前4个字节。

所以只需使用前 4 个字节来确定文件是否为 TIFF。

编辑,最好换一种方式,先检测 PDF。 PDF 的神奇数字更加标准化:正如 Plinth 善意地指出的那样,它们在前 1024 个字节(0x25 0x50 0x44 0x46)的某处以“%PDF”开头。 source

【讨论】:

这很接近,但错误。 TIFF 以两个签名之一开始,0x49 0x49 0x2a 0x00 或 0x4d 0x4d 0x00 0x2a。 您的 PDF 检查也是错误的。 %PDF 只需要出现在前 1024 个字节中。【参考方案7】:

您将不得不编写一个 ashx 来获取请求的文件。

然后,您的处理程序应读取前几个字节(左右)以确定文件类型的真正含义——PDF 和 TIFF 在文件的开头有“幻数”,您可以使用它来确定这一点,然后相应地设置您的响应标头。

【讨论】:

【参考方案8】:

你可以使用Myrmec来识别文件类型,这个库使用文件字节头。这个库在 nuget "Myrmec" 上可用,这是 repo,myrmec 也支持 mime 类型,你可以试试。代码会是这样的:

// create a sniffer instance.
Sniffer sniffer = new Sniffer();

// populate with mata data.
sniffer.Populate(FileTypes.CommonFileTypes);

// get file head byte, may be 20 bytes enough.
byte[] fileHead = ReadFileHead();

// start match.
List<string> results = sniffer.Match(fileHead);

并获取 mime 类型:

List<string> result = sniffer.Match(head);

字符串 mimeType = MimeTypes.GetMimeType(result.First());

但是那个支持tiff的只有“49 49 2A 00”和“4D 4D 00 2A”两个签名,如果有更多可以自己添加,可能可以看myrmec的readme文件寻求帮助。 myrmec github repo

【讨论】:

以上是关于如何检测文件是 PDF 还是 TIFF?的主要内容,如果未能解决你的问题,请参考以下文章

Google Vision Api 支持 PDF 和 TIFF 文本检测,但它也可以与包含图像的 PDf 一起使用吗?

Java如何把一个PDF转为tif

使用 Visual Basic 确定 TIFF 图像是灰度还是彩色?

将 pdf 文件转换为 tiff 文件的最佳方法 [关闭]

PDF to tiff ImageMagick 问题

使用 ghostscript 将 PDF 转换为 TIFF 但不将文件写入磁盘