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 97a6ca72b7 TIKA-4761 -- improve sevenz memory limit handling (#2905)
97a6ca72b7 is described below
commit 97a6ca72b71404a227c65a4d4ca0389b8ddd5b0c
Author: Tim Allison <[email protected]>
AuthorDate: Thu Jun 25 11:50:22 2026 -0400
TIKA-4761 -- improve sevenz memory limit handling (#2905)
---
.../org/apache/tika/parser/pkg/SevenZParser.java | 46 +++++++++++++++++++++-
.../apache/tika/parser/pkg/Seven7ParserTest.java | 22 +++++++++++
2 files changed, 67 insertions(+), 1 deletion(-)
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/main/java/org/apache/tika/parser/pkg/SevenZParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/main/java/org/apache/tika/parser/pkg/SevenZParser.java
index 2900dec7b9..3de2916860 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/main/java/org/apache/tika/parser/pkg/SevenZParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/main/java/org/apache/tika/parser/pkg/SevenZParser.java
@@ -23,15 +23,19 @@ import java.io.InputStream;
import java.util.Collections;
import java.util.Set;
+import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.PasswordRequiredException;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
+import org.apache.tika.config.ConfigDeserializer;
+import org.apache.tika.config.JsonConfig;
import org.apache.tika.config.TikaComponent;
import org.apache.tika.exception.EncryptedDocumentException;
import org.apache.tika.exception.TikaException;
+import org.apache.tika.exception.TikaMemoryLimitException;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.EmbeddedDocumentUtil;
import org.apache.tika.io.TemporaryResources;
@@ -60,6 +64,37 @@ public class SevenZParser extends AbstractArchiveParser {
private static final Set<MediaType> SUPPORTED_TYPES =
Collections.singleton(SEVENZ);
+ /**
+ * Configuration class for JSON deserialization.
+ */
+ public static class Config {
+ // Cap the LZMA/LZMA2 dictionary allocation. A tiny 7z can declare a
huge dictionary
+ // (a single header byte), forcing an eager multi-GiB allocation and
OOMing the parser.
+ // Mirrors CompressorParser's default.
+ private int memoryLimitInKb = 100000;
+
+ public int getMemoryLimitInKb() {
+ return memoryLimitInKb;
+ }
+
+ public void setMemoryLimitInKb(int memoryLimitInKb) {
+ this.memoryLimitInKb = memoryLimitInKb;
+ }
+ }
+
+ private Config defaultConfig = new Config();
+
+ public SevenZParser() {
+ }
+
+ public SevenZParser(Config config) {
+ this.defaultConfig = config;
+ }
+
+ public SevenZParser(JsonConfig jsonConfig) {
+ this(ConfigDeserializer.buildConfig(jsonConfig, Config.class));
+ }
+
@Override
public Set<MediaType> getSupportedTypes(ParseContext context) {
return SUPPORTED_TYPES;
@@ -78,7 +113,10 @@ public class SevenZParser extends AbstractArchiveParser {
SevenZFile sevenZFile;
try {
- SevenZFile.Builder builder = new
SevenZFile.Builder().setFile(tis.getFile());
+ // Use setMaxMemoryLimitKiB (direct KiB); setMaxMemoryLimitKb
divides the arg by 1024.
+ SevenZFile.Builder builder = new SevenZFile.Builder()
+ .setFile(tis.getFile())
+ .setMaxMemoryLimitKiB(defaultConfig.getMemoryLimitInKb());
if (password == null) {
sevenZFile = builder.get();
} else {
@@ -86,6 +124,10 @@ public class SevenZParser extends AbstractArchiveParser {
}
} catch (PasswordRequiredException e) {
throw new EncryptedDocumentException(e);
+ } catch (MemoryLimitException e) {
+ // The limit can be exceeded at open time (assertValidity) as well
as lazily on the
+ // first getNextEntry() when the LZMA/LZMA2 dictionary is
allocated.
+ throw new TikaMemoryLimitException(e.getMessage());
}
metadata.set(Metadata.CONTENT_TYPE, SEVENZ.toString());
@@ -106,6 +148,8 @@ public class SevenZParser extends AbstractArchiveParser {
}
} catch (PasswordRequiredException e) {
throw new EncryptedDocumentException(e);
+ } catch (MemoryLimitException e) {
+ throw new TikaMemoryLimitException(e.getMessage());
} finally {
sevenZFile.close();
xhtml.endDocument();
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/test/java/org/apache/tika/parser/pkg/Seven7ParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/test/java/org/apache/tika/parser/pkg/Seven7ParserTest.java
index 2198bafd01..32a29d9df0 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/test/java/org/apache/tika/parser/pkg/Seven7ParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/test/java/org/apache/tika/parser/pkg/Seven7ParserTest.java
@@ -19,13 +19,16 @@ package org.apache.tika.parser.pkg;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.xml.sax.ContentHandler;
+import org.apache.tika.exception.TikaMemoryLimitException;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;
/**
@@ -71,4 +74,23 @@ public class Seven7ParserTest extends AbstractPkgTest {
assertTrue(mod.startsWith("20"), "Modified at " + mod);
}
}
+
+ /**
+ * A 7z whose declared LZMA2 dictionary exceeds the configured limit must
fail with a
+ * {@link TikaMemoryLimitException} rather than eagerly allocating the
dictionary (which a
+ * tiny crafted 7z can drive to multiple GiB, OOMing the parser). The
default-limit control
+ * is {@link #testEmbedded()} above, which would fail if the limit were
applied in the wrong
+ * unit (e.g. via setMaxMemoryLimitKb, which divides the argument by 1024).
+ */
+ @Test
+ public void testMemoryLimit() throws Exception {
+ SevenZParser.Config config = new SevenZParser.Config();
+ config.setMemoryLimitInKb(1); // 1 KiB, smaller than any real LZMA2
dictionary
+ SevenZParser parser = new SevenZParser(config);
+
+ try (TikaInputStream tis =
getResourceAsStream("/test-documents/test-documents.7z")) {
+ assertThrows(TikaMemoryLimitException.class, () ->
parser.parse(tis,
+ new BodyContentHandler(), new Metadata(), new
ParseContext()));
+ }
+ }
}