This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4761-sevenz in repository https://gitbox.apache.org/repos/asf/tika.git
commit fe82d71fff4b51fd2ccbf4f911fda7d119b579a8 Author: tallison <[email protected]> AuthorDate: Thu Jun 25 11:05:11 2026 -0400 TIKA-4761 -- improve sevenz memory limit handling --- .../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())); + } + } }
