Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b22591e
TIKA-5057: remove dead write-side and FSSHTTPD build code from the …
tballison Nov 6, 2026
9e07233
TIKA-5057: bound shared file node lists and walker revisits in the na…
tballison Nov 7, 2026
eef9850
TIKA-5057: fix buffer-boundary duplication and filters in the OneNote…
tballison Nov 6, 2026
7529c41
TIKA-5057: reject malformed OneNote structures up front
tballison Nov 6, 2026
a14dae5
TIKA-5057: judge OneNote legacy-dump strings with the text-quality de…
tballison Nov 6, 2026
9dd183f
TIKA-5057: ignore reserved object-declaration bits; fall back when a …
tballison Nov 7, 2026
8ba5f16
TIKA-5057: copy an FSSHTTPB embedded blob only after the extractor ac…
tballison Nov 7, 2026
5f78003
TIKA-5057: extract OneNote page text skipped as version history
tballison Nov 7, 2026
45f61b4
TIKA-5057: clean OneNote text: windows-1252 extended ASCII, no NULs, …
tballison Nov 7, 2026
1ba88be
TIKA-5057: end OneNote file node lists at the transaction log's commi…
tballison Nov 7, 2026
161e679
TIKA-5057: accept OneNote file node list IDs below 0x10
tballison Nov 7, 2026
17ff081
TIKA-5057: emit OneNote pictures as img with file name and alt text
tballison Nov 7, 2026
4cefa3c
TIKA-5057: extract OneNote handwriting recognition and OCR alt text a…
tballison Nov 7, 2026
5cc9480
TIKA-5057: list OneNote table-of-contents sections; localized OCR alt…
tballison Nov 8, 2026
ddbe3f1
TIKA-5057: read OneNote 2007 paragraph text; legacy dump for OneNote …
tballison Nov 8, 2026
2d92646
TIKA-5057: treat empty OneNote file node references as unwritten list…
tballison Nov 8, 2026
d4cc250
TIKA-5057: EncryptedDocumentException for password-protected OneNote …
tballison Nov 8, 2026
e6f25ec
TIKA-5057: align FSSHTTPB object references to the OID stream...
tballison Nov 8, 2026
98d82d6
TIKA-5057: note that the empty global ID table fallback is inferred, …
tballison Nov 8, 2026
e8172c5
TIKA-5057: drop U+FFFC object placeholders from OneNote text
tballison Nov 9, 2026
5885f97
TIKA-5057: emit only the current revision of OneNote pages; resolve o…
tballison Nov 9, 2026
40caa69
TIKA-5057: OneNoteParserConfig.includePageHistory restores text from …
tballison Nov 9, 2026
2755248
TIKA-5057: TikaException for a OneNote file shorter than its header
tballison Nov 9, 2026
46b8740
TIKA-5057: scale the OneNote property value budget with file size; sk…
tballison Nov 9, 2026
b1d439f
TIKA-5057: one OneNote property value budget per document, one value …
tballison Nov 9, 2026
1629b15
TIKA-5057: OneNote FSSHTTPB deleted pages behind includeDeletedConten…
tballison Nov 9, 2026
ff4a423
Merge remote-tracking branch 'upstream/main' into TIKA-5057
tballison Nov 9, 2026
6cc4b27
TIKA-5057: buffer OneNote file reads instead of one system call per f…
tballison Nov 9, 2026
85a30be
update CHANGES.txt
tballison Nov 9, 2026
9f02a53
TIKA-5057: stop building the unused OneNote walk map; allocation-free…
tballison Nov 9, 2026
3adb99c
TIKA-5057: trim boilerplate comments and javadoc in OneNote parser
tballison Nov 10, 2026
fbcfba6
TIKA-5057: fail OneNote reads when the file shrinks instead of loopin…
tballison Nov 10, 2026
cc6d894
TIKA-5057: pick the .one current revision by its latest role label; i…
tballison Nov 10, 2026
2ac7391
TIKA-5057: OneNote correctness fixes from review
tballison Nov 10, 2026
fc52ead
TIKA-5057: bound OneNote parsing against crafted structure reuse
tballison Nov 10, 2026
5c55e82
TIKA-5057: remove unused OneNote accessors and fold walker options in…
tballison Nov 10, 2026
88ab591
TIKA-5057: merge FSSHTTPB fixed-size properties, share the stream obj…
tballison Nov 10, 2026
8a48d85
TIKA-5057: allocate OneNote property values lazily and read fixed-siz…
tballison Nov 10, 2026
8438005
TIKA-5057: read FSSHTTPB GUIDs in Microsoft byte order instead of has…
tballison Nov 10, 2026
7282493
TIKA-5057: include original authors in dc:creator for .one sections, …
tballison Nov 10, 2026
02cbb81
TIKA-5057: .one metadata describes the current revisions unless inclu…
tballison Nov 10, 2026
c2feb30
TIKA-5057: read .one property values when the walk first reaches them
tballison Nov 10, 2026
245ec47
TIKA-5057: allocate .one file node fields only for the node's own type
tballison Nov 10, 2026
89044f3
TIKA-5057: copilot fix, count .one dependency-chain lookups against v…
tballison Nov 10, 2026
a1cfdea
TIKA-5057: bound OneNote walk CPU by property count, walk shared FSSH…
tballison Nov 11, 2026
421000e
TIKA-5057: tersify OneNote CHANGES entries
tballison Nov 11, 2026
bb03afa
Merge remote-tracking branch 'upstream/main' into TIKA-5057
tballison Nov 11, 2026
ba6949c
TIKA-5057: bound OneNote ink, text-run and revision-chain lookups; st…
tballison Nov 11, 2026
3ae3d90
TIKA-5057: bound FSSHTTPB work on shared revisions and repeated refer…
tballison Nov 11, 2026
d67c5f5
TIKA-5057: fall back to the text dump for empty .onetoc2 walks; doc a…
tballison Nov 11, 2026
0201e04
TIKA-5057: charge memoized raw reads and every FSSHTTPB cell loop to …
tballison Nov 11, 2026
ae72d0c
TIKA-5057: comment cleanups from review
tballison Nov 11, 2026
af32bb1
fallback to raw dump
tballison Nov 11, 2026
76db8a8
TIKA-5057: keep excluded OneNote content out of the legacy dump; page…
tballison Nov 11, 2026
14131f1
TIKA-5057: hide fully deleted FSSHTTPB sections; dedupe shared revisi…
tballison Nov 12, 2026
9c6bb86
TIKA-5057: tests for withheld content, bounds and partial walks; getB…
tballison Nov 12, 2026
a4cbee5
TIKA-5057: OneNote attachment names and original paths
tballison Nov 12, 2026
48d4d07
TIKA-5057: emit OneNote note tag labels
tballison Nov 12, 2026
e582a9e
TIKA-5057: emit OneNote picture hyperlinks
tballison Nov 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .skills/users/file-forensics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ the defaults are NOT the same everywhere:
config (it prints a banner saying so) that already enables the marquee
items — `parseIncrementalUpdates`, `extractMacros`, `extractActions`,
`extractInlineImages`. On tika-app, `file-forensics-config.json` still adds
`extractFontNames`, `extractScripts`, `includeMissingRows`,
`extractFontNames`, `extractScripts`, `includeMissingRows`, OneNote page history,
deleted/moved-content for legacy `.doc` (on `.docx` the convenience
config already includes it, so testing this switch on a docx shows no
diff — that's expected), and pins `accessCheckMode` to
Expand All @@ -212,6 +212,19 @@ The full switch list:
embedded entries), `includeDeletedContent` and `includeMoveFromContent`
(tracked-change deletions and moved-away text in the output),
`includeMissingRows` (spreadsheet row gaps)
- `one-note-parser.includePageHistory` — OneNote text and metadata
(authors, timestamps, page GUIDs) include every earlier revision and saved
page version, not only each page's current content. Off, earlier authors
and dates are absent, as for the final state of an incrementally updated
PDF. Exception: `.one` (2010–2016) attachments come out either way,
including files only an earlier page version used. In `.one` files saved
by OneNote 2010–2016, history is not marked as such; it is mixed in with
the current text. In newer (FSSHTTPB) sections each saved page version is
its own `<div class="page history">`; edits within a version are merged.
- `one-note-parser.includeDeletedContent` — newer (FSSHTTPB) OneNote
sections still store pages moved to the recycle bin; with this set they
come out as `<div class="page deleted">`, each older version as
`<div class="page deleted history">` (the text may be only in those).
- `jsoup-parser.extractScripts` — script bodies in HTML (including HTML
email bodies and HTML attachments) become their own `MACRO`-typed embedded
entries instead of being silently dropped: JavaScript in a document is
Expand Down Expand Up @@ -268,7 +281,9 @@ The file's own story about its origin — read it as testimony, not fact:
(`Normal.dotm` = stock Word), total edit time, claimed created/modified
timestamps
- **`tk:orig-resource-name`** — for xlsx, the absolute path where the file
was last saved (Excel records it in the workbook): drive letters, network
was last saved (Excel records it in the workbook); on an embedded entry
(OLE packages, OneNote attachments and inserted pictures), the path the
file had where it was inserted: drive letters, network
shares, and `C:\Users\<name>\...` usernames leak here. (The `tk:` prefix
here means Tika surfaced it; the *value* is still the file's own record —
treat it as a claim like the rest of this section.)
Expand Down
6 changes: 6 additions & 0 deletions .skills/users/file-forensics/file-forensics-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
"includeMissingRows": true
}
},
{
"one-note-parser": {
"includePageHistory": true,
"includeDeletedContent": true
}
},
{
"jsoup-parser": {
"extractScripts": true
Expand Down
60 changes: 60 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ Release 4.3.0 - ???
untrained scripts. JunkFilterEncodingDetector takes a HashedJunkModel instead of a
TextQualityDetector (TIKA-5027).

* OneNote sections, .one and FSSHTTPB, give each page's current text and metadata
only, not every stored revision. Set includePageHistory on one-note-parser, in the
config or per request, for earlier revisions and saved page versions, and
includeDeletedContent for FSSHTTPB recycle-bin pages. All .one attachments are still
extracted. A file over a parse limit throws TikaMemoryLimitException instead of
falling back to the legacy text dump. OneNoteTreeWalkerOptions, the public
OneNoteLegacyDumpStrings settings and most public classes of the OneNote structure
model are removed (TIKA-5057).

* FetcherManager.getFetcher and EmitterManager.getEmitter return a Lease
(AbstractComponentManager.getComponent is now acquire); close it when done with
the component and anything it returned. Pipes now closes a
Expand Down Expand Up @@ -169,6 +178,57 @@ Release 4.3.0 - ???

* MP4 and QuickTime files with a box over 2 GB, typically an mdat in front of
moov, keep their metadata: the walk skips such a box instead of stopping (TIKA-5051).

* OneNote FSSHTTPB sections extract each stored file once, not once per reference
to it (TIKA-5057).

* OneNote attachments and pictures carry their stored file name (resourceName) and,
for inserted files, the path they were inserted from (tk:orig-resource-name). .one
sections named none before, and an FSSHTTPB file's icon no longer takes the file's
name and ATTACHMENT type (TIKA-5057).

* OneNote note tag labels ("To Do", "Important", custom tags) are written before the
paragraphs they tag, as <p class="note-tag"> (TIKA-5057).

* OneNote pictures that are hyperlinks are written inside <a href>, so their URLs reach
link handlers (TIKA-5057).

* OneNoteParser parses .onetoc2 notebook tables of contents, listing their section
names (TIKA-5057).

* OneNote sections that fell back to the legacy text dump now parse structurally,
including OneNote 2007 sections, Aspose.Note files and sections with leftover bytes
from earlier saves. OneNote 2003 sections fall back to the dump instead of failing
(TIKA-5057).

* OneNote text that was dropped or garbled is extracted: objects edited in later
revisions, text behind element child references, and paragraphs with hyperlinks
(now <a href>). Extended ASCII is read as windows-1252, and NULs and U+FFFC
placeholders are removed (TIKA-5057).

* OneNote output adds pictures as <img> with alt text, OneNote's OCR text as
<div class="ocr">, and .one handwriting recognition as <span class="handwriting">
(TIKA-5057).

* Password-protected OneNote sections throw EncryptedDocumentException; FSSHTTPB
ones were never detected because their GUIDs were MD5-hashed. dc:creator includes
original authors. A file shorter than its header throws a TikaException
(TIKA-5057).

* OneNote parsing bounds CPU and memory on crafted files with limits scaled to file
size, and rejects more malformed structures up front (TIKA-5057).

* OneNote .one sections allocate far less and small ones parse much faster, reading
through a small page cache instead of one system call per field. Large
multi-revision sections can be slower, as every committed revision is now read
(TIKA-5057).

* OneNote legacy text dump: strings crossing a 1 MB read boundary are written once,
and strings are judged by the text-quality detector (tika-ml-junkdetect) when it is
on the class path (TIKA-5057).

* tika-ml-junkdetect: JunkDetector has a public no-arg constructor, so ServiceLoader
finds it on the class path (TIKA-5057).

* WOFF and WOFF2 fonts (font/woff, font/woff2), SquashFS images
(application/vnd.squashfs), Adobe Swatch Exchange files, PalmDOC e-books and binary
Expand Down
7 changes: 5 additions & 2 deletions docs/modules/ROOT/pages/advanced/junk-detection.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
= Text Quality Scoring (Junk Detection)

The `tika-ml-junkdetect` module scores how much a string reads like real text, in any
language it was trained on, without being told the language. Tika uses it in two places:
language it was trained on, without being told the language. Tika uses it in three places:

* **charset detection**: of several decodes of the same bytes, which one is the real text
(see xref:advanced/charset-detection-design.adoc[Charset Detection Pipeline]);
* **tika-eval**: the `tika-eval:languageness` score of extracted text, plus the worst window
and coverage.
and coverage;
* **OneNote legacy text dump**: which scraped strings to keep when a OneNote file cannot be
parsed structurally. Without `tika-ml-junkdetect` on the class path the dump falls back
to a letter-ratio check.

== What it detects

Expand Down
6 changes: 5 additions & 1 deletion docs/modules/ROOT/pages/formats.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ and
link:{api}/org/apache/tika/parser/microsoft/ooxml/xwpf/ml2006/Word2006MLParser.html[Word2006MLParser].

link:{api}/org/apache/tika/parser/microsoft/onenote/OneNoteParser.html[OneNoteParser]
extracts text from OneNote notebooks, and
extracts text from OneNote sections (`.one`) and notebook tables of contents
(`.onetoc2`); by default only each page's current version. Set
`includePageHistory` on `one-note-parser` for earlier versions and
`includeDeletedContent` for pages in the recycle bin (newer, FSSHTTPB
sections only), in the config or per request.
link:{api}/org/apache/tika/parser/microsoft/MSOwnerFileParser.html[MSOwnerFileParser]
pulls basic metadata from the temporary lock ("owner") files Office leaves
beside open documents.
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/partials/supported-formats.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
** `application/x-chm`
* `org.apache.tika.parser.microsoft.onenote.` link:{tika-javadoc-url}/org/apache/tika/parser/microsoft/onenote/OneNoteParser.html[OneNoteParser]
** `application/onenote; format=one`
** `application/onenote; format=onetoc2`
* `org.apache.tika.parser.microsoft.ooxml.` link:{tika-javadoc-url}/org/apache/tika/parser/microsoft/ooxml/OOXMLParser.html[OOXMLParser]
** `application/vnd.ms-excel.addin.macroenabled.12`
** `application/vnd.ms-excel.sheet.binary.macroenabled.12`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,49 @@ public final class JunkDetector implements TextQualityDetector {
*/
public static final float MIN_COVERAGE = 0.9f;

private static volatile HashedJunkModel bundledModel;

private final HashedJunkModel model;

/**
* Loads the bundled model. {@link java.util.ServiceLoader} needs this constructor on the
* class path; {@link #provider()} serves only the module path.
*
* @throws UncheckedIOException if the bundled model cannot be loaded
*/
public JunkDetector() {
this(provider().model);
}

public JunkDetector(HashedJunkModel model) {
if (model == null) {
throw new IllegalArgumentException("model");
}
this.model = model;
}

/** Loads the bundled model. */
/** The bundled model, read once and shared: it is immutable and several megabytes. */
public static JunkDetector loadFromClasspath() throws IOException {
HashedJunkModel model = bundledModel;
if (model == null) {
synchronized (JunkDetector.class) {
model = bundledModel;
if (model == null) {
model = readBundledModel();
bundledModel = model;
}
}
}
return new JunkDetector(model);
}

private static HashedJunkModel readBundledModel() throws IOException {
InputStream is = JunkDetector.class.getClassLoader().getResourceAsStream(DEFAULT_MODEL_RESOURCE);
if (is == null) {
throw new IOException("Model resource not found on classpath: " + DEFAULT_MODEL_RESOURCE);
}
try (InputStream in = is) {
return new JunkDetector(HashedJunkModel.load(in));
return HashedJunkModel.load(in);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@

import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.ServiceLoader;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import org.apache.tika.quality.TextQualityComparison;
import org.apache.tika.quality.TextQualityDetector;
import org.apache.tika.quality.TextQualityScore;

/**
Expand All @@ -43,6 +45,15 @@ static void loadModel() throws Exception {
detector = JunkDetector.loadFromClasspath();
}

@Test
public void testServiceLoaderFindsTheBundledDetector() {
// the registration needs a public no-arg constructor on the class path
TextQualityDetector detector = ServiceLoader.load(TextQualityDetector.class)
.findFirst().orElseThrow();
assertTrue(detector instanceof JunkDetector, detector.getClass().getName());
assertFalse(detector.score("The quick brown fox jumps over the lazy dog.").isUnknown());
}

/**
* Clean English should score higher than random high-byte garbage interpreted
* as ISO-8859-1. Simulates binary data mixed into a text extraction.
Expand Down
1 change: 0 additions & 1 deletion tika-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1710,7 +1710,6 @@
<exclude>**/CharsetDetector.java</exclude>
<exclude>**/CharsetMatch.java</exclude>
<exclude>**/CharsetRecog*.java</exclude>
<exclude>**/onenote/fsshttpb/unsigned/U*.java</exclude>
<exclude>**/IPTC.java</exclude>
<exclude>**/Photoshop.java</exclude>
<exclude>**/QuattroPro.java</exclude>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,4 @@ class CompactID {
char n;
long guidIndex; //only occupies 24 bits
ExtendedGUID guid;

public char getN() {
return n;
}

public CompactID setN(char n) {
this.n = n;
return this;
}

public long getGuidIndex() {
return guidIndex;
}

public CompactID setGuidIndex(long guidIndex) {
this.guidIndex = guidIndex;
return this;
}

public ExtendedGUID getGuid() {
return guid;
}

public CompactID setGuid(ExtendedGUID guid) {
this.guid = guid;
return this;
}

public String getCompactIDString() {
return new StringBuilder().append(guid).append(", index=").append(guidIndex).append(", n=")
.append((int) n).toString();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@
*/
package org.apache.tika.parser.microsoft.onenote;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import org.apache.tika.parser.microsoft.onenote.fsshttpb.util.BitConverter;

public class ExtendedGUID implements Comparable<ExtendedGUID> {
GUID guid;
Expand Down Expand Up @@ -62,46 +59,12 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(guid, n);
// Objects.hash(guid, n) without its varargs array and boxing
return 31 * (31 + Objects.hashCode(guid)) + Long.hashCode(n);
}

@Override
public String toString() {
return String.format(Locale.US, "%s [%d]", guid, n);
}

public GUID getGuid() {
return guid;
}

public ExtendedGUID setGuid(GUID guid) {
this.guid = guid;
return this;
}

public String getExtendedGuidString() {
return guid.toString() + " [" + n + "]";
}

public long getN() {
return n;
}

public ExtendedGUID setN(long n) {
this.n = n;
return this;
}

/**
* This method is used to convert the element of ExtendedGUID object into a byte List.
*
* @return Return the byte list which store the byte information of ExtendedGUID
*/
public List<Byte> SerializeToByteList() {
List<Byte> byteList = new ArrayList<>(guid.toByteArray());
for (byte b : BitConverter.getBytes(n)) {
byteList.add(b);
}
return byteList;
}
}
Loading
Loading