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

时间:2010-04-28 17:50:59

标签: c# asp.net iis pdf tiff

请不要理解,因为我在不知道所有背景的情况下被扔到了这个项目的中间。如果你有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

这个问题比我想象的容易吗?还是像我期待的那样令人讨厌?

8 个答案:

答案 0 :(得分:22)

好的,有足够多的人弄错了我要发布一些我必须识别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()是对扫描程序的调用,它将Stream标记为PDF标记。这不重要,所以我不打算在这里粘贴它。我正在使用tokenizer而不是查看substring来避免这样的问题:

% 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

此代码被上面的代码段标记为非PDF,而更简单的代码块将错误地将其标记为PDF。

我还应该指出,目前的ISO规范没有以前Adobe拥有的规范中的实现说明。最重要的是来自PDF参考,版本1.6:

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

答案 1 :(得分:8)

可以通过查看第一个字节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

答案 2 :(得分:4)

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

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位整数。这个   被称为big-endian字节顺序。字节   2-3任意但经过精心挑选   数字(42)进一步确定   该文件作为TIFF文件。字节   顺序取决于字节的值   0-1。

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

第7.5.2节

  

PDF文件的第一行应为   由5组成的标题   字符%PDF-后跟一个版本   形式1.N的数字,其中N是a   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,版本中的条目   文档的目录词典(位于   通过文件中的Root条目   预告片,如7.5.5“文件中所述   拖车“),如果存在,应使用   而不是在中指定的版本   标题。

     

如果PDF文件包含二进制数据,则为   大多数人(见7.2,“词汇   约定“),标题行应   紧接着是评论   包含至少四个二进制的行   字符 - 即其字符   代码是128或更大。这确保了   文件传输的正确行为   检查附近数据的应用程序   一个文件的开头来确定   是否对待文件的内容   作为文本或二进制文件。

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

答案 3 :(得分:3)

在内部,文件头信息应该有所帮助。如果你打开一个低级文件,比如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";

答案 4 :(得分:3)

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

答案 5 :(得分:1)

如果你去here,你会发现TIFF通常以“幻数”0x49 0x49 0x2A 0x00开始(还有一些其他定义),这是文件的前4个字节。

因此,只需使用前4个字节来确定文件是否为TIFF。

编辑,以其他方式做到这一点可能更好,并首先检测PDF。 PDF的神奇数字更加标准化:正如Plinth指出的那样,它们以“%PDF”开头,位于前1024字节(0x25 0x50 0x44 0x46)的某处。 source

答案 6 :(得分:0)

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

然后,你的处理程序应该读取前几个字节(或左右)以确定文件类型到底是什么 - PDF和TIFF在文件的开头有“魔术数字”,你可以用来确定它,然后相应地设置您的响应标头。

答案 7 :(得分:0)

你可以使用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);

string mimeType = MimeTypes.GetMimeType(result.First());

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