Java 如何确定文件是否为PDF文件?

Java 如何确定文件是否为PDF文件?,java,validation,pdf,text,Java,Validation,Pdf,Text,我使用Java中的PdfBox从PDF文件中提取文本。提供的某些输入文件无效,PDFTextStripper在这些文件上暂停。是否有一种干净的方法来检查提供的文件是否确实是有效的PDF文件?PDF文件以“%PDF”开头(在TextPad或类似工具中打开一个文件并查看) 有什么原因不能用StringReader读取文件并进行检查吗?您可以找到文件(或字节数组)的mime类型,这样就不会盲目依赖扩展名。我是用aperture的MimeExtractor()来做的,或者几天前我看到了一个用于此的库()

我使用Java中的PdfBox从PDF文件中提取文本。提供的某些输入文件无效,PDFTextStripper在这些文件上暂停。是否有一种干净的方法来检查提供的文件是否确实是有效的PDF文件?

PDF文件以“%PDF”开头(在TextPad或类似工具中打开一个文件并查看)


有什么原因不能用StringReader读取文件并进行检查吗?

您可以找到文件(或字节数组)的mime类型,这样就不会盲目依赖扩展名。我是用aperture的MimeExtractor()来做的,或者几天前我看到了一个用于此的库()


我使用aperture从各种文件中提取文本,不仅是pdf,还必须调整pdf格式(aperture使用pdfbox,但我添加了另一个库作为pdfbox失败时的备用库)

以下是我在NUnit测试中使用的内容,必须针对使用Crystal Reports生成的多个pdf版本进行验证:

public static void CheckIsPDF(byte[] data)
    {
        Assert.IsNotNull(data);
        Assert.Greater(data.Length,4);

        // header 
        Assert.AreEqual(data[0],0x25); // %
        Assert.AreEqual(data[1],0x50); // P
        Assert.AreEqual(data[2],0x44); // D
        Assert.AreEqual(data[3],0x46); // F
        Assert.AreEqual(data[4],0x2D); // -

        if(data[5]==0x31 && data[6]==0x2E && data[7]==0x33) // version is 1.3 ?
        {                  
            // file terminator
            Assert.AreEqual(data[data.Length-7],0x25); // %
            Assert.AreEqual(data[data.Length-6],0x25); // %
            Assert.AreEqual(data[data.Length-5],0x45); // E
            Assert.AreEqual(data[data.Length-4],0x4F); // O
            Assert.AreEqual(data[data.Length-3],0x46); // F
            Assert.AreEqual(data[data.Length-2],0x20); // SPACE
            Assert.AreEqual(data[data.Length-1],0x0A); // EOL
            return;
        }

        if(data[5]==0x31 && data[6]==0x2E && data[7]==0x34) // version is 1.4 ?
        {
            // file terminator
            Assert.AreEqual(data[data.Length-6],0x25); // %
            Assert.AreEqual(data[data.Length-5],0x25); // %
            Assert.AreEqual(data[data.Length-4],0x45); // E
            Assert.AreEqual(data[data.Length-3],0x4F); // O
            Assert.AreEqual(data[data.Length-2],0x46); // F
            Assert.AreEqual(data[data.Length-1],0x0A); // EOL
            return;
        }

        Assert.Fail("Unsupported file format");
    }

由于使用PDFBox,您只需执行以下操作:

PDDocument.load(file);
如果PDF被破坏等,它将失败并出现异常


如果成功,您还可以使用
.isEncrypted()

检查PDF是否已加密。您必须尝试此

public boolean isPDF(File file){
    file = new File("Demo.pdf");
    Scanner input = new Scanner(new FileReader(file));
    while (input.hasNextLine()) {
        final String checkline = input.nextLine();
        if(checkline.contains("%PDF-")) { 
            // a match!
            return true;
        }  
    }
    return false;
}

有一个非常方便和简单的库用于测试PDF内容:

API非常简单:

import com.codeborne.pdftest.PDF;
import static com.codeborne.pdftest.PDF.*;
import static org.junit.Assert.assertThat;

public class PDFContainsTextTest {
  @Test
  public void canAssertThatPdfContainsText() {
    PDF pdf = new PDF(new File("src/test/resources/50quickideas.pdf"));
    assertThat(pdf, containsText("50 Quick Ideas to Improve your User Stories"));
  }
}

这里是NinjaCross代码的Java版本

