pdfbox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From til...@apache.org
Subject svn commit: r1654609 - /pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java
Date Sun, 25 Jan 2015 00:36:44 GMT
Author: tilman
Date: Sun Jan 25 00:36:43 2015
New Revision: 1654609

URL: http://svn.apache.org/r1654609
Log:
PDFBOX-2630: checks outlines linked lists are free of any "contradictions" and loops

Modified:
    pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java

Modified: pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java?rev=1654609&r1=1654608&r2=1654609&view=diff
==============================================================================
--- pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java
(original)
+++ pdfbox/trunk/preflight/src/main/java/org/apache/pdfbox/preflight/process/BookmarkValidationProcess.java
Sun Jan 25 00:36:43 2015
@@ -21,6 +21,9 @@
 
 package org.apache.pdfbox.preflight.process;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import static org.apache.pdfbox.preflight.PreflightConfiguration.ACTIONS_PROCESS;
 import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_NOCATALOG;
 import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_SYNTAX_TRAILER_OUTLINES_INVALID;
@@ -29,6 +32,7 @@ import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSDocument;
 import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSObject;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
@@ -50,14 +54,18 @@ public class BookmarkValidationProcess e
             PDDocumentOutline outlineHierarchy = catalog.getDocumentOutline();
             if (outlineHierarchy != null)
             {
+                COSDictionary dict = outlineHierarchy.getCOSDictionary();
+                COSObject firstObj = (COSObject) dict.getItem(COSName.FIRST);
+                COSObject lastObj = (COSObject) dict.getItem(COSName.LAST);
+
                 // Count entry is mandatory if there are childrens
-                if (!isCountEntryPresent(outlineHierarchy.getCOSDictionary())
+                if (!isCountEntryPresent(dict)
                         && (outlineHierarchy.getFirstChild() != null || outlineHierarchy.getLastChild()
!= null))
                 {
                     addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
                             "Outline Hierarchy doesn't have Count entry"));
                 }
-                else if (isCountEntryPositive(ctx, outlineHierarchy.getCOSDictionary())
+                else if (isCountEntryPositive(ctx, dict)
                         && (outlineHierarchy.getFirstChild() == null || outlineHierarchy.getLastChild()
== null))
                 {
                     addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
@@ -65,13 +73,13 @@ public class BookmarkValidationProcess e
                 }
                 else
                 {
-                    exploreOutlineLevel(ctx, outlineHierarchy.getFirstChild());
+                    exploreOutlineLevel(ctx, outlineHierarchy.getFirstChild(), firstObj,
lastObj);
                 }
             }
         }
         else
         {
-            ctx.addValidationError(new ValidationError(ERROR_SYNTAX_NOCATALOG, "There are
no Catalog entry in the Document"));
+            ctx.addValidationError(new ValidationError(ERROR_SYNTAX_NOCATALOG, "There is
no /Catalog entry in the Document"));
         }
     }
 
@@ -101,27 +109,74 @@ public class BookmarkValidationProcess e
     }
 
     /**
-     * This method explores the Outline Item Level and call a validation method on each Outline
Item. If an invalid
+     * This method explores the Outline Item Level and calls a validation method on each
Outline Item. If an invalid
      * outline item is found, the result list is updated.
      * 
      * @param ctx the preflight context.
-     * @param inputItem
-     *            The first outline item of the level
+     * @param inputItem The first outline item of the level.
+     * @param firstObj The first PDF object of the level.
+     * @param lastObj The last PDF object of the level.
      * @return true if all items are valid in this level.
      * @throws ValidationException
      */
-    protected boolean exploreOutlineLevel(PreflightContext ctx, PDOutlineItem inputItem)
throws ValidationException
+    protected boolean exploreOutlineLevel(PreflightContext ctx, PDOutlineItem inputItem,

+            COSObject firstObj, COSObject lastObj) throws ValidationException
     {
         PDOutlineItem currentItem = inputItem;
+        COSObject currentObj = firstObj;
+        Set<COSObject> levelObjects = new HashSet<COSObject>();
+        levelObjects.add(firstObj);
+        boolean result = true;
+
+        if (currentItem != null && inputItem.getPreviousSibling() != null)
+        {
+            addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
+                    "The value of /Prev of first object " + firstObj + " on a level is "

+                            + inputItem.getCOSDictionary().getItem(COSName.PREV) 
+                            + ", but shouldn't exist"));
+            result = false;
+        }
+        
         while (currentItem != null)
         {
+            COSObject realPrevObject = currentObj;
             if (!validateItem(ctx, currentItem))
             {
+                result = false;
+            }
+            currentObj = (COSObject) currentItem.getCOSDictionary().getItem(COSName.NEXT);
+            if (levelObjects.contains(currentObj))
+            {
+                addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
+                        "Loop detected: /Next " + currentObj + " is already in the list"));
                 return false;
             }
