PdfDocumentIdTest.java

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2025 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.itextpdf.kernel.pdf;

import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.source.RandomAccessSourceFactory;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.PdfReader.StrictnessLevel;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.TestUtil;
import com.itextpdf.test.annotations.LogMessage;
import com.itextpdf.test.annotations.LogMessages;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("IntegrationTest")
public class PdfDocumentIdTest extends ExtendedITextTest {
    public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/PdfDocumentTestID/";
    public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/pdf/PdfDocumentTestID/";

    @BeforeAll
    public static void beforeClass() {
        createOrClearDestinationFolder(destinationFolder);
    }

    @Test
    public void changeIdTest() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        String value = "Modified ID 1234";
        WriterProperties writerProperties = new WriterProperties();
        writerProperties.setModifiedDocumentId(new PdfString((value)));
        PdfWriter writer = new PdfWriter(baos, writerProperties);
        PdfDocument pdfDocument = new PdfDocument(writer);
        pdfDocument.addNewPage();
        pdfDocument.close();

        byte[] documentBytes = baos.toByteArray();

        baos.close();

        PdfReader reader = new PdfReader(new ByteArrayInputStream(documentBytes));
        pdfDocument = new PdfDocument(reader);
        PdfArray idArray = pdfDocument.getTrailer().getAsArray(PdfName.ID);
        Assertions.assertNotNull(idArray);
        String extractedValue = idArray.getAsString(1).getValue();
        pdfDocument.close();