/**
 * Test if the data in the given byte array represents a PDF file.
 */
public static boolean is_pdf(byte[] data) {
    if (data != null && data.length > 4 &&
            data[0] == 0x25 && // %
            data[1] == 0x50 && // P
            data[2] == 0x44 && // D
            data[3] == 0x46 && // F
            data[4] == 0x2D) { // -

        // version 1.3 file terminator
        if (data[5] == 0x31 && data[6] == 0x2E && data[7] == 0x33 &&
                data[data.length - 7] == 0x25 && // %
                data[data.length - 6] == 0x25 && // %
                data[data.length - 5] == 0x45 && // E
                data[data.length - 4] == 0x4F && // O
                data[data.length - 3] == 0x46 && // F
                data[data.length - 2] == 0x20 && // SPACE
                data[data.length - 1] == 0x0A) { // EOL
            return true;
        }

        // version 1.3 file terminator
        if (data[5] == 0x31 && data[6] == 0x2E && data[7] == 0x34 &&
                data[data.length - 6] == 0x25 && // %
                data[data.length - 5] == 0x25 && // %
                data[data.length - 4] == 0x45 && // E
                data[data.length - 3] == 0x4F && // O
                data[data.length - 2] == 0x46 && // F
                data[data.length - 1] == 0x0A) { // EOL
            return true;
        }
    }
    return false;
}
还有一些简单的单元测试:

@Test
public void test_valid_pdf_1_3_data_is_pdf() {
    assertTrue(is_pdf("%PDF-1.3 CONTENT %%EOF \n".getBytes()));
}

@Test
public void test_valid_pdf_1_4_data_is_pdf() {
    assertTrue(is_pdf("%PDF-1.4 CONTENT %%EOF\n".getBytes()));
}

@Test
public void test_invalid_data_is_not_pdf() {
    assertFalse(is_pdf("Hello World".getBytes()));
}

如果您发现任何单元测试失败的地方,请告诉我。

也许我来不及回答。但你应该看看蒂卡。它在内部使用PDFBox解析器来解析PDF

您只需导入tika应用程序最新版本*.jar

 public String parseToStringExample() throws IOException, SAXException, TikaException 
 {

      Tika tika = new Tika();
      try (InputStream stream = ParsingExample.class.getResourceAsStream("test.pdf")) {
           return tika.parseToString(stream); // This should return you the pdf's text
      }
}

这将是一个更干净的解决方案。有关Tika用法的更多详细信息,请参见此处:

我正在使用我在此处和其他网站/帖子上找到的一些建议来确定pdf是否有效。我故意破坏了一个pdf文件,不幸的是,许多解决方案没有检测到该文件被破坏

最后,在对API中的不同方法进行修补之后,我尝试了以下方法:

PDDocument.load(file).getPage(0).getContents().toString();
这不会引发异常,但会输出以下内容:

 WARN  [COSParser:1154] The end of the stream doesn't point to the correct offset, using workaround to read the stream, stream start position: 171, length: 1145844, expected end position: 1146015
就我个人而言,如果文件损坏,我希望抛出一个异常,这样我就可以自己处理它,但我正在实现的API似乎已经以自己的方式处理了它们

为了解决这个问题,我决定尝试使用提供warm语句的类(COSParser)解析文件。我发现有一个名为PDFParser的子类,它继承了一个名为“setLenient”的方法,该方法是键()

然后,我实施了以下措施:

        RandomAccessFile accessFile = new RandomAccessFile(file, "r");
        PDFParser parser = new PDFParser(accessFile); 
        parser.setLenient(false);
        parser.parse();

正如我所希望的那样,这为我的损坏文件引发了一个异常。希望这能帮到别人

罗杰·基斯的答案是错误的!因为并非所有PDF文件都在1.3版中,也并非所有文件都以EOL终止。 以下答案适用于所有未损坏的pdf文件:

