Skip to content

Commit 97a6ca7

Browse files
authored
TIKA-4761 -- improve sevenz memory limit handling (#2905)
1 parent 90b72de commit 97a6ca7

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

  • tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/main/java/org/apache/tika/parser/pkg/SevenZParser.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,19 @@
2323
import java.util.Collections;
2424
import java.util.Set;
2525

26+
import org.apache.commons.compress.MemoryLimitException;
2627
import org.apache.commons.compress.PasswordRequiredException;
2728
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
2829
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
2930
import org.xml.sax.ContentHandler;
3031
import org.xml.sax.SAXException;
3132

33+
import org.apache.tika.config.ConfigDeserializer;
34+
import org.apache.tika.config.JsonConfig;
3235
import org.apache.tika.config.TikaComponent;
3336
import org.apache.tika.exception.EncryptedDocumentException;
3437
import org.apache.tika.exception.TikaException;
38+
import org.apache.tika.exception.TikaMemoryLimitException;
3539
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
3640
import org.apache.tika.extractor.EmbeddedDocumentUtil;
3741
import org.apache.tika.io.TemporaryResources;
@@ -60,6 +64,37 @@ public class SevenZParser extends AbstractArchiveParser {
6064

6165
private static final Set<MediaType> SUPPORTED_TYPES = Collections.singleton(SEVENZ);
6266

67+
/**
68+
* Configuration class for JSON deserialization.
69+
*/
70+
public static class Config {
71+
// Cap the LZMA/LZMA2 dictionary allocation. A tiny 7z can declare a huge dictionary
72+
// (a single header byte), forcing an eager multi-GiB allocation and OOMing the parser.
73+
// Mirrors CompressorParser's default.
74+
private int memoryLimitInKb = 100000;
75+
76+
public int getMemoryLimitInKb() {
77+
return memoryLimitInKb;
78+
}
79+
80+
public void setMemoryLimitInKb(int memoryLimitInKb) {
81+
this.memoryLimitInKb = memoryLimitInKb;
82+
}
83+
}
84+
85+
private Config defaultConfig = new Config();
86+
87+
public SevenZParser() {
88+
}
89+
90+
public SevenZParser(Config config) {
91+
this.defaultConfig = config;
92+
}
93+
94+
public SevenZParser(JsonConfig jsonConfig) {
95+
this(ConfigDeserializer.buildConfig(jsonConfig, Config.class));
96+
}
97+
6398
@Override
6499
public Set<MediaType> getSupportedTypes(ParseContext context) {
65100
return SUPPORTED_TYPES;
@@ -78,14 +113,21 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata
78113

79114
SevenZFile sevenZFile;
80115
try {
81-
SevenZFile.Builder builder = new SevenZFile.Builder().setFile(tis.getFile());
116+
// Use setMaxMemoryLimitKiB (direct KiB); setMaxMemoryLimitKb divides the arg by 1024.
117+
SevenZFile.Builder builder = new SevenZFile.Builder()
118+
.setFile(tis.getFile())
119+
.setMaxMemoryLimitKiB(defaultConfig.getMemoryLimitInKb());
82120
if (password == null) {
83121
sevenZFile = builder.get();
84122
} else {
85123
sevenZFile = builder.setPassword(password.toCharArray()).get();
86124
}
87125
} catch (PasswordRequiredException e) {
88126
throw new EncryptedDocumentException(e);
127+
} catch (MemoryLimitException e) {
128+
// The limit can be exceeded at open time (assertValidity) as well as lazily on the
129+
// first getNextEntry() when the LZMA/LZMA2 dictionary is allocated.
130+
throw new TikaMemoryLimitException(e.getMessage());
89131
}
90132

91133
metadata.set(Metadata.CONTENT_TYPE, SEVENZ.toString());
@@ -106,6 +148,8 @@ public void parse(TikaInputStream tis, ContentHandler handler, Metadata metadata
106148
}
107149
} catch (PasswordRequiredException e) {
108150
throw new EncryptedDocumentException(e);
151+
} catch (MemoryLimitException e) {
152+
throw new TikaMemoryLimitException(e.getMessage());
109153
} finally {
110154
sevenZFile.close();
111155
xhtml.endDocument();

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pkg-module/src/test/java/org/apache/tika/parser/pkg/Seven7ParserTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
import static org.junit.jupiter.api.Assertions.assertEquals;
2020
import static org.junit.jupiter.api.Assertions.assertNotNull;
2121
import static org.junit.jupiter.api.Assertions.assertNull;
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
2223
import static org.junit.jupiter.api.Assertions.assertTrue;
2324

2425
import org.junit.jupiter.api.Test;
2526
import org.xml.sax.ContentHandler;
2627

28+
import org.apache.tika.exception.TikaMemoryLimitException;
2729
import org.apache.tika.io.TikaInputStream;
2830
import org.apache.tika.metadata.Metadata;
31+
import org.apache.tika.parser.ParseContext;
2932
import org.apache.tika.sax.BodyContentHandler;
3033

3134
/**
@@ -71,4 +74,23 @@ public void testEmbedded() throws Exception {
7174
assertTrue(mod.startsWith("20"), "Modified at " + mod);
7275
}
7376
}
77+
78+
/**
79+
* A 7z whose declared LZMA2 dictionary exceeds the configured limit must fail with a
80+
* {@link TikaMemoryLimitException} rather than eagerly allocating the dictionary (which a
81+
* tiny crafted 7z can drive to multiple GiB, OOMing the parser). The default-limit control
82+
* is {@link #testEmbedded()} above, which would fail if the limit were applied in the wrong
83+
* unit (e.g. via setMaxMemoryLimitKb, which divides the argument by 1024).
84+
*/
85+
@Test
86+
public void testMemoryLimit() throws Exception {
87+
SevenZParser.Config config = new SevenZParser.Config();
88+
config.setMemoryLimitInKb(1); // 1 KiB, smaller than any real LZMA2 dictionary
89+
SevenZParser parser = new SevenZParser(config);
90+
91+
try (TikaInputStream tis = getResourceAsStream("/test-documents/test-documents.7z")) {
92+
assertThrows(TikaMemoryLimitException.class, () -> parser.parse(tis,
93+
new BodyContentHandler(), new Metadata(), new ParseContext()));
94+
}
95+
}
7496
}

0 commit comments

Comments
 (0)