This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new ddff151c27 bump bookmarks limit (#3212)
ddff151c27 is described below

commit ddff151c274c7d3df3405c59fdd00a877aee8873
Author: Tim Allison <[email protected]>
AuthorDate: Sun Sep 20 08:53:28 2026 -0400

    bump bookmarks limit (#3212)
---
 CHANGES.txt                                        | 13 +++----
 .../apache/tika/parser/pdf/AbstractPDF2XHTML.java  | 14 ++++++--
 .../org/apache/tika/parser/pdf/PDFParserTest.java  | 40 ++++++++++++++++++++++
 3 files changed, 57 insertions(+), 10 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 41a40a0203..225d306e66 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,14 +2,11 @@ Release 4.1.0 - unreleased
 
    * A file name no longer turns plain text into a message type (emlx, mbox,
      rfc822, news, HTTP capture) when the type's magic has already rejected
-     the bytes: prose named .emlx is text/plain. An HTTP response captured
-     without its version line ("200 OK", then headers) is detected as
-     application/x-httpresponse (TIKA-4890).
-
-   * Apple Mail emlx files are detected as message/x-emlx (real files pad the
-     byte-count line to 10 columns and were falling through to text/html or
-     message/rfc822), and RFC822Parser now parses them, dropping the byte-count
-     line and, when the count is intact, the trailing plist (TIKA-4890).
+     the bytes (TIKA-4890).
+
+   * Apple Mail emlx files are detected as message/x-emlx and RFC822Parser 
+     now parses them, dropping the byte-count line and, 
+     when the count is intact, the trailing plist (TIKA-4890).
 
    * The tika-server UAT gains an inference pass (TIKA-4913).
      
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
index cb73cdc034..eff1fbb3e5 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/AbstractPDF2XHTML.java
@@ -148,7 +148,8 @@ class AbstractPDF2XHTML extends PDFTextStripper {
      * the embedded document tree.
      */
     private final static int MAX_RECURSION_DEPTH = 100;
-    private final static int MAX_BOOKMARK_ITEMS = 10000;
+    // a transcript's word index is ~12k items; the budget bounds a hostile 
file, not a real one
+    private final static int MAX_BOOKMARK_ITEMS = 100000;
     /** Deeper bookmarks are written as items of the deepest list, within the 
XML nesting limit. */
     private final static int MAX_BOOKMARK_DEPTH = 50;
 
@@ -1466,8 +1467,13 @@ class AbstractPDF2XHTML extends PDFTextStripper {
         xhtml.startElement("ul");
         int lists = 1;
         int items = 0;
+        boolean truncated = false;
         while (true) {
-            if (current == null || seen.contains(current) || items > 
MAX_BOOKMARK_ITEMS) {
+            if (items >= MAX_BOOKMARK_ITEMS && current != null && 
!seen.contains(current)) {
+                truncated = true;
+                current = null;
+            }
+            if (current == null || seen.contains(current)) {
                 if (parents.isEmpty()) {
                     break;
                 }
@@ -1499,6 +1505,10 @@ class AbstractPDF2XHTML extends PDFTextStripper {
             }
         }
         xhtml.endElement("ul");
+        if (truncated) {
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    "bookmark outline truncated at " + MAX_BOOKMARK_ITEMS + " 
items");
+        }
     }
 
     void extractAcroForm(PDDocument pdf) throws IOException, SAXException, 
TikaException {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
index e48e93a0ae..63d9ccc407 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/test/java/org/apache/tika/parser/pdf/PDFParserTest.java
@@ -613,6 +613,46 @@ public class PDFParserTest extends TikaTest {
         assertTrue(i < j);
     }
 
+    /** A wide outline (a transcript's word index) is written whole; past the 
budget the cut is recorded. */
+    @Test
+    public void testWideBookmarksAndTheBudget() throws Exception {
+        for (int n : new int[]{12000, 100500}) {
+            try (PDDocument doc = new PDDocument()) {
+                doc.addPage(new PDPage(PDRectangle.LETTER));
+                PDDocumentOutline outline = new PDDocumentOutline();
+                doc.getDocumentCatalog().setDocumentOutline(outline);
+                for (int i = 0; i < n / 4; i++) {
+                    PDOutlineItem word = new PDOutlineItem();
+                    word.setTitle("word" + i);
+                    outline.addLast(word);
+                    for (int j = 0; j < 3; j++) {
+                        PDOutlineItem ref = new PDOutlineItem();
+                        ref.setTitle("ref" + i + "-" + j);
+                        word.addLast(ref);
+                    }
+                }
+                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+                doc.save(bos);
+                Metadata metadata = new Metadata();
+                String xml;
+                try (TikaInputStream tis = 
TikaInputStream.get(bos.toByteArray())) {
+                    ToXMLContentHandler handler = new ToXMLContentHandler();
+                    new PDFParser().parse(tis, handler, metadata, new 
ParseContext());
+                    xml = handler.toString();
+                }
+                int items = xml.split("<li>").length - 1;
+                String warn = String.join(" ", 
metadata.getValues(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING));
+                if (n <= 100000) {
+                    assertEquals(n, items, "every item written");
+                    assertFalse(warn.contains("bookmark outline truncated"), 
warn);
+                } else {
+                    assertEquals(100000, items, "cut at the budget");
+                    assertContains("bookmark outline truncated at 100000 
items", warn);
+                }
+            }
+        }
+    }
+
     /**
      * An outline as deep as the item budget allows, each item the only child 
of the last,
      * on a thread with a stack a quarter of the default: every title is 
written, lists nest

Reply via email to