Skip to content

Commit 17826b5

Browse files
authored
* TIKA-4518 -- improve pst handling with -Z option
1 parent 8b0dd1f commit 17826b5

15 files changed

Lines changed: 194 additions & 118 deletions

File tree

CHANGES.txt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@ Release 4.0.0-BETA1 - ???
77
* Headers are no longer injected into the body/content of MSG files (TIKA-4345). Please open
88
a ticket if you need this behavior across email formats.
99

10-
* Remove tika-batch (TIKA-4333).
10+
* Removed several modules, including: tika-batch (TIKA-4333), snaps deployment (TIKA-4502),
11+
dotnet (TIKA-4332), advanced media module (TIKA-4500), tika-dl module (TIKA-4499),
12+
tika-fuzzing module (TIKA-4506).
1113

12-
* Remove snaps deployment (TIKA-4502).
13-
14-
* Removed the dotnet module (TIKA-4332).
15-
16-
* Removed the advanced media module (TIKA-4500).
17-
18-
* Removed the tika-dl module (TIKA-4499).
19-
20-
* Removed the tika-fuzzing module (TIKA-4506).
14+
* API changes in the EmbeddedStreamTranslator (TIKA-4518).
2115

2216
OTHER CHANGES
2317

tika-app/src/main/java/org/apache/tika/cli/TikaCLI.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,9 +1112,7 @@ public void parseEmbedded(TikaInputStream tis, ContentHandler contentHandler, Me
11121112

11131113
try (OutputStream os = Files.newOutputStream(outputFile)) {
11141114
if (embeddedStreamTranslator.shouldTranslate(tis, metadata)) {
1115-
try (InputStream translated = embeddedStreamTranslator.translate(tis, metadata)) {
1116-
IOUtils.copy(translated, os);
1117-
}
1115+
embeddedStreamTranslator.translate(tis, metadata, os);
11181116
} else {
11191117
IOUtils.copy(tis, os);
11201118
}

tika-app/src/test/java/org/apache/tika/cli/TikaCLIAsyncTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public void testAsync() throws Exception {
121121
json++;
122122
}
123123
}
124-
assertEquals(20, json);
124+
assertEquals(21, json);
125125
}
126126

