EmbeddedEntriesNoTempFileTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tika.parser.odf;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.Random;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.tika.TikaTest;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.extractor.RewindRecordingExtractor;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.epub.EpubParser;
/**
* An entry of an ODF or EPUB container is in the zip already. Rewinding the
* stream handed to the embedded-document extractor -- which digesting does for
* every embedded document, and which the ODF parser itself does to detect a
* picture -- must re-open the entry, not cache a copy and spill it to a temp
* file. The payload is over the 1 MB a cache keeps in memory, so the difference
* is observable; it is built at test time by rewriting one entry of a fixture.
*/
public class EmbeddedEntriesNoTempFileTest extends TikaTest {
private static final int PAYLOAD_LENGTH = 2 * 1024 * 1024;
@TempDir
Path tempDir;
@Test
public void testOdfPictureIsNotSpooled() throws Exception {
Path odt = withEntryReplaced("testODTEmbeddedImageLink.odt",
"Pictures/10000201000001240000006457F5B1D1243E0671.png", "picture.odt");
RewindRecordingExtractor extractor = parse(odt, new OpenDocumentParser());
extractor.assertSawLength(PAYLOAD_LENGTH);
extractor.assertNothingSpooled();
}
@Test
public void testEpubResourceIsNotSpooled() throws Exception {
Path epub = withEntryReplaced("testEPUB.epub", "OPS/CoverDesign.jpg", "cover.epub");
RewindRecordingExtractor extractor = parse(epub, new EpubParser());
extractor.assertSawLength(PAYLOAD_LENGTH);
extractor.assertNothingSpooled();
}
private RewindRecordingExtractor parse(Path file, Parser parser) throws Exception {
RewindRecordingExtractor extractor = new RewindRecordingExtractor();
ParseContext context = new ParseContext();
context.set(EmbeddedDocumentExtractor.class, extractor);
Metadata metadata = new Metadata();
try (TikaInputStream tis = TikaInputStream.get(file, metadata)) {
parser.parse(tis, new DefaultHandler(), metadata, context);
}
return extractor;
}
/**
* A copy of the fixture with one entry's bytes replaced by the payload; every
* other entry is copied raw, so the stored {@code mimetype} entry stays stored.
*/
private Path withEntryReplaced(String fixture, String entryName, String name)
throws IOException {
Path original = tempDir.resolve("original-" + name);
try (InputStream is = getResourceAsStream("/test-documents/" + fixture)) {
Files.copy(is, original);
}
Path copy = tempDir.resolve(name);
boolean replaced = false;
try (ZipFile in = ZipFile.builder().setPath(original).get();
ZipArchiveOutputStream out = new ZipArchiveOutputStream(copy.toFile())) {
Enumeration<ZipArchiveEntry> entries = in.getEntries();
while (entries.hasMoreElements()) {
ZipArchiveEntry entry = entries.nextElement();
if (entry.getName().equals(entryName)) {
ZipArchiveEntry big = new ZipArchiveEntry(entryName);
big.setMethod(ZipArchiveEntry.DEFLATED);
out.putArchiveEntry(big);
out.write(payload());
out.closeArchiveEntry();
replaced = true;
} else {
try (InputStream raw = in.getRawInputStream(entry)) {
out.addRawArchiveEntry(entry, raw);
}
}
}
}
if (!replaced) {
throw new IOException(entryName + " is not in " + fixture);
}
return copy;
}
/** Incompressible filler, so DEFLATE keeps it at full size. */
private static byte[] payload() {
byte[] bytes = new byte[PAYLOAD_LENGTH];
new Random(4878).nextBytes(bytes);
return bytes;
}
}