[ 
https://issues.apache.org/jira/browse/TIKA-5057?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18141933#comment-18141933
 ] 

ASF GitHub Bot commented on TIKA-5057:
--------------------------------------

Copilot commented on code in PR #3564:
URL: https://github.com/apache/tika/pull/3564#discussion_r4524528541


##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/onenote/OneNoteDirectFileResource.java:
##########
@@ -23,60 +23,89 @@
 import java.nio.ByteBuffer;
 
 /**
- * This is copied mostly from the the
- * former org.apache.tika.parser.mp4.DirectFileReadDataSource
- * <p>
- * Implements a simple way to encapsulate a {@link 
org.apache.tika.io.TikaInputStream} that you
- * will have to seek,read,repeat while parsing OneNote contents.
+ * Seekable reads over a OneNote file. The parser mostly reads a few bytes at 
a time, so reads
+ * come from a window of the file rather than one system call each.
  */
 class OneNoteDirectFileResource implements Closeable {
 
-    private static final int TRANSFER_SIZE = 8192;
+    private static final int WINDOW_SIZE = 64 * 1024;
 
     private final RandomAccessFile raf;
+    private final long size;
+    private final byte[] window = new byte[WINDOW_SIZE];
+    // file offset of window[0], and how many bytes of the window are valid
+    private long windowStart;
+    private int windowLength;
+    private long position;
 
     public OneNoteDirectFileResource(File f) throws IOException {
         this.raf = new RandomAccessFile(f, "r");
+        this.size = raf.length();
     }
 
     public int read() throws IOException {
-        return raf.read();
+        if (position >= size) {
+            return -1;
+        }
+        if (!inWindow(position)) {
+            fill(position);
+        }
+        return window[(int) (position++ - windowStart)] & 0xFF;
     }
 
     public int read(ByteBuffer byteBuffer) throws IOException {
         int len = byteBuffer.remaining();
         int totalRead = 0;
-        int bytesRead = 0;
-        byte[] buf = new byte[TRANSFER_SIZE];
-        while (totalRead < len) {
-            int bytesToRead = Math.min((len - totalRead), TRANSFER_SIZE);
-            bytesRead = raf.read(buf, 0, bytesToRead);
-            if (bytesRead < 0) {
-                break;
-            } else {
-                totalRead += bytesRead;
+        while (totalRead < len && position < size) {
+            if (!inWindow(position)) {
+                if (len - totalRead >= WINDOW_SIZE) {
+                    // large reads bypass the window
+                    int n = raf.getChannel().read(byteBuffer, position);
+                    if (n < 0) {
+                        break;
+                    }
+                    position += n;
+                    totalRead += n;
+                    continue;
+                }
+                fill(position);
             }
-            byteBuffer.put(buf, 0, bytesRead);
+            int offset = (int) (position - windowStart);
+            int n = Math.min(len - totalRead, windowLength - offset);
+            byteBuffer.put(window, offset, n);
+            position += n;
+            totalRead += n;
         }

Review Comment:
   The read loop can stall indefinitely if a read returns 0 bytes (e.g., 
`FileChannel.read(...)` returning 0, or `fill()` producing `windowLength == 
0`). In that case `position`/`totalRead` do not advance and the `while` loop 
never terminates. Handle `n == 0` explicitly (e.g., treat as an error or break) 
and in `fill()` throw if `n <= 0` when `pos < size` to avoid infinite loops on 
unexpected short/empty reads.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/onenote/OneNoteDirectFileResource.java:
##########
@@ -23,60 +23,89 @@
 import java.nio.ByteBuffer;
 
 /**
- * This is copied mostly from the the
- * former org.apache.tika.parser.mp4.DirectFileReadDataSource
- * <p>
- * Implements a simple way to encapsulate a {@link 
org.apache.tika.io.TikaInputStream} that you
- * will have to seek,read,repeat while parsing OneNote contents.
+ * Seekable reads over a OneNote file. The parser mostly reads a few bytes at 
a time, so reads
+ * come from a window of the file rather than one system call each.
  */
 class OneNoteDirectFileResource implements Closeable {
 
-    private static final int TRANSFER_SIZE = 8192;
+    private static final int WINDOW_SIZE = 64 * 1024;
 
     private final RandomAccessFile raf;
+    private final long size;
+    private final byte[] window = new byte[WINDOW_SIZE];
+    // file offset of window[0], and how many bytes of the window are valid
+    private long windowStart;
+    private int windowLength;
+    private long position;
 
     public OneNoteDirectFileResource(File f) throws IOException {
         this.raf = new RandomAccessFile(f, "r");
+        this.size = raf.length();
     }
 
     public int read() throws IOException {
-        return raf.read();
+        if (position >= size) {
+            return -1;
+        }
+        if (!inWindow(position)) {
+            fill(position);
+        }
+        return window[(int) (position++ - windowStart)] & 0xFF;
     }
 
     public int read(ByteBuffer byteBuffer) throws IOException {
         int len = byteBuffer.remaining();
         int totalRead = 0;
-        int bytesRead = 0;
-        byte[] buf = new byte[TRANSFER_SIZE];
-        while (totalRead < len) {
-            int bytesToRead = Math.min((len - totalRead), TRANSFER_SIZE);
-            bytesRead = raf.read(buf, 0, bytesToRead);
-            if (bytesRead < 0) {
-                break;
-            } else {
-                totalRead += bytesRead;
+        while (totalRead < len && position < size) {
+            if (!inWindow(position)) {
+                if (len - totalRead >= WINDOW_SIZE) {
+                    // large reads bypass the window
+                    int n = raf.getChannel().read(byteBuffer, position);
+                    if (n < 0) {
+                        break;
+                    }
+                    position += n;
+                    totalRead += n;
+                    continue;
+                }
+                fill(position);
             }
-            byteBuffer.put(buf, 0, bytesRead);
+            int offset = (int) (position - windowStart);
+            int n = Math.min(len - totalRead, windowLength - offset);
+            byteBuffer.put(window, offset, n);
+            position += n;
+            totalRead += n;
         }
-        if (bytesRead < 0 && position() == size() && 
byteBuffer.hasRemaining()) {
+        if (totalRead < len && position == size) {
             throw new IOException("End of stream reached earlier than 
expected");
         }
-        return ((bytesRead < 0) && (totalRead == 0)) ? -1 : totalRead;
+        return totalRead == 0 && len > 0 ? -1 : totalRead;
+    }
+
+    private boolean inWindow(long pos) {
+        return pos >= windowStart && pos < windowStart + windowLength;
+    }
+
+    private void fill(long pos) throws IOException {
+        raf.seek(pos);
+        int n = raf.read(window, 0, (int) Math.min(WINDOW_SIZE, size - pos));
+        windowStart = pos;
+        windowLength = Math.max(n, 0);
     }

Review Comment:
   The read loop can stall indefinitely if a read returns 0 bytes (e.g., 
`FileChannel.read(...)` returning 0, or `fill()` producing `windowLength == 
0`). In that case `position`/`totalRead` do not advance and the `while` loop 
never terminates. Handle `n == 0` explicitly (e.g., treat as an error or break) 
and in `fill()` throw if `n <= 0` when `pos < size` to avoid infinite loops on 
unexpected short/empty reads.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/onenote/OneNoteTreeWalkerOptions.java:
##########
@@ -26,30 +26,41 @@
  * Options when walking the one note tree.
  */
 public class OneNoteTreeWalkerOptions implements Serializable {
-    private boolean crawlAllFileNodesFromRoot = true;
+    private boolean crawlAllFileNodesFromRoot = false;
+    private boolean includeDeletedContent = false;
     private boolean onlyLatestRevision = true;
     private Set<OneNotePropertyEnum> utf16PropertiesToPrint = new HashSet<>(
-            Arrays.asList(OneNotePropertyEnum.ImageFilename, 
OneNotePropertyEnum.Author));
+            Arrays.asList(OneNotePropertyEnum.ImageFilename, 
OneNotePropertyEnum.ImageAltText,
+                    OneNotePropertyEnum.Author, 
OneNotePropertyEnum.FolderChildFilename));
 
     /**
-     * Do this to ignore revisions and just parse all file nodes from the root 
recursively.
+     * Whether text comes from every revision, page history included, rather 
than only each
+     * page's current revision. Metadata and attachments come from every 
revision either way.
      */
     public boolean isCrawlAllFileNodesFromRoot() {
         return crawlAllFileNodesFromRoot;
     }

Review Comment:
   The option name `crawlAllFileNodesFromRoot` now appears to control 'include 
page history' semantics (per the updated Javadoc and its use from 
`OneNoteParserConfig.isIncludePageHistory()`), which makes the API misleading. 
Consider introducing an explicitly named option like `includePageHistory` on 
`OneNoteTreeWalkerOptions` (and either deprecating the old name or restoring 
its original meaning) to prevent incorrect usage by callers constructing 
options directly.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/onenote/OneNoteTreeWalkerOptions.java:
##########
@@ -26,30 +26,41 @@
  * Options when walking the one note tree.
  */
 public class OneNoteTreeWalkerOptions implements Serializable {
-    private boolean crawlAllFileNodesFromRoot = true;
+    private boolean crawlAllFileNodesFromRoot = false;
+    private boolean includeDeletedContent = false;

Review Comment:
   The option name `crawlAllFileNodesFromRoot` now appears to control 'include 
page history' semantics (per the updated Javadoc and its use from 
`OneNoteParserConfig.isIncludePageHistory()`), which makes the API misleading. 
Consider introducing an explicitly named option like `includePageHistory` on 
`OneNoteTreeWalkerOptions` (and either deprecating the old name or restoring 
its original meaning) to prevent incorrect usage by callers constructing 
options directly.



##########
tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/onenote/OneNoteTreeWalkerOptions.java:
##########
@@ -26,30 +26,41 @@
  * Options when walking the one note tree.
  */
 public class OneNoteTreeWalkerOptions implements Serializable {
-    private boolean crawlAllFileNodesFromRoot = true;
+    private boolean crawlAllFileNodesFromRoot = false;
+    private boolean includeDeletedContent = false;
     private boolean onlyLatestRevision = true;
     private Set<OneNotePropertyEnum> utf16PropertiesToPrint = new HashSet<>(
-            Arrays.asList(OneNotePropertyEnum.ImageFilename, 
OneNotePropertyEnum.Author));
+            Arrays.asList(OneNotePropertyEnum.ImageFilename, 
OneNotePropertyEnum.ImageAltText,
+                    OneNotePropertyEnum.Author, 
OneNotePropertyEnum.FolderChildFilename));
 
     /**
-     * Do this to ignore revisions and just parse all file nodes from the root 
recursively.
+     * Whether text comes from every revision, page history included, rather 
than only each
+     * page's current revision. Metadata and attachments come from every 
revision either way.
      */
     public boolean isCrawlAllFileNodesFromRoot() {
         return crawlAllFileNodesFromRoot;
     }
 
     /**
-     * Do this to ignore revisions and just parse all file nodes from the root 
recursively.
-     *
-     * @param crawlAllFileNodesFromRoot
-     * @return
+     * @param crawlAllFileNodesFromRoot whether to emit text from every 
revision
+     * @return this
      */
     public OneNoteTreeWalkerOptions setCrawlAllFileNodesFromRoot(
             boolean crawlAllFileNodesFromRoot) {
         this.crawlAllFileNodesFromRoot = crawlAllFileNodesFromRoot;
         return this;
     }

Review Comment:
   The option name `crawlAllFileNodesFromRoot` now appears to control 'include 
page history' semantics (per the updated Javadoc and its use from 
`OneNoteParserConfig.isIncludePageHistory()`), which makes the API misleading. 
Consider introducing an explicitly named option like `includePageHistory` on 
`OneNoteTreeWalkerOptions` (and either deprecating the old name or restoring 
its original meaning) to prevent incorrect usage by callers constructing 
options directly.





> General improvements to onenote parser
> --------------------------------------
>
>                 Key: TIKA-5057
>                 URL: https://issues.apache.org/jira/browse/TIKA-5057
>             Project: Tika
>          Issue Type: Task
>            Reporter: Tim Allison
>            Priority: Trivial
>
> When I have extra tokens, I'll have an agent focus on a parser at a time for 
> robustness and simplicity. Let's start with onenote. CC [~henrylindemanglean]
> :robot: swarm recommends:
> {noformat}
>   Native .one path (the common format, unrelated to TIKA-5055):
>   1. Exponential parse on shared child lists (OneNotePtr:714). A file-node 
> list referenced twice is parsed twice. Only the current path is checked for 
> cycles, so a crafted file of a few KB blows up as 2^depth. Three reviewers 
> found this independently.
>   2. Exponential tree walk (OneNoteTreeWalker:648). The same shape: shared 
> objects are walked once per reference path. Each visit also builds and keeps 
> a HashMap of decoded properties, which OneNoteParser:144 throws away.
>   3. Wrong references, lost text (OneNotePtr:1185). The OID/OSID/ContextID 
> stream position is copied to a local variable and never written back. Every 
> reference property starts again at stream[0], so later references are never 
> walked. It also removes the
>      cap on reference counts.
>   4. Duplicate text in real files (OneNoteTreeWalker:569-592). Reproduced: 
> testOneNote2.one produces 176 paragraphs, 105 of them "Untitled picture.png". 
> Image filenames and TextExtendedAscii text are never deduplicated and ignore 
> the revision filter.
>   5. Unchecked counts and budgets (OneNotePtr:838, :518, :809):
>      - ObjectInfoDependencyOverrideData counts are unbounded.
>      - The global-ID cap applies per node, with no total.
>      - A negative FileDataStoreObject length gets through.
>   6. Smaller issues:
>      - Embedded file data is read in full before shouldParseEmbedded is asked 
> (OneNoteTreeWalker:435).
>      - The UTF-16 NUL terminator stays in author names and image filenames.
>   Legacy string dump, used whenever a parse falls back 
> (OneNoteLegacyDumpStrings:95):
>   - Never resets its buffer at 1 MB boundaries. A long run is emitted again 
> at every boundary, so work grows quadratically and normal files over 1 MB get 
> duplicated text.
>   - Small bugs: it drops the last byte of each buffer, 
> Character.isWhitespace(i) tests the loop index instead of the character, and 
> split(" ") allocates one String per word.
>   Fallback after a partial walk (OneNoteParser:172, :197): on both paths, the 
> full legacy dump is added after whatever the failed walk already wrote, so 
> content appears twice.
>   FSSHTTPB:
>   - GUIDs are MD5 hashes, not decoded (BitReader:140, AdapterHelper:37). They 
> come from UUID.nameUUIDFromBytes. As a result:
>     - Encrypted sections are never detected; the check at 
> MSOneStoreParser:328 can't match.
>     - Embedded relationship IDs contain hashed GUIDs.
>   - Every cell mapping copies its whole revision chain 
> (MSOneStoreParser:362), so the cost grows with mappings × objects.
>   - Slow spots:
>     - removeSupersededObjects is O(n²) (:393).
>     - resourceInfoFromReferencingObject scans every object for each unnamed 
> blob (MSOneStorePackage:1140).
>     - hasUsablePicture copies a whole blob just to check it's non-empty 
> (:1118).
>   - Small port bugs:
>     - cProperties is read as signed, so values ≥ 0x8000 abort the parse.
>     - An undefined property type throws an NPE.
>     - StorageManifestSchemaGUID reads from offset 0 instead of the current 
> position.
>     - BitConverter's bounds check ignores the index.
>   Dead code:
>   - The serialize/write path: 65 files, about 970 lines.
>   - The FSSHTTPD chunking/build side.
>   - getContent() and its builders.
>   - Unreferenced classes: Error, PropertyIDType, RequestTypes, UMath.
>   - Debug print() methods and option branches that are never set.
> {noformat}



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to