        Assertions.assertEquals(value, extractedValue);
    }

    @Test
    public void changeIdTest02() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new PdfException(e);
        }
        PdfString initialId = new PdfString(md5.digest("Initial ID 56789".getBytes()));
        PdfWriter writer = new PdfWriter(baos, new WriterProperties().setInitialDocumentId(initialId));
        PdfDocument pdfDocument = new PdfDocument(writer);
        pdfDocument.addNewPage();
        pdfDocument.close();

        byte[] documentBytes = baos.toByteArray();

        baos.close();

        PdfReader reader = new PdfReader(new ByteArrayInputStream(documentBytes));
        pdfDocument = new PdfDocument(reader);
        PdfArray idArray = pdfDocument.getTrailer().getAsArray(PdfName.ID);
        Assertions.assertNotNull(idArray);
        PdfString extractedString = idArray.getAsString(1);
        pdfDocument.close();

        Assertions.assertEquals(initialId, extractedString);
    }

    @Test
    public void changeIdTest03() throws IOException {
        ByteArrayOutputStream baosInitial = new ByteArrayOutputStream();
        ByteArrayOutputStream baosModified = new ByteArrayOutputStream();

        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new PdfException(e);
        }
        PdfString initialId = new PdfString(md5.digest("Initial ID 56789".getBytes()));
        PdfString modifiedId = new PdfString("Modified ID 56789");

        PdfWriter writer = new PdfWriter(baosInitial, new WriterProperties()
                .setInitialDocumentId(initialId).setModifiedDocumentId(modifiedId));
        PdfDocument pdfDocument = new PdfDocument(writer);
        pdfDocument.addNewPage();
        pdfDocument.close();

        PdfReader reader = new PdfReader(new RandomAccessSourceFactory().createSource(baosInitial.toByteArray()), new ReaderProperties());
        pdfDocument = new PdfDocument(reader);
        PdfArray idArray = pdfDocument.getTrailer().getAsArray(PdfName.ID);
        pdfDocument.close();
        Assertions.assertNotNull(idArray);
        PdfString extractedInitialValue = idArray.getAsString(0);
        Assertions.assertEquals(initialId, extractedInitialValue);
        PdfString extractedModifiedValue = idArray.getAsString(1);
        Assertions.assertEquals(modifiedId, extractedModifiedValue);


        pdfDocument = new PdfDocument(new PdfReader(new RandomAccessSourceFactory().createSource(baosInitial.toByteArray()), new ReaderProperties()),
                new PdfWriter(baosModified));
        new PdfCanvas(pdfDocument.addNewPage())
                .saveState()
                .lineTo(100, 100)
                .moveTo(100, 100)
                .stroke()
                .restoreState();
        pdfDocument.close();

        reader = new PdfReader(new RandomAccessSourceFactory().createSource(baosModified.toByteArray()), new ReaderProperties());
        pdfDocument = new PdfDocument(reader);
        idArray = pdfDocument.getTrailer().getAsArray(PdfName.ID);
        pdfDocument.close();
        Assertions.assertNotNull(idArray);
        extractedInitialValue = idArray.getAsString(0);
        Assertions.assertEquals(initialId, extractedInitialValue);
        extractedModifiedValue = idArray.getAsString(1);
        Assertions.assertNotEquals(modifiedId, extractedModifiedValue);
    }

    @Test
    public void fetchReaderIdTest() throws IOException{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new PdfException(e);
        }
        PdfString initialId = new PdfString(md5.digest("Initial ID 56789".getBytes()));
        PdfWriter writer = new PdfWriter(baos, new WriterProperties().setInitialDocumentId(initialId));
        PdfDocument pdfDocument = new PdfDocument(writer);
        pdfDocument.addNewPage();
        pdfDocument.close();

        byte[] documentBytes = baos.toByteArray();

        baos.close();

        PdfReader reader = new PdfReader(new ByteArrayInputStream(documentBytes));
        pdfDocument = new PdfDocument(reader);
        String firstOriginalId = new String(reader.getOriginalFileId());
        String secondOriginalId = new String(reader.getOriginalFileId());
        String firstModifiedId = new String(reader.getModifiedFileId());
        String secondModifiedId = new String(reader.getModifiedFileId());

        Assertions.assertEquals(firstOriginalId, secondOriginalId);
        Assertions.assertEquals(firstModifiedId, secondModifiedId);
    }

    @Test
    public void writerPropertiesPriorityTest() throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new PdfException(e);
        }
        PdfString originalId = new PdfString(md5.digest("Initial ID 01234".getBytes()));
        PdfString modifiedId = new PdfString(md5.digest("Modified ID 56789".getBytes()));
        PdfWriter writer = new PdfWriter(baos, new WriterProperties().setInitialDocumentId(originalId).setModifiedDocumentId(modifiedId));
        PdfDocument pdfDocument = new PdfDocument(writer);
        pdfDocument.addNewPage();
        pdfDocument.close();

        byte[] documentBytes = baos.toByteArray();

        baos.close();

        PdfString newOriginalId = new PdfString(md5.digest("Initial ID 98765".getBytes()));
        PdfString newModifiedId = new PdfString(md5.digest("Modified ID 43210".getBytes()));

        PdfReader reader = new PdfReader(new ByteArrayInputStream(documentBytes));
        PdfWriter newWriter = new PdfWriter(new ByteArrayOutputStream(), new WriterProperties()
                .setInitialDocumentId(newOriginalId)
                .setModifiedDocumentId(newModifiedId));
        pdfDocument = new PdfDocument(reader, newWriter);

        String extractedOriginalId = pdfDocument.getOriginalDocumentId().getValue();
        String extractedModifiedId = pdfDocument.getModifiedDocumentId().getValue();

        pdfDocument.close();

        Assertions.assertEquals(extractedOriginalId, newOriginalId.getValue());
        Assertions.assertEquals(extractedModifiedId, newModifiedId.getValue());
    }

    @Test
    public void readPdfWithTwoStringIdsTest() throws IOException{
        PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "pdfWithTwoStringIds.pdf"));
        String originalId = null;
        String modifiedId = null;
        if(pdfDocument.getOriginalDocumentId() != null) {
            originalId = pdfDocument.getOriginalDocumentId().getValue();
        }
        if(pdfDocument.getModifiedDocumentId() != null) {
            modifiedId = pdfDocument.getModifiedDocumentId().getValue();
        }
        pdfDocument.close();

        Assertions.assertNotNull(originalId);
        Assertions.assertNotNull(modifiedId);

    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED)
    })
    public void readPdfWithTwoNumberIdsTest() throws IOException{
        PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "pdfWithTwoNumberIds.pdf"));
        String originalId = null;
        String modifiedId = null;
        if(pdfDocument.getOriginalDocumentId() != null) {
            originalId = pdfDocument.getOriginalDocumentId().getValue();
        }
        if(pdfDocument.getModifiedDocumentId() != null) {
            modifiedId = pdfDocument.getModifiedDocumentId().getValue();
        }

        pdfDocument.close();

        Assertions.assertNull(originalId);
        Assertions.assertNull(modifiedId);

    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED)
    })
    public void readPdfWithOneNumberOneStringIdsTest() throws IOException{
        PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "pdfWithOneNumberOneStringIds.pdf"));
        String originalId = null;
        String modifiedId = null;
        if(pdfDocument.getOriginalDocumentId() != null) {
            originalId = pdfDocument.getOriginalDocumentId().getValue();
        }
        if(pdfDocument.getModifiedDocumentId() != null) {
            modifiedId = pdfDocument.getModifiedDocumentId().getValue();
        }
        pdfDocument.close();

        Assertions.assertNull(originalId);
        Assertions.assertNotNull(modifiedId);
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED)
    })
    public void readPdfWithOneStringIdValueTest() throws IOException{
        PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "pdfWithOneStringId.pdf"));
        String originalId = null;
        String modifiedId = null;
        if(pdfDocument.getOriginalDocumentId() != null) {
            originalId = pdfDocument.getOriginalDocumentId().getValue();
        }
        if(pdfDocument.getModifiedDocumentId() != null) {
            modifiedId = pdfDocument.getModifiedDocumentId().getValue();
        }
        pdfDocument.close();

        Assertions.assertNull(originalId);
        Assertions.assertNull(modifiedId);
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED)
    })
    public void readPdfWithOneNumberIdValueTest() throws IOException{
        PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "pdfWithOneNumberId.pdf"));
        String originalId = null;
        String modifiedId = null;
        if(pdfDocument.getOriginalDocumentId() != null) {
            originalId = pdfDocument.getOriginalDocumentId().getValue();
        }
        if(pdfDocument.getModifiedDocumentId() != null) {
            modifiedId = pdfDocument.getModifiedDocumentId().getValue();
        }
        pdfDocument.close();

        Assertions.assertNull(originalId);
        Assertions.assertNull(modifiedId);
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED)
    })
    public void readPdfWithNoIdTest() throws IOException{
        PdfReader reader = new PdfReader(sourceFolder + "pdfWithNoId.pdf");

        PdfDocument pdfDocument = new PdfDocument(reader);
        String originalId = null;
        String modifiedId = null;
        if(pdfDocument.getOriginalDocumentId() != null) {
            originalId = pdfDocument.getOriginalDocumentId().getValue();
        }
        if(pdfDocument.getModifiedDocumentId() != null) {
            modifiedId = pdfDocument.getModifiedDocumentId().getValue();
        }
        pdfDocument.close();

        Assertions.assertNull(originalId);
        Assertions.assertNull(modifiedId);

        Assertions.assertEquals(0, reader.getOriginalFileId().length);
        Assertions.assertEquals(0, reader.getModifiedFileId().length);
    }

    @Test
    public void readPdfWithNoIdAndConservativeReadingTest() throws IOException{
        try (PdfReader reader = new PdfReader(sourceFolder + "pdfWithNoId.pdf")
                .setStrictnessLevel(StrictnessLevel.CONSERVATIVE)) {

            Exception e = Assertions.assertThrows(PdfException.class, () -> new PdfDocument(reader));
            Assertions.assertEquals(IoLogMessageConstant.DOCUMENT_IDS_ARE_CORRUPTED, e.getMessage());
        }
    }


//    @Test
//    public void appendModeTest() {
//        String originalId;
//        String newOriginalId;
//        String appendModeNewOriginalId;
//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//        PdfWriter initialWriter = CompareTool.createTestPdfWriter(baos, new WriterProperties().setInitialDocumentId(originalId));
//        PdfWriter newWriter = CompareTool.createTestPdfWriter(baos, new WriterProperties().setInitialDocumentId(newOriginalId));
//        PdfWriter appendModeWriter = CompareTool.createTestPdfWriter(baos, new WriterProperties().setInitialDocumentId(appendModeNewOriginalId));
//
//
//    }
//
//    @Test
//    public void encryptionAes128Test() {
//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//        PdfString originalId = new PdfString("Original ID 56789");
//        PdfWriter initialWriter = CompareTool.createTestPdfWriter(baos, new WriterProperties().setInitialDocumentId(originalId));
//
//        Assertions.assertNotEquals();
//        Assertions.assertEquals();
//
//    }
}