127127
private void checkForPrettyPrint(File f) throws IOException {

tika-app/src/test/java/org/apache/tika/cli/TikaCLITest.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.io.File;
2727
import java.io.IOException;
2828
import java.io.PrintStream;
29+
import java.io.Reader;
2930
import java.net.URI;
3031
import java.nio.file.FileVisitResult;
3132
import java.nio.file.FileVisitor;
@@ -34,6 +35,7 @@
3435
import java.nio.file.Paths;
3536
import java.nio.file.attribute.BasicFileAttributes;
3637
import java.util.HashSet;
38+
import java.util.List;
3739
import java.util.Set;
3840

3941
import org.jetbrains.annotations.NotNull;
@@ -44,7 +46,11 @@
4446
import org.junit.jupiter.api.io.TempDir;
4547

4648
import org.apache.tika.exception.TikaException;
49+
import org.apache.tika.metadata.Metadata;
50+
import org.apache.tika.metadata.TikaCoreProperties;
51+
import org.apache.tika.serialization.JsonMetadataList;
4752
import org.apache.tika.utils.ProcessUtils;
53+
import org.apache.tika.utils.StringUtils;
4854

4955
/**
5056
* Tests the Tika's cli
@@ -285,6 +291,28 @@ public void testRUnpack() throws Exception {
285291
testRecursiveUnpack("testPDFPackage.pdf", expectedChildren, 2);
286292
}
287293

294+
@Test
295+
public void testPSTRUnpack() throws Exception {
296+
String[] expectedChildren = new String[]{"testPST.pst.json",
297+
"testPST.pst-embed/00000007-First email.msg",
298+
"testPST.pst-embed/00000001-Feature Generators.msg",
299+
"testPST.pst-embed/00000008-First email.msg",
300+
"testPST.pst-embed/00000004-[jira] [Resolved] (TIKA-1249) Vcard files detection.msg",
301+
"testPST.pst-embed/00000003-Feature Generators.msg",
302+
"testPST.pst-embed/00000002-putstatic\".msg",
303+
"testPST.pst-embed/00000005-[jira] [Commented] (TIKA-1250) Process loops infintely processing a CHM file.msg",
304+
"testPST.pst-embed/00000009-attachment.docx",
305+
"testPST.pst-embed/00000006-[WEBINAR] - \"Introducing Couchbase Server 2.5\".msg"};
306+
testRecursiveUnpack("testPST.pst", expectedChildren, 2);
307+
try (Reader reader = Files.newBufferedReader(extractDir.resolve("testPST.pst.json"))) {
308+
List<Metadata> metadataList = JsonMetadataList.fromJson(reader);
309+
for (Metadata m : metadataList) {
310+
String content = m.get(TikaCoreProperties.TIKA_CONTENT);
311+
assertFalse(StringUtils.isBlank(content));
312+
}
313+
}
314+
}
315+
288316

289317
/**
290318
* Tests -l option of the cli
@@ -378,7 +406,6 @@ private void testRecursiveUnpack(String targetFile, String[] expectedChildrenFil
378406
.list();
379407
assertNotNull(jsonFile);
380408
assertEquals(expectedLength, jsonFile.length);
381-
//assertEquals(fileNames.size(), expectedChildrenFileNames.length);
382409

383410
for (String expectedChildName : expectedChildrenFileNames) {
384411
assertTrue(fileNames.contains(expectedChildName));
2.2 MB
Binary file not shown.

tika-core/src/main/java/org/apache/tika/extractor/DefaultEmbeddedStreamTranslator.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@
1717
package org.apache.tika.extractor;
1818

1919
import java.io.IOException;
20-
import java.io.InputStream;
20+
import java.io.OutputStream;
2121
import java.util.List;
2222

2323
import org.apache.tika.config.ServiceLoader;
24+
import org.apache.tika.io.TikaInputStream;
2425
import org.apache.tika.metadata.Metadata;
2526
import org.apache.tika.utils.ServiceLoaderUtils;
2627

2728
/**
2829
* Loads EmbeddedStreamTranslators via service loading. Tries to run each
29-
* in turn and returns the first non-null value. If no translation has occurred,
30-
* this returns the original InputStream. If a translation has occurred, the
31-
* translator will consume the InputStream but not close it.
30+
* in turn. If a translator accepts the stream, it will do the translation but not close the stream.
3231
*/
3332
public class DefaultEmbeddedStreamTranslator implements EmbeddedStreamTranslator {
3433

@@ -58,7 +57,7 @@ private DefaultEmbeddedStreamTranslator(List<EmbeddedStreamTranslator> translato
5857
* @throws IOException
5958
*/
6059
@Override
61-
public boolean shouldTranslate(InputStream inputStream, Metadata metadata) throws IOException {
60+
public boolean shouldTranslate(TikaInputStream inputStream, Metadata metadata) throws IOException {
6261
for (EmbeddedStreamTranslator translator : translators) {
6362
if (translator.shouldTranslate(inputStream, metadata)) {
6463
return true;
@@ -68,20 +67,20 @@ public boolean shouldTranslate(InputStream inputStream, Metadata metadata) throw
6867
}
6968

7069
/**
71-
* This will consume the InputStream and return a new stream of translated bytes.
70+
* This will consume the InputStream and write the stream to the output stream
7271
* @param inputStream
7372
* @param metadata
73+
* @param outputStream to write to
7474
* @return
7575
* @throws IOException
7676
*/
7777
@Override
78-
public InputStream translate(InputStream inputStream, Metadata metadata) throws IOException {
78+
public void translate(TikaInputStream inputStream, Metadata metadata, OutputStream outputStream) throws IOException {
7979
for (EmbeddedStreamTranslator translator : translators) {
80-
InputStream translated = translator.translate(inputStream, metadata);
81-
if (translated != null) {
82-
return translated;
80+
if (translator.shouldTranslate(inputStream, metadata)) {
81+
translator.translate(inputStream, metadata, outputStream);
82+
return;
8383
}
8484
}
85-
return inputStream;
8685
}
8786
}

tika-core/src/main/java/org/apache/tika/extractor/EmbeddedStreamTranslator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
package org.apache.tika.extractor;
1818

1919
import java.io.IOException;
20-
import java.io.InputStream;
20+
import java.io.OutputStream;
2121

22+
import org.apache.tika.io.TikaInputStream;
2223
import org.apache.tika.metadata.Metadata;
2324

2425
/**
@@ -30,9 +31,8 @@
3031
*/
3132
public interface EmbeddedStreamTranslator {
3233

33-
boolean shouldTranslate(InputStream inputStream, Metadata metadata) throws IOException;
34+
boolean shouldTranslate(TikaInputStream inputStream, Metadata metadata) throws IOException;
3435

35-
InputStream translate(InputStream inputStream,
36-
Metadata metadata) throws IOException;
36+
void translate(TikaInputStream inputStream, Metadata metadata, OutputStream os) throws IOException;
3737

3838
}

tika-core/src/main/java/org/apache/tika/extractor/RUnpackExtractor.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.File;
2222
import java.io.IOException;
2323
import java.io.InputStream;
24+
import java.io.OutputStream;
2425
import java.nio.file.Files;
2526
import java.nio.file.Path;
2627
import java.nio.file.StandardCopyOption;
@@ -110,18 +111,27 @@ public void parseEmbedded(
110111
}
111112
}
112113

113-
private void parseWithBytes(TikaInputStream stream, ContentHandler handler, Metadata metadata)
114-
throws TikaException, IOException, SAXException {
115-
//TODO -- improve the efficiency of this so that we're not
116-
//literally writing out a file per request
114+
private void parseWithBytes(TikaInputStream tis, ContentHandler handler, Metadata metadata) throws TikaException, IOException, SAXException {
115+
117116
Path tmp = Files.createTempFile("tika-tmp-", ".bin");
118-
if (embeddedStreamTranslator.shouldTranslate(stream, metadata)) {
119-
Files.copy(embeddedStreamTranslator.translate(stream, metadata), tmp, StandardCopyOption.REPLACE_EXISTING);
120-
} else {
121-
Files.copy(stream, tmp, StandardCopyOption.REPLACE_EXISTING);
122-
}
123-
try (TikaInputStream tmpTis = TikaInputStream.get(tmp)) {
124-
parse(tmpTis, handler, metadata);
117+
try {
118+
//translate the stream or not
119+
if (embeddedStreamTranslator.shouldTranslate(tis, metadata)) {
120+
try (OutputStream os = Files.newOutputStream(tmp)) {
121+
embeddedStreamTranslator.translate(tis, metadata, os);
122+
}
123+
} else {
124+
Files.copy(tis, tmp, StandardCopyOption.REPLACE_EXISTING);
125+
}
126+
127+
//now do the parse
128+
if (tis.getOpenContainer() != null) {
129+
parse(tis, handler, metadata);
130+
} else {
131+
try (TikaInputStream tisTmp = TikaInputStream.get(tmp)) {
132+
parse(tisTmp, handler, metadata);
133+
}
134+
}
125135
} finally {
126136
try {
127137
storeEmbeddedBytes(tmp, metadata);
@@ -139,6 +149,10 @@ private void parse(InputStream stream, ContentHandler handler, Metadata metadata
139149
}
140150

141151
private void storeEmbeddedBytes(Path p, Metadata metadata) {
152+
if (p == null) {
153+
return;
154+
}
155+
142156
if (! embeddedBytesSelector.select(metadata)) {
143157
if (LOGGER.isDebugEnabled()) {
144158
LOGGER.debug("skipping embedded bytes {} <-> {}",

tika-core/src/main/java/org/apache/tika/io/FilenameUtils.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,16 @@ public static String getSuffixFromPath(String path) {
140140

141141
public static String getSanitizedEmbeddedFileName(Metadata metadata,
142142
String defaultExtension, int maxLength) {
143-
String path = getEmbeddedPath(metadata);
143+
String path = getEmbeddedName(metadata);
144144
//fName could be a full path or null
145145
if (StringUtils.isBlank(path)) {
146146
return null;
147147
}
148148
path = path.replaceAll("\u0000", " ");
149+
if (path.startsWith("\"") && path.endsWith("\"")) {
150+
path = path.substring(1, path.length() - 1);
151+
}
152+
149153
int prefixLength = getPrefixLength(path);
150154
if (prefixLength > 0) {
151155
path = path.substring(prefixLength);
@@ -173,6 +177,7 @@ public static String getSanitizedEmbeddedFileName(Metadata metadata,
173177
namePart = namePart.replaceAll("(\\.\\.)+", "_");
174178
namePart = namePart.replaceAll("[/\\\\]+", "_");
175179
namePart = namePart.replaceAll(":+", "_");
180+
namePart = namePart.trim();
176181

177182
if (StringUtils.isBlank(namePart)) {
178183
return null;
@@ -286,6 +291,7 @@ private static String removeProtocol(String path) {
286291
return path;
287292
}
288293

294+
//may return null
289295
private static String getEmbeddedPath(Metadata metadata) {
290296
//potentially look for other values in embedded path or original file name, etc...
291297
//maybe different fallback order?
@@ -304,6 +310,27 @@ private static String getEmbeddedPath(Metadata metadata) {
304310
return metadata.get(TikaCoreProperties.ORIGINAL_RESOURCE_NAME);
305311
}
306312

313+
//this tries for resource name first, and then backs off to path
314+
private static String getEmbeddedName(Metadata metadata) {
315+
//potentially look for other values in embedded path or original file name, etc...
316+
//maybe different fallback order?
317+
String path = metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY);
318+
if (! StringUtils.isBlank(path)) {
319+
return path;
320+
}
321+
path = metadata.get(TikaCoreProperties.EMBEDDED_RELATIONSHIP_ID);
322+
if (! StringUtils.isBlank(path)) {
323+
return path;
324+
}
325+
326+
path = metadata.get(TikaCoreProperties.EMBEDDED_RESOURCE_PATH);
327+
if (! StringUtils.isBlank(path)) {
328+
return path;
329+
}
330+
331+
return metadata.get(TikaCoreProperties.ORIGINAL_RESOURCE_NAME);
332+
}
333+
307334
/**
308335
* Calculate the extension based on the {@link Metadata#CONTENT_TYPE} value.
309336
* On parse exception or null value, return the default value.

tika-core/src/test/java/org/apache/tika/io/FilenameUtilsTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ public void testEmbeddedFileNames() throws Exception {
151151
assertEquals("brown fox.xlsx", sanitizeFilename("a:/the quick:brown fox.xlsx"));
152152
assertEquals("_the quick brown fox.xlsx", sanitizeFilename("C:\\a/b/c/..the quick brown fox.xlsx"));
153153
assertEquals("_the quick brown fox.xlsx", sanitizeFilename("~/a/b/c/.the quick brown fox.xlsx"));
154+
assertEquals("the quick%3Ebrown fox.xlsx", sanitizeFilename("the quick>brown fox.xlsx"));
155+
assertEquals("the quick\"brown fox.xlsx", sanitizeFilename("the quick\"brown fox.xlsx"));
156+
assertEquals("the quick brown fox.xlsx", sanitizeFilename("\"the quick brown fox.xlsx\""));
154157

155158
assertEquals("_.docx", sanitizeFilename("..................docx"));
156159
assertEquals("_.docx", sanitizeFilename("..docx"));
@@ -168,7 +171,7 @@ public void testEmbeddedFileNames() throws Exception {
168171
@Test
169172
public void testEmbeddedFilePaths() throws Exception {
170173
String n = "the quick brown fox.docx";
171-
/*assertEquals(n, sanitizePath(n));
174+
assertEquals(n, sanitizePath(n));
172175
assertEquals(n, sanitizePath(n.substring(0, n.length() - 5),
173176
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"));
174177
assertEquals(n, sanitizeFilename("the quick\u0000brown fox.docx"));
@@ -204,7 +207,7 @@ public void testEmbeddedFilePaths() throws Exception {
204207
assertNull(sanitizePath(""));
205208
assertNull(sanitizePath(null));
206209
assertNull(sanitizePath("/"));
207-
assertNull(sanitizePath("~/"));*/
210+
assertNull(sanitizePath("~/"));
208211
assertNull(sanitizePath("C:"));
209212
assertNull(sanitizePath("C:/"));
210213
assertNull(sanitizePath("C:\\"));
@@ -235,6 +238,7 @@ private Metadata getMetadata(String name, String contentType) {
235238

236239
private Metadata getMetadata(String name) {
237240
Metadata metadata = new Metadata();
241+
metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, name);
238242
metadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_PATH, name);
239243
return metadata;
240244
}

0 commit comments

Comments
 (0)