bug 60128: close open file descriptors when exceptions are thrown from OPCPackage.open
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1760702 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
6219cc6664
commit
ed5cd06fb7
@ -248,9 +248,10 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
|
|||||||
* @throws InvalidFormatException
|
* @throws InvalidFormatException
|
||||||
* If the specified file doesn't exist, and a parsing error
|
* If the specified file doesn't exist, and a parsing error
|
||||||
* occur.
|
* occur.
|
||||||
|
* @throws InvalidOperationException
|
||||||
*/
|
*/
|
||||||
public static OPCPackage open(String path, PackageAccess access)
|
public static OPCPackage open(String path, PackageAccess access)
|
||||||
throws InvalidFormatException {
|
throws InvalidFormatException, InvalidOperationException {
|
||||||
if (path == null || "".equals(path.trim())) {
|
if (path == null || "".equals(path.trim())) {
|
||||||
throw new IllegalArgumentException("'path' must be given");
|
throw new IllegalArgumentException("'path' must be given");
|
||||||
}
|
}
|
||||||
@ -261,8 +262,20 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OPCPackage pack = new ZipPackage(path, access);
|
OPCPackage pack = new ZipPackage(path, access);
|
||||||
|
boolean success = false;
|
||||||
if (pack.partList == null && access != PackageAccess.WRITE) {
|
if (pack.partList == null && access != PackageAccess.WRITE) {
|
||||||
|
try {
|
||||||
pack.getParts();
|
pack.getParts();
|
||||||
|
success = true;
|
||||||
|
} finally {
|
||||||
|
if (! success) {
|
||||||
|
try {
|
||||||
|
pack.close();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new InvalidOperationException("Could not close OPCPackage while cleaning up", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pack.originalPackagePath = new File(path).getAbsolutePath();
|
pack.originalPackagePath = new File(path).getAbsolutePath();
|
||||||
return pack;
|
return pack;
|
||||||
|
@ -19,6 +19,7 @@ package org.apache.poi.openxml4j.opc;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -88,9 +89,18 @@ public final class ZipPackage extends OPCPackage {
|
|||||||
*/
|
*/
|
||||||
ZipPackage(InputStream in, PackageAccess access) throws IOException {
|
ZipPackage(InputStream in, PackageAccess access) throws IOException {
|
||||||
super(access);
|
super(access);
|
||||||
@SuppressWarnings("resource")
|
|
||||||
ThresholdInputStream zis = ZipHelper.openZipStream(in);
|
ThresholdInputStream zis = ZipHelper.openZipStream(in);
|
||||||
|
try {
|
||||||
this.zipArchive = new ZipInputStreamZipEntrySource(zis);
|
this.zipArchive = new ZipInputStreamZipEntrySource(zis);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
try {
|
||||||
|
zis.close();
|
||||||
|
} catch (final IOException e2) {
|
||||||
|
e2.addSuppressed(e);
|
||||||
|
throw new IOException("Failed to close zip input stream while cleaning up", e2);
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to read zip entry source", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,8 +110,9 @@ public final class ZipPackage extends OPCPackage {
|
|||||||
* The path of the file to open or create.
|
* The path of the file to open or create.
|
||||||
* @param access
|
* @param access
|
||||||
* The package access mode.
|
* The package access mode.
|
||||||
|
* @throws InvalidOperationException
|
||||||
*/
|
*/
|
||||||
ZipPackage(String path, PackageAccess access) {
|
ZipPackage(String path, PackageAccess access) throws InvalidOperationException {
|
||||||
this(new File(path), access);
|
this(new File(path), access);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +123,9 @@ public final class ZipPackage extends OPCPackage {
|
|||||||
* The file to open or create.
|
* The file to open or create.
|
||||||
* @param access
|
* @param access
|
||||||
* The package access mode.
|
* The package access mode.
|
||||||
|
* @throws InvalidOperationException
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
ZipPackage(File file, PackageAccess access) throws InvalidOperationException {
|
||||||
ZipPackage(File file, PackageAccess access) {
|
|
||||||
super(access);
|
super(access);
|
||||||
|
|
||||||
ZipEntrySource ze;
|
ZipEntrySource ze;
|
||||||
@ -127,37 +138,73 @@ public final class ZipPackage extends OPCPackage {
|
|||||||
throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e);
|
throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e);
|
||||||
}
|
}
|
||||||
logger.log(POILogger.ERROR, "Error in zip file "+file+" - falling back to stream processing (i.e. ignoring zip central directory)");
|
logger.log(POILogger.ERROR, "Error in zip file "+file+" - falling back to stream processing (i.e. ignoring zip central directory)");
|
||||||
// some zips can't be opened via ZipFile in JDK6, as the central directory
|
ze = openZipEntrySourceStream(file);
|
||||||
// contains either non-latin entries or the compression type can't be handled
|
|
||||||
// the workaround is to iterate over the stream and not the directory
|
|
||||||
FileInputStream fis = null;
|
|
||||||
ThresholdInputStream zis = null;
|
|
||||||
try {
|
|
||||||
fis = new FileInputStream(file);
|
|
||||||
zis = ZipHelper.openZipStream(fis);
|
|
||||||
ze = new ZipInputStreamZipEntrySource(zis);
|
|
||||||
} catch (IOException e2) {
|
|
||||||
if (zis != null) {
|
|
||||||
try {
|
|
||||||
zis.close();
|
|
||||||
} catch (IOException e3) {
|
|
||||||
throw new InvalidOperationException("Can't open the specified file: '" + file + "'"+
|
|
||||||
" and couldn't close the file input stream", e);
|
|
||||||
}
|
|
||||||
} else if (fis != null) {
|
|
||||||
try {
|
|
||||||
fis.close();
|
|
||||||
} catch (IOException e3) {
|
|
||||||
throw new InvalidOperationException("Can't open the specified file: '" + file + "'"+
|
|
||||||
" and couldn't close the file input stream", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.zipArchive = ze;
|
this.zipArchive = ze;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ZipEntrySource openZipEntrySourceStream(File file) throws InvalidOperationException {
|
||||||
|
final FileInputStream fis;
|
||||||
|
// Acquire a resource that is needed to read the next level of openZipEntrySourceStream
|
||||||
|
try {
|
||||||
|
// open the file input stream
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
} catch (final FileNotFoundException e) {
|
||||||
|
// If the source cannot be acquired, abort (no resources to free at this level)
|
||||||
|
throw new InvalidOperationException("Can't open the specified file input stream from file: '" + file + "'", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an error occurs while reading the next level of openZipEntrySourceStream, free the acquired resource
|
||||||
|
try {
|
||||||
|
// read from the file input stream
|
||||||
|
return openZipEntrySourceStream(fis);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
try {
|
||||||
|
// abort: close the file input stream
|
||||||
|
fis.close();
|
||||||
|
} catch (final IOException e2) {
|
||||||
|
throw new InvalidOperationException("Could not close the specified file input stream from file: '" + file + "'", e2);
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Failed to read the file input stream from file: '" + file + "'", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ZipEntrySource openZipEntrySourceStream(FileInputStream fis) throws InvalidOperationException {
|
||||||
|
final ThresholdInputStream zis;
|
||||||
|
// Acquire a resource that is needed to read the next level of openZipEntrySourceStream
|
||||||
|
try {
|
||||||
|
// open the zip input stream
|
||||||
|
zis = ZipHelper.openZipStream(fis);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
// If the source cannot be acquired, abort (no resources to free at this level)
|
||||||
|
throw new InvalidOperationException("Could not open the file input stream", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an error occurs while reading the next level of openZipEntrySourceStream, free the acquired resource
|
||||||
|
try {
|
||||||
|
// read from the zip input stream
|
||||||
|
return openZipEntrySourceStream(zis);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
try {
|
||||||
|
// abort: close the zip input stream
|
||||||
|
zis.close();
|
||||||
|
} catch (final IOException e2) {
|
||||||
|
throw new InvalidOperationException("Failed to read the zip entry source stream and could not close the zip input stream", e2);
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Failed to read the zip entry source stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ZipEntrySource openZipEntrySourceStream(ThresholdInputStream zis) throws InvalidOperationException {
|
||||||
|
// Acquire the final level resource. If this is acquired successfully, the zip package was read successfully from the input stream
|
||||||
|
try {
|
||||||
|
// open the zip entry source stream
|
||||||
|
return new ZipInputStreamZipEntrySource(zis);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidOperationException("Could not open the specified zip entry source stream", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Opens a Zip based Open XML document from
|
* Constructor. Opens a Zip based Open XML document from
|
||||||
* a custom ZipEntrySource, typically an open archive
|
* a custom ZipEntrySource, typically an open archive
|
||||||
@ -220,11 +267,12 @@ public final class ZipPackage extends OPCPackage {
|
|||||||
boolean hasSettingsXML = false;
|
boolean hasSettingsXML = false;
|
||||||
entries = this.zipArchive.getEntries();
|
entries = this.zipArchive.getEntries();
|
||||||
while (entries.hasMoreElements()) {
|
while (entries.hasMoreElements()) {
|
||||||
ZipEntry entry = entries.nextElement();
|
final ZipEntry entry = entries.nextElement();
|
||||||
if (entry.getName().equals("mimetype")) {
|
final String name = entry.getName();
|
||||||
|
if ("mimetype".equals(name)) {
|
||||||
hasMimetype = true;
|
hasMimetype = true;
|
||||||
}
|
}
|
||||||
if (entry.getName().equals("settings.xml")) {
|
if ("settings.xml".equals(name)) {
|
||||||
hasSettingsXML = true;
|
hasSettingsXML = true;
|
||||||
}
|
}
|
||||||
numEntries++;
|
numEntries++;
|
||||||
|
@ -134,15 +134,15 @@ public class ZipSecureFile extends ZipFile {
|
|||||||
return MAX_TEXT_SIZE;
|
return MAX_TEXT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZipSecureFile(File file, int mode) throws IOException {
|
public ZipSecureFile(File file, int mode) throws ZipException, IOException {
|
||||||
super(file, mode);
|
super(file, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZipSecureFile(File file) throws IOException {
|
public ZipSecureFile(File file) throws ZipException, IOException {
|
||||||
super(file);
|
super(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZipSecureFile(String name) throws IOException {
|
public ZipSecureFile(String name) throws ZipException, IOException {
|
||||||
super(name);
|
super(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,9 +682,12 @@ public class TestExtractorFactory {
|
|||||||
// Text
|
// Text
|
||||||
try {
|
try {
|
||||||
ExtractorFactory.createExtractor(OPCPackage.open(txt.toString()));
|
ExtractorFactory.createExtractor(OPCPackage.open(txt.toString()));
|
||||||
fail();
|
fail("TestExtractorFactory.testPackage() failed on " + txt.toString());
|
||||||
} catch(UnsupportedFileFormatException e) {
|
} catch(UnsupportedFileFormatException e) {
|
||||||
// Good
|
// Good
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("TestExtractorFactory.testPackage() failed on " + txt.toString());
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -942,6 +945,7 @@ public class TestExtractorFactory {
|
|||||||
"openxml4j/OPCCompliance_CoreProperties_OnlyOneCorePropertiesPartFAIL.docx",
|
"openxml4j/OPCCompliance_CoreProperties_OnlyOneCorePropertiesPartFAIL.docx",
|
||||||
"openxml4j/OPCCompliance_CoreProperties_UnauthorizedXMLLangAttributeFAIL.docx",
|
"openxml4j/OPCCompliance_CoreProperties_UnauthorizedXMLLangAttributeFAIL.docx",
|
||||||
"openxml4j/OPCCompliance_DerivedPartNameFAIL.docx",
|
"openxml4j/OPCCompliance_DerivedPartNameFAIL.docx",
|
||||||
|
"openxml4j/invalid.xlsx",
|
||||||
"spreadsheet/54764-2.xlsx", // see TestXSSFBugs.bug54764()
|
"spreadsheet/54764-2.xlsx", // see TestXSSFBugs.bug54764()
|
||||||
"spreadsheet/54764.xlsx", // see TestXSSFBugs.bug54764()
|
"spreadsheet/54764.xlsx", // see TestXSSFBugs.bug54764()
|
||||||
"spreadsheet/Simple.xlsb",
|
"spreadsheet/Simple.xlsb",
|
||||||
|
@ -943,4 +943,22 @@ public final class TestPackage {
|
|||||||
ZipSecureFile.setMaxTextSize(before);
|
ZipSecureFile.setMaxTextSize(before);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bug 60128
|
||||||
|
@Test
|
||||||
|
public void testCorruptFile() throws IOException {
|
||||||
|
OPCPackage pkg = null;
|
||||||
|
File file = OpenXML4JTestDataSamples.getSampleFile("invalid.xlsx");
|
||||||
|
try {
|
||||||
|
pkg = OPCPackage.open(file, PackageAccess.READ);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e.getClass().getName());
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (pkg != null) {
|
||||||
|
pkg.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,7 @@ public class TestZipPackage {
|
|||||||
public void testClosingStreamOnException() throws IOException {
|
public void testClosingStreamOnException() throws IOException {
|
||||||
InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
|
InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
|
||||||
File tmp = File.createTempFile("poi-test-truncated-zip", "");
|
File tmp = File.createTempFile("poi-test-truncated-zip", "");
|
||||||
|
// create a corrupted zip file by truncating a valid zip file to the first 100 bytes
|
||||||
OutputStream os = new FileOutputStream(tmp);
|
OutputStream os = new FileOutputStream(tmp);
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
os.write(is.read());
|
os.write(is.read());
|
||||||
@ -192,10 +193,15 @@ public class TestZipPackage {
|
|||||||
os.close();
|
os.close();
|
||||||
is.close();
|
is.close();
|
||||||
|
|
||||||
|
// feed the corrupted zip file to OPCPackage
|
||||||
try {
|
try {
|
||||||
OPCPackage.open(tmp, PackageAccess.READ);
|
OPCPackage.open(tmp, PackageAccess.READ);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
// expected: the zip file is invalid
|
||||||
|
// this test does not care if open() throws an exception or not.
|
||||||
}
|
}
|
||||||
|
// If the stream is not closed on exception, it will keep a file descriptor to tmp,
|
||||||
|
// and requests to the OS to delete the file will fail.
|
||||||
assertTrue("Can't delete tmp file", tmp.delete());
|
assertTrue("Can't delete tmp file", tmp.delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
test-data/openxml4j/invalid.xlsx
Normal file
BIN
test-data/openxml4j/invalid.xlsx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user