+            levelObjects.add(currentObj);
             currentItem = currentItem.getNextSibling();
+            if (currentItem == null)
+            {
+                if (realPrevObject != lastObj)
+                {
+                    addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
+                            "Last object on a level isn't the expected /Last: " + lastObj
+                                    + ", but is " + currentObj));
+                    result = false;
+                }
+            }
+            else 
+            {
+                COSObject prevObject = (COSObject) currentItem.getCOSDictionary().getItem(COSName.PREV);
+                if (prevObject != realPrevObject)
+                {
+                    addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
+                            "The value of /Prev at " + currentObj
+                                    + " doesn't point to previous object " + realPrevObject
+                                    + ", but to " + prevObject));
+                    result = false;
+                }
+            }
         }
-        return true;
+        return result;
     }
 
     /**
@@ -138,10 +193,28 @@ public class BookmarkValidationProcess e
         boolean isValid = true;
         // Dest entry isn't permitted if the A entry is present
         // A entry isn't permitted if the Dest entry is present
-        // If the A enntry is present, the referenced actions is validated
+        // If the A entry is present, the referenced actions is validated
         COSDictionary dictionary = inputItem.getCOSDictionary();
         COSBase dest = dictionary.getItem(COSName.DEST);
         COSBase action = dictionary.getItem(COSName.A);
+        
+        // Prev, Next, First and Last must be indirect objects
+        if (!checkIndirect(ctx, dictionary, COSName.PREV))
+        {
+            return false;
+        }
+        if (!checkIndirect(ctx, dictionary, COSName.NEXT))
+        {
+            return false;
+        }
+        if (!checkIndirect(ctx, dictionary, COSName.FIRST))
+        {
+            return false;
+        }
+        if (!checkIndirect(ctx, dictionary, COSName.LAST))
+        {
+            return false;
+        }
 
         if (action != null && dest != null)
         {
@@ -152,7 +225,8 @@ public class BookmarkValidationProcess e
         else if (action != null)
         {
             ContextHelper.validateElement(ctx, dictionary, ACTIONS_PROCESS);
-        } // else no specific validation
+        }
+        // else no specific validation
 
         // check children
         PDOutlineItem fChild = inputItem.getFirstChild();
@@ -166,12 +240,34 @@ public class BookmarkValidationProcess e
             }
             else
             {
+                COSObject firstObj = (COSObject) dictionary.getItem(COSName.FIRST);
+                COSObject lastObj = (COSObject) dictionary.getItem(COSName.LAST);
+                if ((firstObj == null && lastObj != null) || (firstObj != null &&
lastObj == null))
+                {
+                    addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
+                            "/First and /Last are both required if there are outline entries"));
+                    isValid = false;
+                }
+                
                 // there are some descendants, so dictionary must have a Count entry
-                isValid = isValid && exploreOutlineLevel(ctx, fChild);
+                isValid = isValid && exploreOutlineLevel(ctx, fChild, firstObj, lastObj);
             }
         }
 
         return isValid;
     }
 
+    // verify that if the named item exists, that it is is an indirect object
+    private boolean checkIndirect(PreflightContext ctx, COSDictionary dictionary, COSName
name)
+    {
+        COSBase item = dictionary.getItem(name);
+        if (item != null && !(item instanceof COSObject))
+        {
+            addValidationError(ctx, new ValidationError(ERROR_SYNTAX_TRAILER_OUTLINES_INVALID,
+                    "/" + name.getName() + " entry must be an indirect object"));
+            return false;
+        }
+        return true;
+    }
+
 }



Mime
View raw message