public static boolean is_pdf(byte[] data) {
    if (data != null && data.length > 4
            && data[0] == 0x25 && // %
            data[1] == 0x50 && // P
            data[2] == 0x44 && // D
            data[3] == 0x46 && // F
            data[4] == 0x2D) { // -

        // version 1.3 file terminator
        if (//data[5] == 0x31 && data[6] == 0x2E && data[7] == 0x33 &&
                data[data.length - 7] == 0x25 && // %
                data[data.length - 6] == 0x25 && // %
                data[data.length - 5] == 0x45 && // E
                data[data.length - 4] == 0x4F && // O
                data[data.length - 3] == 0x46 && // F
                data[data.length - 2] == 0x20 // SPACE
                //&& data[data.length - 1] == 0x0A// EOL
                ) {
            return true;
        }

        // version 1.3 file terminator
        if (//data[5] == 0x31 && data[6] == 0x2E && data[7] == 0x34 &&
                data[data.length - 6] == 0x25 && // %
                data[data.length - 5] == 0x25 && // %
                data[data.length - 4] == 0x45 && // E
                data[data.length - 3] == 0x4F && // O
                data[data.length - 2] == 0x46 // F
                //&& data[data.length - 1] == 0x0A // EOL
                ) {
            return true;
        }
    }
    return false;
}

一般来说,我们可以这样做,任何pdf版本都将以%%EOF结尾,这样我们就可以像下面一样检查了

public static boolean is_pdf(byte[] data) {
        String s = new String(data);
        String d = s.substring(data.length - 7, data.length - 1);
        if (data != null && data.length > 4 &&
                data[0] == 0x25 && // %
                data[1] == 0x50 && // P
                data[2] == 0x44 && // D
                data[3] == 0x46 && // F
                data[4] == 0x2D) { // -

              if(d.contains("%%EOF")){
                 return true; 
              }         
        }
        return false;
    }

这是一种检查
%%EOF
是否存在的方法,可选检查空白字符。您可以传入
文件
字节[]
对象。在某些PDF版本中,对空白字符的限制较少

公共布尔值isPdf(字节[]数据){
if(data==null | | data.length<5)返回false;
//%PDF-
如果(数据[0]==0x25&&data[1]==0x50&&data[2]==0x44&&data[3]==0x46&&data[4]==0x2D){
int offset=data.length-8,count=0;//用可选空格检查%%EOF的最后8个字节
布尔hasSpace=false,hasCr=false,hasLf=false;
while(偏移量<数据长度){
如果(计数==0&&data[偏移量]==0x25)计数+;/%
如果(计数==1&&data[偏移量]==0x25)计数+;/%
如果(count==2&&data[offset]==0x45)count++;//E
if(count==3&&data[offset]==0x4F)count++;//O
如果(count==4&&data[offset]==0x46)count++;//F
//元信息的可选标志
如果(count==5&&data[offset]==0x20)hasSpace=true;//空格
if(count==5&&data[offset]==0x0D)hasCr=true;//CR
如果(计数==5&&data[offset]==0x0A)hasLf=true;//LF/EOL
offset++;
}
如果(计数=5){
String version=data.length>13?String.format(“%s%s%s”,(char)数据[5],(char)数据[6],(char)数据[7]):“?”;
System.out.printf(“版本:%s |空间:%b | CR:%b | LF:%b%n”,版本,haspace,hasCr,hasLf);
返回true;
}
}
返回false;
}
public boolean isPdf(文件)引发IOException{
返回isPdf(文件,false);
}
//带版本:16字节,不带版本:13字节。
公共布尔值isPdf(文件文件,布尔值includeVersion)引发IOException{
如果(file==null)返回false;
int offsetStart=includeVersion?8:5,offsetEnd=8;
字节[]字节=新字节[offsetStart+offsetEnd];
InputStream is=新文件InputStream(文件);
试一试{
is.read(字节,0,偏移开始);//%PDF-
is.skip(file.length()-bytes.length);//跳过字节
is.read(字节、偏移开始、偏移结束);//%%EOF、SP?、CR?、LF?
}最后{
is.close();
}
返回isPdf(字节);
}

我对依靠神奇数字并不感兴趣。我最终使用Apache的飞行前库来实现以下目的:

编译组:“org.apache.pdfbox”,名称:“飞行前”,版本: “2.0.19”


PreflightParser为文件和其他数据源提供了构造函数。

我已经尝试过了,PDF文件似乎可以使用多种编码,对于有效且可读的PDF文件,读取的文本有时与%PDF不匹配。并非所有以%PDF开头的文件都是有效的PDF文件。哦,我忘了提到现在有一个用于文本提取的apache项目,以防您喜欢它而不是apertur
private boolean isPdf(InputStream fileInputStream) {
    try {
        PreflightParser preflightParser = new PreflightParser(new ByteArrayDataSource(fileInputStream));
        preflightParser.parse();
        return true;
    } catch (Exception e) {
        return false;
    }
}