Bug 62187 - commit Commons Compress unrelated changes

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1830061 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-04-25 10:03:39 +00:00
parent 6dc8c3034e
commit 2d6380833a
30 changed files with 1066 additions and 1194 deletions

View File

@ -74,13 +74,12 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
* </p> * </p>
* See <a "http://poi.apache.org/spreadsheet/how-to.html#sxssf"> * See <a "http://poi.apache.org/spreadsheet/how-to.html#sxssf">
* http://poi.apache.org/spreadsheet/how-to.html#sxssf</a>. * http://poi.apache.org/spreadsheet/how-to.html#sxssf</a>.
*
* @author Yegor Kozlov
*/ */
public class BigGridDemo { public final class BigGridDemo {
private static final String XML_ENCODING = "UTF-8"; private static final String XML_ENCODING = "UTF-8";
private BigGridDemo() {}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
// Step 1. Create a template file. Setup sheets and workbook-level objects such as // Step 1. Create a template file. Setup sheets and workbook-level objects such as
@ -229,17 +228,17 @@ public class BigGridDemo {
private final Writer _out; private final Writer _out;
private int _rownum; private int _rownum;
public SpreadsheetWriter(Writer out){ SpreadsheetWriter(Writer out){
_out = out; _out = out;
} }
public void beginSheet() throws IOException { void beginSheet() throws IOException {
_out.write("<?xml version=\"1.0\" encoding=\""+XML_ENCODING+"\"?>" + _out.write("<?xml version=\"1.0\" encoding=\""+XML_ENCODING+"\"?>" +
"<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">" ); "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">" );
_out.write("<sheetData>\n"); _out.write("<sheetData>\n");
} }
public void endSheet() throws IOException { void endSheet() throws IOException {
_out.write("</sheetData>"); _out.write("</sheetData>");
_out.write("</worksheet>"); _out.write("</worksheet>");
} }
@ -249,7 +248,7 @@ public class BigGridDemo {
* *
* @param rownum 0-based row number * @param rownum 0-based row number
*/ */
public void insertRow(int rownum) throws IOException { void insertRow(int rownum) throws IOException {
_out.write("<row r=\""+(rownum+1)+"\">\n"); _out.write("<row r=\""+(rownum+1)+"\">\n");
this._rownum = rownum; this._rownum = rownum;
} }
@ -257,7 +256,7 @@ public class BigGridDemo {
/** /**
* Insert row end marker * Insert row end marker
*/ */
public void endRow() throws IOException { void endRow() throws IOException {
_out.write("</row>\n"); _out.write("</row>\n");
} }

View File

@ -23,7 +23,11 @@ package org.apache.poi;
public abstract class UnsupportedFileFormatException extends IllegalArgumentException { public abstract class UnsupportedFileFormatException extends IllegalArgumentException {
private static final long serialVersionUID = -8281969197282030046L; private static final long serialVersionUID = -8281969197282030046L;
public UnsupportedFileFormatException(String s) { protected UnsupportedFileFormatException(String s) {
super(s); super(s);
} }
protected UnsupportedFileFormatException(String message, Throwable cause) {
super(message, cause);
}
} }

View File

@ -60,7 +60,7 @@ public abstract class POIXMLFactory {
return createDocumentPart(cls, ORPHAN_PART, new Object[]{part}); return createDocumentPart(cls, ORPHAN_PART, new Object[]{part});
} }
} catch (Exception e) { } catch (Exception e) {
throw new POIXMLException(e); throw new POIXMLException((e.getCause() != null ? e.getCause() : e).getMessage(), e);
} }
} }

View File

@ -26,4 +26,8 @@ public class NotOfficeXmlFileException extends UnsupportedFileFormatException {
public NotOfficeXmlFileException(String message) { public NotOfficeXmlFileException(String message) {
super(message); super(message);
} }
public NotOfficeXmlFileException(String message, Throwable cause) {
super(message, cause);
}
} }

View File

@ -1,47 +0,0 @@
/* ====================================================================
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.poi.openxml4j.opc;
import java.util.zip.Deflater;
/**
* Specifies the compression level for content that is stored in a PackagePart.
*
* @author Julien Chable
* @version 1.0
*/
public enum CompressionOption {
/** Compression is optimized for performance. */
FAST(Deflater.BEST_SPEED),
/** Compression is optimized for size. */
MAXIMUM(Deflater.BEST_COMPRESSION),
/** Compression is optimized for a balance between size and performance. */
NORMAL(Deflater.DEFAULT_COMPRESSION),
/** Compression is turned off. */
NOT_COMPRESSED(Deflater.NO_COMPRESSION);
private final int value;
CompressionOption(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}

View File

@ -76,12 +76,12 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
/** /**
* Package access. * Package access.
*/ */
private PackageAccess packageAccess; private final PackageAccess packageAccess;
/** /**
* Package parts collection. * Package parts collection.
*/ */
protected PackagePartCollection partList; private PackagePartCollection partList;
/** /**
* Package relationships. * Package relationships.
@ -91,17 +91,17 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
/** /**
* Part marshallers by content type. * Part marshallers by content type.
*/ */
protected Map<ContentType, PartMarshaller> partMarshallers; protected final Map<ContentType, PartMarshaller> partMarshallers = new HashMap<>(5);
/** /**
* Default part marshaller. * Default part marshaller.
*/ */
protected PartMarshaller defaultPartMarshaller; protected final PartMarshaller defaultPartMarshaller = new DefaultMarshaller();
/** /**
* Part unmarshallers by content type. * Part unmarshallers by content type.
*/ */
protected Map<ContentType, PartUnmarshaller> partUnmarshallers; protected final Map<ContentType, PartUnmarshaller> partUnmarshallers = new HashMap<>(2);
/** /**
* Core package properties. * Core package properties.
@ -138,41 +138,27 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
if (getClass() != ZipPackage.class) { if (getClass() != ZipPackage.class) {
throw new IllegalArgumentException("PackageBase may not be subclassed"); throw new IllegalArgumentException("PackageBase may not be subclassed");
} }
init();
this.packageAccess = access; this.packageAccess = access;
final ContentType contentType = newCorePropertiesPart();
// TODO Delocalize specialized marshallers
this.partUnmarshallers.put(contentType, new PackagePropertiesUnmarshaller());
this.partMarshallers.put(contentType, new ZipPackagePropertiesMarshaller());
} }
/** private static ContentType newCorePropertiesPart() {
* Initialize the package instance.
*/
private void init() {
this.partMarshallers = new HashMap<>(5);
this.partUnmarshallers = new HashMap<>(2);
try { try {
// Add 'default' unmarshaller return new ContentType(ContentTypes.CORE_PROPERTIES_PART);
this.partUnmarshallers.put(new ContentType(
ContentTypes.CORE_PROPERTIES_PART),
new PackagePropertiesUnmarshaller());
// Add default marshaller
this.defaultPartMarshaller = new DefaultMarshaller();
// TODO Delocalize specialized marshallers
this.partMarshallers.put(new ContentType(
ContentTypes.CORE_PROPERTIES_PART),
new ZipPackagePropertiesMarshaller());
} catch (InvalidFormatException e) { } catch (InvalidFormatException e) {
// Should never happen // Should never happen
throw new OpenXML4JRuntimeException( throw new OpenXML4JRuntimeException(
"Package.init() : this exception should never happen, " + "Package.init() : this exception should never happen, " +
"if you read this message please send a mail to the developers team. : " + "if you read this message please send a mail to the developers team. : " +
e.getMessage(), e.getMessage(), e
e
); );
} }
} }
/** /**
* Open a package with read/write permission. * Open a package with read/write permission.
* *
@ -625,7 +611,8 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
return null; return null;
} }
} }
return getPartImpl(partName);
return partList.get(partName);
} }
/** /**
@ -741,21 +728,12 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
boolean hasCorePropertiesPart = false; boolean hasCorePropertiesPart = false;
boolean needCorePropertiesPart = true; boolean needCorePropertiesPart = true;
PackagePart[] parts = this.getPartsImpl(); partList = getPartsImpl();
this.partList = new PackagePartCollection(); for (PackagePart part : new ArrayList<>(partList.sortedValues())) {
for (PackagePart part : parts) { part.loadRelationships();
if (partList.containsKey(part._partName)) {
throw new InvalidFormatException(
"A part with the name '" +
part._partName +
"' already exist : Packages shall not contain equivalent " +
"part names and package implementers shall neither create " +
"nor recognize packages with equivalent part names. [M1.12]");
}
// Check OPC compliance rule M4.1 // Check OPC compliance rule M4.1
if (part.getContentType().equals( if (ContentTypes.CORE_PROPERTIES_PART.equals(part.getContentType())) {
ContentTypes.CORE_PROPERTIES_PART)) {
if (!hasCorePropertiesPart) { if (!hasCorePropertiesPart) {
hasCorePropertiesPart = true; hasCorePropertiesPart = true;
} else { } else {
@ -768,11 +746,10 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
PartUnmarshaller partUnmarshaller = partUnmarshallers.get(part._contentType); PartUnmarshaller partUnmarshaller = partUnmarshallers.get(part._contentType);
if (partUnmarshaller != null) { if (partUnmarshaller != null) {
UnmarshallContext context = new UnmarshallContext(this, UnmarshallContext context = new UnmarshallContext(this, part._partName);
part._partName);
try { try {
PackagePart unmarshallPart = partUnmarshaller PackagePart unmarshallPart = partUnmarshaller.unmarshall(context, part.getInputStream());
.unmarshall(context, part.getInputStream()); partList.remove(part.getPartName());
partList.put(unmarshallPart._partName, unmarshallPart); partList.put(unmarshallPart._partName, unmarshallPart);
// Core properties case-- use first CoreProperties part we come across // Core properties case-- use first CoreProperties part we come across
@ -790,12 +767,6 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
} catch (InvalidOperationException invoe) { } catch (InvalidOperationException invoe) {
throw new InvalidFormatException(invoe.getMessage(), invoe); throw new InvalidFormatException(invoe.getMessage(), invoe);
} }
} else {
try {
partList.put(part._partName, part);
} catch (InvalidOperationException e) {
throw new InvalidFormatException(e.getMessage(), e);
}
} }
} }
} }
@ -1457,6 +1428,7 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
} }
} }
/* Accesseurs */ /* Accesseurs */
/** /**
@ -1575,21 +1547,12 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
protected abstract void saveImpl(OutputStream outputStream) protected abstract void saveImpl(OutputStream outputStream)
throws IOException; throws IOException;
/**
* Get the package part mapped to the specified URI.
*
* @param partName
* The URI of the part to retrieve.
* @return The package part located by the specified URI, else <b>null</b>.
*/
protected abstract PackagePart getPartImpl(PackagePartName partName);
/** /**
* Get all parts link to the package. * Get all parts link to the package.
* *
* @return A list of the part owned by the package. * @return A list of the part owned by the package.
*/ */
protected abstract PackagePart[] getPartsImpl() protected abstract PackagePartCollection getPartsImpl()
throws InvalidFormatException; throws InvalidFormatException;
/** /**

View File

@ -106,9 +106,10 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
_isRelationshipPart = this._partName.isRelationshipPartURI(); _isRelationshipPart = this._partName.isRelationshipPartURI();
// Load relationships if any // Load relationships if any
if (loadRelationships) if (loadRelationships) {
loadRelationships(); loadRelationships();
} }
}
/** /**
* Constructor. * Constructor.
@ -558,7 +559,7 @@ public abstract class PackagePart implements RelationshipSource, Comparable<Pack
* @throws InvalidFormatException * @throws InvalidFormatException
* Throws if * Throws if
*/ */
private void loadRelationships() throws InvalidFormatException { /* package */ void loadRelationships() throws InvalidFormatException {
if (this._relationships == null && !this._isRelationshipPart) { if (this._relationships == null && !this._isRelationshipPart) {
this.throwExceptionIfRelationship(); this.throwExceptionIfRelationship();
_relationships = new PackageRelationshipCollection(this); _relationships = new PackageRelationshipCollection(this);

View File

@ -45,12 +45,12 @@ public final class PackageRelationshipCollection implements
/** /**
* Package relationships ordered by ID. * Package relationships ordered by ID.
*/ */
private TreeMap<String, PackageRelationship> relationshipsByID; private final TreeMap<String, PackageRelationship> relationshipsByID = new TreeMap<>();
/** /**
* Package relationships ordered by type. * Package relationships ordered by type.
*/ */
private TreeMap<String, PackageRelationship> relationshipsByType; private final TreeMap<String, PackageRelationship> relationshipsByType = new TreeMap<>();
/** /**
* A lookup of internal relationships to avoid * A lookup of internal relationships to avoid
@ -88,8 +88,6 @@ public final class PackageRelationshipCollection implements
* Constructor. * Constructor.
*/ */
PackageRelationshipCollection() { PackageRelationshipCollection() {
relationshipsByID = new TreeMap<>();
relationshipsByType = new TreeMap<>();
} }
/** /**
@ -149,8 +147,6 @@ public final class PackageRelationshipCollection implements
*/ */
public PackageRelationshipCollection(OPCPackage container, PackagePart part) public PackageRelationshipCollection(OPCPackage container, PackagePart part)
throws InvalidFormatException { throws InvalidFormatException {
this();
if (container == null) if (container == null)
throw new IllegalArgumentException("container needs to be specified"); throw new IllegalArgumentException("container needs to be specified");

View File

@ -17,17 +17,23 @@
package org.apache.poi.openxml4j.opc; package org.apache.poi.openxml4j.opc;
import static org.apache.poi.openxml4j.opc.ContentTypes.RELATIONSHIPS_PART;
import static org.apache.poi.openxml4j.opc.internal.ContentTypeManager.CONTENT_TYPES_PART_NAME;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; 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;
import java.util.Enumeration; import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.poi.UnsupportedFileFormatException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException; import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
@ -41,10 +47,10 @@ import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager; import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
import org.apache.poi.openxml4j.opc.internal.ZipHelper; import org.apache.poi.openxml4j.opc.internal.ZipHelper;
import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller; import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream;
import org.apache.poi.openxml4j.util.ZipEntrySource; import org.apache.poi.openxml4j.util.ZipEntrySource;
import org.apache.poi.openxml4j.util.ZipFileZipEntrySource; import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource; import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource;
import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
@ -95,12 +101,12 @@ public final class ZipPackage extends OPCPackage {
*/ */
ZipPackage(InputStream in, PackageAccess access) throws IOException { ZipPackage(InputStream in, PackageAccess access) throws IOException {
super(access); super(access);
ThresholdInputStream zis = ZipHelper.openZipStream(in); ZipArchiveThresholdInputStream zis = ZipHelper.openZipStream(in);
try { try {
this.zipArchive = new ZipInputStreamZipEntrySource(zis); this.zipArchive = new ZipInputStreamZipEntrySource(zis);
} catch (final IOException e) { } catch (final IOException e) {
IOUtils.closeQuietly(zis); IOUtils.closeQuietly(zis);
throw new IOException("Failed to read zip entry source", e); throw e;
} }
} }
@ -138,6 +144,9 @@ public final class ZipPackage extends OPCPackage {
if (access == PackageAccess.WRITE) { if (access == PackageAccess.WRITE) {
throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e); throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e);
} }
if ("java.util.zip.ZipException: archive is not a ZIP archive".equals(e.getMessage())) {
throw new NotOfficeXmlFileException("archive is not a ZIP archive", e);
}
LOG.log(POILogger.ERROR, "Error in zip file "+file+" - falling back to stream processing (i.e. ignoring zip central directory)"); LOG.log(POILogger.ERROR, "Error in zip file "+file+" - falling back to stream processing (i.e. ignoring zip central directory)");
ze = openZipEntrySourceStream(file); ze = openZipEntrySourceStream(file);
} }
@ -159,19 +168,19 @@ public final class ZipPackage extends OPCPackage {
try { try {
// read from the file input stream // read from the file input stream
return openZipEntrySourceStream(fis); return openZipEntrySourceStream(fis);
} catch (final InvalidOperationException|UnsupportedFileFormatException e) {
// abort: close the zip input stream
IOUtils.closeQuietly(fis);
throw e;
} catch (final Exception e) { } catch (final Exception e) {
// abort: close the file input stream // abort: close the file input stream
IOUtils.closeQuietly(fis); IOUtils.closeQuietly(fis);
if (e instanceof InvalidOperationException) {
throw (InvalidOperationException)e;
} else {
throw new InvalidOperationException("Failed to read the file input stream from file: '" + file + "'", e); throw new InvalidOperationException("Failed to read the file input stream from file: '" + file + "'", e);
} }
} }
}
private static ZipEntrySource openZipEntrySourceStream(FileInputStream fis) throws InvalidOperationException { private static ZipEntrySource openZipEntrySourceStream(FileInputStream fis) throws InvalidOperationException {
final ThresholdInputStream zis; final ZipArchiveThresholdInputStream zis;
// Acquire a resource that is needed to read the next level of openZipEntrySourceStream // Acquire a resource that is needed to read the next level of openZipEntrySourceStream
try { try {
// open the zip input stream // open the zip input stream
@ -185,18 +194,18 @@ public final class ZipPackage extends OPCPackage {
try { try {
// read from the zip input stream // read from the zip input stream
return openZipEntrySourceStream(zis); return openZipEntrySourceStream(zis);
} catch (final InvalidOperationException|UnsupportedFileFormatException e) {
// abort: close the zip input stream
IOUtils.closeQuietly(zis);
throw e;
} catch (final Exception e) { } catch (final Exception e) {
// abort: close the zip input stream // abort: close the zip input stream
IOUtils.closeQuietly(zis); IOUtils.closeQuietly(zis);
if (e instanceof InvalidOperationException) {
throw (InvalidOperationException)e;
} else {
throw new InvalidOperationException("Failed to read the zip entry source stream", e); throw new InvalidOperationException("Failed to read the zip entry source stream", e);
} }
} }
}
private static ZipEntrySource openZipEntrySourceStream(ThresholdInputStream zis) throws InvalidOperationException { private static ZipEntrySource openZipEntrySourceStream(ZipArchiveThresholdInputStream zis) throws InvalidOperationException {
// Acquire the final level resource. If this is acquired successfully, the zip package was read successfully from the input stream // Acquire the final level resource. If this is acquired successfully, the zip package was read successfully from the input stream
try { try {
// open the zip entry source stream // open the zip entry source stream
@ -224,70 +233,43 @@ public final class ZipPackage extends OPCPackage {
/** /**
* Retrieves the parts from this package. We assume that the package has not * Retrieves the parts from this package. We assume that the package has not
* been yet inspect to retrieve all the parts, this method will open the * been yet inspect to retrieve all the parts, this method will open the
* archive and look for all parts contain inside it. If the package part * archive and look for all parts contain inside it.
* list is not empty, it will be emptied.
* *
* @return All parts contain in this package. * @return All parts contain in this package.
* @throws InvalidFormatException if the package is not valid. * @throws InvalidFormatException if the package is not valid.
*/ */
@Override @Override
protected PackagePart[] getPartsImpl() throws InvalidFormatException { protected PackagePartCollection getPartsImpl() throws InvalidFormatException {
if (this.partList == null) { final PackagePartCollection newPartList = new PackagePartCollection();
// The package has just been created, we create an empty part
// list.
this.partList = new PackagePartCollection();
}
if (this.zipArchive == null) { if (zipArchive == null) {
return this.partList.sortedValues().toArray( return newPartList;
new PackagePart[this.partList.size()]);
} }
// First we need to parse the content type part // First we need to parse the content type part
Enumeration<? extends ZipEntry> entries = this.zipArchive.getEntries(); final ZipEntry contentTypeEntry =
while (entries.hasMoreElements()) { zipArchive.getEntry(CONTENT_TYPES_PART_NAME);
ZipEntry entry = entries.nextElement(); if (contentTypeEntry != null) {
if (entry.getName().equalsIgnoreCase(
ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
try { try {
this.contentTypeManager = new ZipContentTypeManager( this.contentTypeManager = new ZipContentTypeManager(
getZipArchive().getInputStream(entry), this); zipArchive.getInputStream(contentTypeEntry), this);
} catch (IOException e) { } catch (IOException e) {
throw new InvalidFormatException(e.getMessage(), e); throw new InvalidFormatException(e.getMessage(), e);
} }
break; } else {
}
}
// At this point, we should have loaded the content type part
if (this.contentTypeManager == null) {
// Is it a different Zip-based format? // Is it a different Zip-based format?
int numEntries = 0; final boolean hasMimetype = zipArchive.getEntry(MIMETYPE) != null;
boolean hasMimetype = false; final boolean hasSettingsXML = zipArchive.getEntry(SETTINGS_XML) != null;
boolean hasSettingsXML = false;
entries = this.zipArchive.getEntries();
while (entries.hasMoreElements()) {
final ZipEntry entry = entries.nextElement();
final String name = entry.getName();
if (MIMETYPE.equals(name)) {
hasMimetype = true;
}
if (SETTINGS_XML.equals(name)) {
hasSettingsXML = true;
}
numEntries++;
}
if (hasMimetype && hasSettingsXML) { if (hasMimetype && hasSettingsXML) {
throw new ODFNotOfficeXmlFileException( throw new ODFNotOfficeXmlFileException(
"The supplied data appears to be in ODF (Open Document) Format. " + "The supplied data appears to be in ODF (Open Document) Format. " +
"Formats like these (eg ODS, ODP) are not supported, try Apache ODFToolkit"); "Formats like these (eg ODS, ODP) are not supported, try Apache ODFToolkit");
} }
if (numEntries == 0) { if (!zipArchive.getEntries().hasMoreElements()) {
throw new NotOfficeXmlFileException( throw new NotOfficeXmlFileException(
"No valid entries or contents found, this is not a valid OOXML " + "No valid entries or contents found, this is not a valid OOXML " +
"(Office Open XML) file"); "(Office Open XML) file");
} }
// Fallback exception // Fallback exception
throw new InvalidFormatException( throw new InvalidFormatException(
"Package should contain a content type part [M1.13]"); "Package should contain a content type part [M1.13]");
@ -297,76 +279,69 @@ public final class ZipPackage extends OPCPackage {
// (Need to create relationships before other // (Need to create relationships before other
// parts, otherwise we might create a part before // parts, otherwise we might create a part before
// its relationship exists, and then it won't tie up) // its relationship exists, and then it won't tie up)
entries = this.zipArchive.getEntries(); final List<EntryTriple> entries =
while (entries.hasMoreElements()) { Collections.list(zipArchive.getEntries()).stream()
ZipEntry entry = entries.nextElement(); .map(zae -> new EntryTriple(zae, contentTypeManager))
PackagePartName partName = buildPartName(entry); .filter(mm -> mm.partName != null)
if(partName == null) { .sorted()
continue; .collect(Collectors.toList());
for (final EntryTriple et : entries) {
et.register(newPartList);
} }
// Only proceed for Relationships at this stage return newPartList;
String contentType = contentTypeManager.getContentType(partName);
if (contentType != null && contentType.equals(ContentTypes.RELATIONSHIPS_PART)) {
try {
PackagePart part = new ZipPackagePart(this, entry, partName, contentType);
partList.put(partName, part);
} catch (InvalidOperationException e) {
throw new InvalidFormatException(e.getMessage(), e);
}
}
} }
// Then we can go through all the other parts private class EntryTriple implements Comparable<EntryTriple> {
entries = this.zipArchive.getEntries(); final ZipEntry zipArchiveEntry;
while (entries.hasMoreElements()) { final PackagePartName partName;
ZipEntry entry = entries.nextElement(); final String contentType;
PackagePartName partName = buildPartName(entry);
if(partName == null) {
continue;
}
String contentType = contentTypeManager.getContentType(partName); EntryTriple(final ZipEntry zipArchiveEntry, final ContentTypeManager contentTypeManager) {
if (contentType != null && contentType.equals(ContentTypes.RELATIONSHIPS_PART)) { this.zipArchiveEntry = zipArchiveEntry;
// Already handled
}
else if (contentType != null) {
try {
PackagePart part = new ZipPackagePart(this, entry, partName, contentType);
partList.put(partName, part);
} catch (InvalidOperationException e) {
throw new InvalidFormatException(e.getMessage(), e);
}
} else {
throw new InvalidFormatException(
"The part " + partName.getURI().getPath()
+ " does not have any content type ! Rule: Package require content types when retrieving a part from a package. [M.1.14]");
}
}
return partList.sortedValues().toArray(new PackagePart[partList.size()]); final String entryName = zipArchiveEntry.getName();
} PackagePartName ppn = null;
/**
* Builds a PackagePartName for the given ZipEntry,
* or null if it's the content types / invalid part
*/
private PackagePartName buildPartName(ZipEntry entry) {
try { try {
// We get an error when we parse [Content_Types].xml // We get an error when we parse [Content_Types].xml
// because it's not a valid URI. // because it's not a valid URI.
if (entry.getName().equalsIgnoreCase( ppn = (CONTENT_TYPES_PART_NAME.equalsIgnoreCase(entryName)) ? null
ContentTypeManager.CONTENT_TYPES_PART_NAME)) { : PackagingURIHelper.createPartName(ZipHelper.getOPCNameFromZipItemName(entryName));
return null;
}
return PackagingURIHelper.createPartName(ZipHelper
.getOPCNameFromZipItemName(entry.getName()));
} catch (Exception e) { } catch (Exception e) {
// We assume we can continue, even in degraded mode ... // We assume we can continue, even in degraded mode ...
LOG.log(POILogger.WARN,"Entry " LOG.log(POILogger.WARN,"Entry " + entryName + " is not valid, so this part won't be add to the package.", e);
+ entry.getName() }
+ " is not valid, so this part won't be add to the package.", e);
return null; this.partName = ppn;
this.contentType = (ppn == null) ? null : contentTypeManager.getContentType(partName);
}
void register(final PackagePartCollection partList) throws InvalidFormatException {
if (contentType == null) {
throw new InvalidFormatException("The part " + partName.getURI().getPath() + " does not have any " +
"content type ! Rule: Package require content types when retrieving a part from a package. [M.1.14]");
}
if (partList.containsKey(partName)) {
throw new InvalidFormatException(
"A part with the name '"+partName+"' already exist : Packages shall not contain equivalent part names " +
"and package implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");
}
try {
partList.put(partName, new ZipPackagePart(ZipPackage.this, zipArchiveEntry, partName, contentType, false));
} catch (InvalidOperationException e) {
throw new InvalidFormatException(e.getMessage(), e);
}
}
@Override
public int compareTo(EntryTriple o) {
final int contentTypeOrder1 = RELATIONSHIPS_PART.equals(contentType) ? -1 : 1;
final int contentTypeOrder2 = RELATIONSHIPS_PART.equals(o.contentType) ? -1 : 1;
final int cmpCT = Integer.compare(contentTypeOrder1, contentTypeOrder2);
return cmpCT != 0 ? cmpCT : partName.compareTo(o.partName);
} }
} }
@ -494,21 +469,6 @@ public final class ZipPackage extends OPCPackage {
} }
} }
/**
* Implement the getPart() method to retrieve a part from its URI in the
* current package
*
*
* @see #getPart(PackageRelationship)
*/
@Override
protected PackagePart getPartImpl(PackagePartName partName) {
if (partList.containsKey(partName)) {
return partList.get(partName);
}
return null;
}
/** /**
* Save this package into the specified stream * Save this package into the specified stream
* *
@ -523,13 +483,8 @@ public final class ZipPackage extends OPCPackage {
// Check that the document was open in write mode // Check that the document was open in write mode
throwExceptionIfReadOnly(); throwExceptionIfReadOnly();
final ZipOutputStream zos; try (final ZipOutputStream zos = (outputStream instanceof ZipOutputStream)
try { ? (ZipOutputStream) outputStream : new ZipOutputStream(outputStream)) {
if (!(outputStream instanceof ZipOutputStream)) {
zos = new ZipOutputStream(outputStream);
} else {
zos = (ZipOutputStream) outputStream;
}
// If the core properties part does not exist in the part list, // If the core properties part does not exist in the part list,
// we save it as well // we save it as well
@ -574,20 +529,14 @@ public final class ZipPackage extends OPCPackage {
final PackagePartName ppn = part.getPartName(); final PackagePartName ppn = part.getPartName();
LOG.log(POILogger.DEBUG,"Save part '" + ZipHelper.getZipItemNameFromOPCName(ppn.getName()) + "'"); LOG.log(POILogger.DEBUG,"Save part '" + ZipHelper.getZipItemNameFromOPCName(ppn.getName()) + "'");
PartMarshaller marshaller = partMarshallers.get(part._contentType); final PartMarshaller marshaller = partMarshallers.get(part._contentType);
String errMsg = "The part " + ppn.getURI() + " failed to be saved in the stream with marshaller ";
if (marshaller != null) { final PartMarshaller pm = (marshaller != null) ? marshaller : defaultPartMarshaller;
if (!marshaller.marshall(part, zos)) { if (!pm.marshall(part, zos)) {
throw new OpenXML4JException(errMsg + marshaller); String errMsg = "The part " + ppn.getURI() + " failed to be saved in the stream with marshaller ";
} throw new OpenXML4JException(errMsg + pm);
} else {
if (!defaultPartMarshaller.marshall(part, zos)) {
throw new OpenXML4JException(errMsg + defaultPartMarshaller);
} }
} }
}
zos.close();
} catch (OpenXML4JRuntimeException e) { } catch (OpenXML4JRuntimeException e) {
// no need to wrap this type of Exception // no need to wrap this type of Exception
throw e; throw e;

View File

@ -25,6 +25,7 @@ import java.util.zip.ZipEntry;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.internal.ContentType;
import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller; import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPartMarshaller;
import org.apache.poi.util.NotImplemented; import org.apache.poi.util.NotImplemented;
@ -48,11 +49,11 @@ public class ZipPackagePart extends PackagePart {
* @param container * @param container
* The container package. * The container package.
* @param partName * @param partName
* Part name. * The part name.
* @param contentType * @param contentType
* Content type. * Content type.
* @throws InvalidFormatException * @throws InvalidFormatException
* Throws if the content of this part invalid. * Throws if the content of this part is invalid.
*/ */
public ZipPackagePart(OPCPackage container, PackagePartName partName, public ZipPackagePart(OPCPackage container, PackagePartName partName,
String contentType) throws InvalidFormatException { String contentType) throws InvalidFormatException {
@ -73,10 +74,10 @@ public class ZipPackagePart extends PackagePart {
* @throws InvalidFormatException * @throws InvalidFormatException
* Throws if the content of this part is invalid. * Throws if the content of this part is invalid.
*/ */
public ZipPackagePart(OPCPackage container, ZipEntry zipEntry, /* package */ ZipPackagePart(OPCPackage container, ZipEntry zipEntry,
PackagePartName partName, String contentType) PackagePartName partName, String contentType, boolean loadRelationships)
throws InvalidFormatException { throws InvalidFormatException {
super(container, partName, contentType); super(container, partName, new ContentType(contentType), loadRelationships);
this.zipEntry = zipEntry; this.zipEntry = zipEntry;
} }

View File

@ -57,26 +57,23 @@ public class ZipContentTypeManager extends ContentTypeManager {
@SuppressWarnings("resource") @SuppressWarnings("resource")
@Override @Override
public boolean saveImpl(Document content, OutputStream out) { public boolean saveImpl(Document content, OutputStream out) {
ZipOutputStream zos = null; final ZipOutputStream zos = (out instanceof ZipOutputStream)
if (out instanceof ZipOutputStream) ? (ZipOutputStream) out : new ZipOutputStream(out);
zos = (ZipOutputStream) out;
else
zos = new ZipOutputStream(out);
ZipEntry partEntry = new ZipEntry(CONTENT_TYPES_PART_NAME); ZipEntry partEntry = new ZipEntry(CONTENT_TYPES_PART_NAME);
try { try {
// Referenced in ZIP // Referenced in ZIP
zos.putNextEntry(partEntry); zos.putNextEntry(partEntry);
try {
// Saving data in the ZIP file // Saving data in the ZIP file
if (!StreamHelper.saveXmlInStream(content, zos)) { return StreamHelper.saveXmlInStream(content, zos);
return false; } finally {
}
zos.closeEntry(); zos.closeEntry();
}
} catch (IOException ioe) { } catch (IOException ioe) {
logger.log(POILogger.ERROR, "Cannot write: " + CONTENT_TYPES_PART_NAME logger.log(POILogger.ERROR, "Cannot write: " + CONTENT_TYPES_PART_NAME
+ " in Zip !", ioe); + " in Zip !", ioe);
return false; return false;
} }
return true;
} }
} }

View File

@ -24,9 +24,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException; import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
@ -34,8 +32,8 @@ import org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException;
import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.openxml4j.opc.ZipPackage; import org.apache.poi.openxml4j.opc.ZipPackage;
import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream;
import org.apache.poi.openxml4j.util.ZipSecureFile; import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
import org.apache.poi.poifs.filesystem.FileMagic; import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -72,24 +70,6 @@ public final class ZipHelper {
return new ZipEntry(corePropsRel.getTargetURI().getPath()); return new ZipEntry(corePropsRel.getTargetURI().getPath());
} }
/**
* Retrieve the Zip entry of the content types part.
*/
public static ZipEntry getContentTypeZipEntry(ZipPackage pkg) {
Enumeration<? extends ZipEntry> entries = pkg.getZipArchive().getEntries();
// Enumerate through the Zip entries until we find the one named
// '[Content_Types].xml'.
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().equals(
ContentTypeManager.CONTENT_TYPES_PART_NAME)) {
return entry;
}
}
return null;
}
/** /**
* Convert a zip name into an OPC name by adding a leading forward slash to * Convert a zip name into an OPC name by adding a leading forward slash to
* the specified item name. * the specified item name.
@ -158,7 +138,7 @@ public final class ZipHelper {
* Warning - this will consume the first few bytes of the stream, * Warning - this will consume the first few bytes of the stream,
* you should push-back or reset the stream after use! * you should push-back or reset the stream after use!
*/ */
public static void verifyZipHeader(InputStream stream) throws NotOfficeXmlFileException, IOException { private static void verifyZipHeader(InputStream stream) throws NotOfficeXmlFileException, IOException {
InputStream is = FileMagic.prepareToCheckMagic(stream); InputStream is = FileMagic.prepareToCheckMagic(stream);
FileMagic fm = FileMagic.valueOf(is); FileMagic fm = FileMagic.valueOf(is);
@ -191,14 +171,14 @@ public final class ZipHelper {
* @return The zip stream freshly open. * @return The zip stream freshly open.
*/ */
@SuppressWarnings("resource") @SuppressWarnings("resource")
public static ThresholdInputStream openZipStream(InputStream stream) throws IOException { public static ZipArchiveThresholdInputStream openZipStream(InputStream stream) throws IOException {
// Peek at the first few bytes to sanity check // Peek at the first few bytes to sanity check
InputStream checkedStream = FileMagic.prepareToCheckMagic(stream); InputStream checkedStream = FileMagic.prepareToCheckMagic(stream);
verifyZipHeader(checkedStream); verifyZipHeader(checkedStream);
// Open as a proper zip stream // Open as a proper zip stream
InputStream zis = new ZipInputStream(checkedStream); InputStream zis = new ZipInputStream(checkedStream);
return ZipSecureFile.addThreshold(zis); return new ZipArchiveThresholdInputStream(zis);
} }
/** /**
@ -211,7 +191,7 @@ public final class ZipHelper {
* @throws IOException if the zip file cannot be opened or closed to read the header signature * @throws IOException if the zip file cannot be opened or closed to read the header signature
* @throws NotOfficeXmlFileException if stream does not start with zip header signature * @throws NotOfficeXmlFileException if stream does not start with zip header signature
*/ */
public static ZipFile openZipFile(File file) throws IOException, NotOfficeXmlFileException { public static ZipSecureFile openZipFile(File file) throws IOException, NotOfficeXmlFileException {
if (!file.exists()) { if (!file.exists()) {
throw new FileNotFoundException("File does not exist"); throw new FileNotFoundException("File does not exist");
} }
@ -235,7 +215,7 @@ public final class ZipHelper {
* The file path. * The file path.
* @return The zip archive freshly open. * @return The zip archive freshly open.
*/ */
public static ZipFile openZipFile(String path) throws IOException { public static ZipSecureFile openZipFile(String path) throws IOException {
return openZipFile(new File(path)); return openZipFile(new File(path));
} }
} }

View File

@ -49,15 +49,15 @@ public final class ZipPackagePropertiesMarshaller extends PackagePropertiesMarsh
try { try {
// Save in ZIP // Save in ZIP
zos.putNextEntry(ctEntry); // Add entry in ZIP zos.putNextEntry(ctEntry); // Add entry in ZIP
try {
super.marshall(part, out); // Marshall the properties inside a XML super.marshall(part, out); // Marshall the properties inside a XML
// Document // Document
if (!StreamHelper.saveXmlInStream(xmlDoc, out)) { return StreamHelper.saveXmlInStream(xmlDoc, out);
return false; } finally {
}
zos.closeEntry(); zos.closeEntry();
}
} catch (IOException e) { } catch (IOException e) {
throw new OpenXML4JException(e.getLocalizedMessage(), e); throw new OpenXML4JException(e.getLocalizedMessage(), e);
} }
return true;
} }
} }

View File

@ -36,6 +36,7 @@ import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.openxml4j.opc.internal.PartMarshaller; import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
import org.apache.poi.openxml4j.opc.internal.ZipHelper; import org.apache.poi.openxml4j.opc.internal.ZipHelper;
import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.DocumentHelper;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.xssf.usermodel.XSSFRelation; import org.apache.poi.xssf.usermodel.XSSFRelation;
@ -47,7 +48,6 @@ import org.w3c.dom.Element;
*/ */
public final class ZipPartMarshaller implements PartMarshaller { public final class ZipPartMarshaller implements PartMarshaller {
private final static POILogger logger = POILogFactory.getLogger(ZipPartMarshaller.class); private final static POILogger logger = POILogFactory.getLogger(ZipPartMarshaller.class);
private final static int READ_WRITE_FILE_BUFFER_SIZE = 8192;
/** /**
* Save the specified part. * Save the specified part.
@ -80,17 +80,11 @@ public final class ZipPartMarshaller implements PartMarshaller {
zos.putNextEntry(partEntry); zos.putNextEntry(partEntry);
// Saving data in the ZIP file // Saving data in the ZIP file
InputStream ins = part.getInputStream(); try (final InputStream ins = part.getInputStream()) {
byte[] buff = new byte[READ_WRITE_FILE_BUFFER_SIZE]; IOUtils.copy(ins, zos);
while (ins.available() > 0) { } finally {
int resultRead = ins.read(buff);
if (resultRead == -1) {
// End of file reached
break;
}
zos.write(buff, 0, resultRead);
}
zos.closeEntry(); zos.closeEntry();
}
} catch (IOException ioe) { } catch (IOException ioe) {
logger.log(POILogger.ERROR,"Cannot write: " + part.getPartName() + ": in ZIP", logger.log(POILogger.ERROR,"Cannot write: " + part.getPartName() + ": in ZIP",
ioe); ioe);
@ -178,14 +172,14 @@ public final class ZipPartMarshaller implements PartMarshaller {
relPartName.getURI().toASCIIString()).getPath()); relPartName.getURI().toASCIIString()).getPath());
try { try {
zos.putNextEntry(ctEntry); zos.putNextEntry(ctEntry);
if (!StreamHelper.saveXmlInStream(xmlOutDoc, zos)) { try {
return false; return StreamHelper.saveXmlInStream(xmlOutDoc, zos);
} } finally {
zos.closeEntry(); zos.closeEntry();
}
} catch (IOException e) { } catch (IOException e) {
logger.log(POILogger.ERROR,"Cannot create zip entry " + relPartName, e); logger.log(POILogger.ERROR,"Cannot create zip entry " + relPartName, e);
return false; return false;
} }
return true; // success
} }
} }

View File

@ -0,0 +1,53 @@
/* ====================================================================
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.poi.openxml4j.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import org.apache.poi.util.IOUtils;
/**
* So we can close the real zip entry and still
* effectively work with it.
* Holds the (decompressed!) data in memory, so
* close this as soon as you can!
*/
/* package */ class ZipArchiveFakeEntry extends ZipEntry {
private final byte[] data;
ZipArchiveFakeEntry(ZipEntry entry, InputStream inp) throws IOException {
super(entry.getName());
final long entrySize = entry.getSize();
if (entrySize < -1 || entrySize>=Integer.MAX_VALUE) {
throw new IOException("ZIP entry size is too large or invalid");
}
// Grab the de-compressed contents for later
data = (entrySize == -1) ? IOUtils.toByteArray(inp) : IOUtils.toByteArray(inp, (int)entrySize);
}
public InputStream getInputStream() {
return new ByteArrayInputStream(data);
}
}

View File

@ -0,0 +1,243 @@
/* ====================================================================
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.poi.openxml4j.util;
import static org.apache.poi.openxml4j.util.ZipSecureFile.MAX_ENTRY_SIZE;
import static org.apache.poi.openxml4j.util.ZipSecureFile.MIN_INFLATE_RATIO;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.SuppressForbidden;
public class ZipArchiveThresholdInputStream extends PushbackInputStream {
private static final POILogger LOG = POILogFactory.getLogger(ZipArchiveThresholdInputStream.class);
// don't alert for expanded sizes smaller than 100k
private static final long GRACE_ENTRY_SIZE = 100*1024L;
private static final String MAX_ENTRY_SIZE_MSG =
"Zip bomb detected! The file would exceed the max size of the expanded data in the zip-file.\n" +
"This may indicates that the file is used to inflate memory usage and thus could pose a security risk.\n" +
"You can adjust this limit via ZipSecureFile.setMaxEntrySize() if you need to work with files which are very large.\n" +
"Counter: %d, cis.counter: %d\n" +
"Limits: MAX_ENTRY_SIZE: %d, Entry: %s";
private static final String MIN_INFLATE_RATIO_MSG =
"Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.\n" +
"This may indicate that the file is used to inflate memory usage and thus could pose a security risk.\n" +
"You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.\n" +
"Counter: %d, cis.counter: %d, ratio: %f\n" +
"Limits: MIN_INFLATE_RATIO: %f, Entry: %s";
private static final String SECURITY_BLOCKED =
"SecurityManager doesn't allow manipulation via reflection for zipbomb detection - continue with original input stream";
/**
* the reference to the current entry is only used for a more detailed log message in case of an error
*/
private ZipEntry entry;
private long counter;
private long markPos;
private final ZipArchiveThresholdInputStream cis;
private boolean guardState = true;
public ZipArchiveThresholdInputStream(final InputStream zipIS) throws IOException {
super(zipIS);
if (zipIS instanceof InflaterInputStream) {
cis = AccessController.doPrivileged(inject(zipIS));
} else {
// the inner stream is a ZipFileInputStream, i.e. the data wasn't compressed
cis = null;
}
}
private ZipArchiveThresholdInputStream(InputStream is, ZipArchiveThresholdInputStream cis) {
super(is);
this.cis = cis;
}
@SuppressForbidden
private static PrivilegedAction<ZipArchiveThresholdInputStream> inject(final InputStream zipIS) {
return () -> {
try {
final Field f = FilterInputStream.class.getDeclaredField("in");
f.setAccessible(true);
final InputStream oldInner = (InputStream)f.get(zipIS);
final ZipArchiveThresholdInputStream inner = new ZipArchiveThresholdInputStream(oldInner, null);
f.set(zipIS, inner);
return inner;
} catch (Exception ex) {
LOG.log(POILogger.WARN, SECURITY_BLOCKED, ex);
}
return null;
};
}
@Override
public int read() throws IOException {
int b = in.read();
if (b > -1) {
advance(1);
}
return b;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
int cnt = in.read(b, off, len);
if (cnt > -1) {
advance(cnt);
}
return cnt;
}
@Override
public long skip(long n) throws IOException {
long s = in.skip(n);
counter += s;
return s;
}
@Override
public synchronized void reset() throws IOException {
counter = markPos;
super.reset();
}
/**
* De-/activate threshold check.
* A disabled guard might make sense, when POI is processing its own temporary data (see #59743)
*
* @param guardState {@code true} (= default) enables the threshold check
*/
public void setGuardState(boolean guardState) {
this.guardState = guardState;
}
public void advance(int advance) throws IOException {
counter += advance;
if (!guardState) {
return;
}
final String entryName = entry == null ? "not set" : entry.getName();
final long cisCount = (cis == null ? 0 : cis.counter);
// check the file size first, in case we are working on uncompressed streams
if(counter > MAX_ENTRY_SIZE) {
throw new IOException(String.format(Locale.ROOT, MAX_ENTRY_SIZE_MSG, counter, cisCount, MAX_ENTRY_SIZE, entryName));
}
// no expanded size?
if (cis == null) {
return;
}
// don't alert for small expanded size
if (counter <= GRACE_ENTRY_SIZE) {
return;
}
double ratio = (double)cis.counter/(double)counter;
if (ratio >= MIN_INFLATE_RATIO) {
return;
}
// one of the limits was reached, report it
throw new IOException(String.format(Locale.ROOT, MIN_INFLATE_RATIO_MSG, counter, cisCount, ratio, MIN_INFLATE_RATIO, entryName));
}
public ZipEntry getNextEntry() throws IOException {
if (!(in instanceof ZipInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
}
counter = 0;
return ((ZipInputStream)in).getNextEntry();
}
public void closeEntry() throws IOException {
if (!(in instanceof ZipInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
}
counter = 0;
((ZipInputStream)in).closeEntry();
}
@Override
public void unread(int b) throws IOException {
if (!(in instanceof PushbackInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
}
if (--counter < 0) {
counter = 0;
}
((PushbackInputStream)in).unread(b);
}
@Override
public void unread(byte[] b, int off, int len) throws IOException {
if (!(in instanceof PushbackInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
}
counter -= len;
if (--counter < 0) {
counter = 0;
}
((PushbackInputStream)in).unread(b, off, len);
}
@Override
@SuppressForbidden("just delegating")
public int available() throws IOException {
return in.available();
}
@Override
public boolean markSupported() {
return in.markSupported();
}
@Override
public synchronized void mark(int readlimit) {
markPos = counter;
in.mark(readlimit);
}
/**
* Sets the zip entry for a detailed logging
* @param entry the entry
*/
void setEntry(ZipEntry entry) {
this.entry = entry;
}
}

View File

@ -33,23 +33,32 @@ public interface ZipEntrySource extends Closeable {
/** /**
* Returns an Enumeration of all the Entries * Returns an Enumeration of all the Entries
*/ */
public Enumeration<? extends ZipEntry> getEntries(); Enumeration<? extends ZipEntry> getEntries();
/**
* Return an entry by its path
* @param path the path in unix-notation
* @return the entry or {@code null} if not found
*
* @since POI 4.0.0
*/
ZipEntry getEntry(String path);
/** /**
* Returns an InputStream of the decompressed * Returns an InputStream of the decompressed
* data that makes up the entry * data that makes up the entry
*/ */
public InputStream getInputStream(ZipEntry entry) throws IOException; InputStream getInputStream(ZipEntry entry) throws IOException;
/** /**
* Indicates we are done with reading, and * Indicates we are done with reading, and
* resources may be freed * resources may be freed
*/ */
@Override @Override
public void close() throws IOException; void close() throws IOException;
/** /**
* Has close been called already? * Has close been called already?
*/ */
public boolean isClosed(); boolean isClosed();
} }

View File

@ -16,6 +16,9 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.openxml4j.util; package org.apache.poi.openxml4j.util;
import static org.apache.commons.collections4.IteratorUtils.asIterable;
import static org.apache.commons.collections4.IteratorUtils.asIterator;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Enumeration; import java.util.Enumeration;
@ -33,16 +36,20 @@ public class ZipFileZipEntrySource implements ZipEntrySource {
this.zipArchive = zipFile; this.zipArchive = zipFile;
} }
@Override
public void close() throws IOException { public void close() throws IOException {
if(zipArchive != null) { if(zipArchive != null) {
zipArchive.close(); zipArchive.close();
} }
zipArchive = null; zipArchive = null;
} }
@Override
public boolean isClosed() { public boolean isClosed() {
return (zipArchive == null); return (zipArchive == null);
} }
@Override
public Enumeration<? extends ZipEntry> getEntries() { public Enumeration<? extends ZipEntry> getEntries() {
if (zipArchive == null) if (zipArchive == null)
throw new IllegalStateException("Zip File is closed"); throw new IllegalStateException("Zip File is closed");
@ -50,10 +57,30 @@ public class ZipFileZipEntrySource implements ZipEntrySource {
return zipArchive.entries(); return zipArchive.entries();
} }
@Override
public InputStream getInputStream(ZipEntry entry) throws IOException { public InputStream getInputStream(ZipEntry entry) throws IOException {
if (zipArchive == null) if (zipArchive == null)
throw new IllegalStateException("Zip File is closed"); throw new IllegalStateException("Zip File is closed");
return zipArchive.getInputStream(entry); return zipArchive.getInputStream(entry);
} }
@Override
public ZipEntry getEntry(final String path) {
String normalizedPath = path.replace('\\', '/');
final ZipEntry entry = zipArchive.getEntry(normalizedPath);
if (entry != null) {
return entry;
}
// the opc spec allows case-insensitive filename matching (see #49609)
for (final ZipEntry ze : asIterable(asIterator(zipArchive.entries()))) {
if (normalizedPath.equalsIgnoreCase(ze.getName().replace('\\','/'))) {
return ze;
}
}
return null;
}
} }

View File

@ -16,16 +16,14 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.openxml4j.util; package org.apache.poi.openxml4j.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream; import org.apache.commons.collections4.IteratorUtils;
/** /**
* Provides a way to get at all the ZipEntries * Provides a way to get at all the ZipEntries
@ -36,7 +34,7 @@ import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
* done, to free up that memory! * done, to free up that memory!
*/ */
public class ZipInputStreamZipEntrySource implements ZipEntrySource { public class ZipInputStreamZipEntrySource implements ZipEntrySource {
private ArrayList<FakeZipEntry> zipEntries; private final Map<String, ZipArchiveFakeEntry> zipEntries = new HashMap<>();
/** /**
* Reads all the entries from the ZipInputStream * Reads all the entries from the ZipInputStream
@ -44,100 +42,53 @@ public class ZipInputStreamZipEntrySource implements ZipEntrySource {
* We'll then eat lots of memory, but be able to * We'll then eat lots of memory, but be able to
* work with the entries at-will. * work with the entries at-will.
*/ */
public ZipInputStreamZipEntrySource(ThresholdInputStream inp) throws IOException { public ZipInputStreamZipEntrySource(ZipArchiveThresholdInputStream inp) throws IOException {
zipEntries = new ArrayList<>(); for (;;) {
final ZipEntry zipEntry = inp.getNextEntry();
boolean going = true;
while(going) {
ZipEntry zipEntry = inp.getNextEntry();
if (zipEntry == null) { if (zipEntry == null) {
going = false; break;
} else {
FakeZipEntry entry = new FakeZipEntry(zipEntry, inp);
inp.closeEntry();
zipEntries.add(entry);
} }
zipEntries.put(zipEntry.getName(), new ZipArchiveFakeEntry(zipEntry, inp));
} }
inp.close(); inp.close();
} }
@Override
public Enumeration<? extends ZipEntry> getEntries() { public Enumeration<? extends ZipEntry> getEntries() {
return new EntryEnumerator(); return IteratorUtils.asEnumeration(zipEntries.values().iterator());
} }
@Override
public InputStream getInputStream(ZipEntry zipEntry) { public InputStream getInputStream(ZipEntry zipEntry) {
assert (zipEntry instanceof FakeZipEntry); assert (zipEntry instanceof ZipArchiveFakeEntry);
FakeZipEntry entry = (FakeZipEntry)zipEntry; return ((ZipArchiveFakeEntry)zipEntry).getInputStream();
return entry.getInputStream();
} }
@Override
public void close() { public void close() {
// Free the memory // Free the memory
zipEntries = null; zipEntries.clear();
} }
@Override
public boolean isClosed() { public boolean isClosed() {
return (zipEntries == null); return zipEntries.isEmpty();
} }
/** @Override
* Why oh why oh why are Iterator and Enumeration public ZipEntry getEntry(final String path) {
* still not compatible? final String normalizedPath = path.replace('\\', '/');
*/ final ZipEntry ze = zipEntries.get(normalizedPath);
private class EntryEnumerator implements Enumeration<ZipEntry> { if (ze != null) {
private Iterator<? extends ZipEntry> iterator; return ze;
private EntryEnumerator() {
iterator = zipEntries.iterator();
} }
public boolean hasMoreElements() { for (final Map.Entry<String, ZipArchiveFakeEntry> fze : zipEntries.entrySet()) {
return iterator.hasNext(); if (normalizedPath.equalsIgnoreCase(fze.getKey())) {
} return fze.getValue();
public ZipEntry nextElement() {
return iterator.next();
} }
} }
/** return null;
* So we can close the real zip entry and still
* effectively work with it.
* Holds the (decompressed!) data in memory, so
* close this as soon as you can!
*/
public static class FakeZipEntry extends ZipEntry {
private byte[] data;
public FakeZipEntry(ZipEntry entry, InputStream inp) throws IOException {
super(entry.getName());
// Grab the de-compressed contents for later
ByteArrayOutputStream baos;
long entrySize = entry.getSize();
if (entrySize !=-1) {
if (entrySize>=Integer.MAX_VALUE) {
throw new IOException("ZIP entry size is too large");
}
baos = new ByteArrayOutputStream((int) entrySize);
} else {
baos = new ByteArrayOutputStream();
}
byte[] buffer = new byte[4096];
int read = 0;
while( (read = inp.read(buffer)) != -1 ) {
baos.write(buffer, 0, read);
}
data = baos.toByteArray();
}
public InputStream getInputStream() {
return new ByteArrayInputStream(data);
}
} }
} }

View File

@ -18,44 +18,28 @@
package org.apache.poi.openxml4j.util; package org.apache.poi.openxml4j.util;
import java.io.File; import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.SuppressForbidden;
/** /**
* This class wraps a {@link ZipFile} in order to check the * This class wraps a {@link ZipFile} in order to check the
* entries for <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a> * entries for <a href="https://en.wikipedia.org/wiki/Zip_bomb">zip bombs</a>
* while reading the archive. * while reading the archive.<p>
* If a {@link ZipInputStream} is directly used, the wrapper *
* can be applied via {@link #addThreshold(InputStream)}.
* The alert limits can be globally defined via {@link #setMaxEntrySize(long)} * The alert limits can be globally defined via {@link #setMaxEntrySize(long)}
* and {@link #setMinInflateRatio(double)}. * and {@link #setMinInflateRatio(double)}.
*/ */
public class ZipSecureFile extends ZipFile { public class ZipSecureFile extends ZipFile {
private static final POILogger LOG = POILogFactory.getLogger(ZipSecureFile.class); /* package */ static double MIN_INFLATE_RATIO = 0.01d;
/* package */ static long MAX_ENTRY_SIZE = 0xFFFFFFFFL;
private static double MIN_INFLATE_RATIO = 0.01d;
private static long MAX_ENTRY_SIZE = 0xFFFFFFFFL;
// don't alert for expanded sizes smaller than 100k
private final static long GRACE_ENTRY_SIZE = 100*1024L;
// The default maximum size of extracted text // The default maximum size of extracted text
private static long MAX_TEXT_SIZE = 10*1024*1024L; private static long MAX_TEXT_SIZE = 10*1024*1024L;
private final String fileName;
/** /**
* Sets the ratio between de- and inflated bytes to detect zipbomb. * Sets the ratio between de- and inflated bytes to detect zipbomb.
* It defaults to 1% (= 0.01d), i.e. when the compression is better than * It defaults to 1% (= 0.01d), i.e. when the compression is better than
@ -134,16 +118,14 @@ public class ZipSecureFile extends ZipFile {
return MAX_TEXT_SIZE; return MAX_TEXT_SIZE;
} }
public ZipSecureFile(File file, int mode) throws ZipException, IOException { public ZipSecureFile(File file) throws IOException {
super(file, mode);
}
public ZipSecureFile(File file) throws ZipException, IOException {
super(file); super(file);
this.fileName = file.getAbsolutePath();
} }
public ZipSecureFile(String name) throws ZipException, IOException { public ZipSecureFile(String name) throws IOException {
super(name); super(name);
this.fileName = new File(name).getAbsolutePath();
} }
/** /**
@ -156,176 +138,22 @@ public class ZipSecureFile extends ZipFile {
* @param entry the zip file entry * @param entry the zip file entry
* @return the input stream for reading the contents of the specified * @return the input stream for reading the contents of the specified
* zip file entry. * zip file entry.
* @throws ZipException if a ZIP format error has occurred
* @throws IOException if an I/O error has occurred * @throws IOException if an I/O error has occurred
* @throws IllegalStateException if the zip file has been closed * @throws IllegalStateException if the zip file has been closed
*/ */
@Override @Override
@SuppressWarnings("resource") @SuppressWarnings("resource")
public InputStream getInputStream(ZipEntry entry) throws IOException { public ZipArchiveThresholdInputStream getInputStream(ZipEntry entry) throws IOException {
InputStream zipIS = super.getInputStream(entry); ZipArchiveThresholdInputStream zatis = new ZipArchiveThresholdInputStream(super.getInputStream(entry));
return addThreshold(zipIS); zatis.setEntry(entry);
return zatis;
} }
public static ThresholdInputStream addThreshold(final InputStream zipIS) throws IOException { /**
ThresholdInputStream newInner; * Returns the path name of the ZIP file.
if (zipIS instanceof InflaterInputStream) { * @return the path name of the ZIP file
newInner = AccessController.doPrivileged(new PrivilegedAction<ThresholdInputStream>() { // NOSONAR */
@Override public String getName() {
@SuppressForbidden("TODO: Fix this to not use reflection (it will break in Java 9)! " + return fileName;
"Better would be to wrap *before* instead of trying to insert wrapper afterwards.")
public ThresholdInputStream run() {
try {
Field f = FilterInputStream.class.getDeclaredField("in");
f.setAccessible(true);
InputStream oldInner = (InputStream)f.get(zipIS);
ThresholdInputStream newInner2 = new ThresholdInputStream(oldInner, null);
f.set(zipIS, newInner2);
return newInner2;
} catch (Exception ex) {
LOG.log(POILogger.WARN, "SecurityManager doesn't allow manipulation via reflection for zipbomb detection - continue with original input stream", ex);
}
return null;
}
});
} else {
// the inner stream is a ZipFileInputStream, i.e. the data wasn't compressed
newInner = null;
}
return new ThresholdInputStream(zipIS, newInner);
}
public static class ThresholdInputStream extends PushbackInputStream {
long counter;
long markPos;
ThresholdInputStream cis;
public ThresholdInputStream(InputStream is, ThresholdInputStream cis) {
super(is);
this.cis = cis;
}
@Override
public int read() throws IOException {
int b = in.read();
if (b > -1) {
advance(1);
}
return b;
}
@Override
public int read(byte b[], int off, int len) throws IOException {
int cnt = in.read(b, off, len);
if (cnt > -1) {
advance(cnt);
}
return cnt;
}
@Override
public long skip(long n) throws IOException {
long s = in.skip(n);
counter += s;
return s;
}
@Override
public synchronized void reset() throws IOException {
counter = markPos;
super.reset();
}
public void advance(int advance) throws IOException {
counter += advance;
// check the file size first, in case we are working on uncompressed streams
if(counter > MAX_ENTRY_SIZE) {
throw new IOException("Zip bomb detected! The file would exceed the max size of the expanded data in the zip-file. "
+ "This may indicates that the file is used to inflate memory usage and thus could pose a security risk. "
+ "You can adjust this limit via ZipSecureFile.setMaxEntrySize() if you need to work with files which are very large. "
+ "Counter: " + counter + ", cis.counter: " + (cis == null ? 0 : cis.counter)
+ "Limits: MAX_ENTRY_SIZE: " + MAX_ENTRY_SIZE);
}
// no expanded size?
if (cis == null) {
return;
}
// don't alert for small expanded size
if (counter <= GRACE_ENTRY_SIZE) {
return;
}
double ratio = (double)cis.counter/(double)counter;
if (ratio >= MIN_INFLATE_RATIO) {
return;
}
// one of the limits was reached, report it
throw new IOException("Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.\n"
+ "This may indicate that the file is used to inflate memory usage and thus could pose a security risk.\n"
+ "You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.\n"
+ "Counter: " + counter + ", cis.counter: " + cis.counter + ", ratio: " + ratio + "\n"
+ "Limits: MIN_INFLATE_RATIO: " + MIN_INFLATE_RATIO);
}
public ZipEntry getNextEntry() throws IOException {
if (!(in instanceof ZipInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
}
counter = 0;
return ((ZipInputStream)in).getNextEntry();
}
public void closeEntry() throws IOException {
if (!(in instanceof ZipInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a ZipInputStream");
}
counter = 0;
((ZipInputStream)in).closeEntry();
}
@Override
public void unread(int b) throws IOException {
if (!(in instanceof PushbackInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
}
if (--counter < 0) {
counter = 0;
}
((PushbackInputStream)in).unread(b);
}
@Override
public void unread(byte[] b, int off, int len) throws IOException {
if (!(in instanceof PushbackInputStream)) {
throw new UnsupportedOperationException("underlying stream is not a PushbackInputStream");
}
counter -= len;
if (--counter < 0) {
counter = 0;
}
((PushbackInputStream)in).unread(b, off, len);
}
@Override
@SuppressForbidden("just delegating")
public int available() throws IOException {
return in.available();
}
@Override
public boolean markSupported() {
return in.markSupported();
}
@Override
public synchronized void mark(int readlimit) {
markPos = counter;
in.mark(readlimit);
}
} }
} }

View File

@ -53,15 +53,17 @@ import org.apache.poi.util.TempFile;
* sensitive data is not stored in raw format on disk. * sensitive data is not stored in raw format on disk.
*/ */
@Beta @Beta
public class AesZipFileZipEntrySource implements ZipEntrySource { public final class AesZipFileZipEntrySource implements ZipEntrySource {
private static final POILogger LOG = POILogFactory.getLogger(AesZipFileZipEntrySource.class); private static final POILogger LOG = POILogFactory.getLogger(AesZipFileZipEntrySource.class);
private static final String PADDING = "PKCS5Padding";
private final File tmpFile; private final File tmpFile;
private final ZipFile zipFile; private final ZipFile zipFile;
private final Cipher ci; private final Cipher ci;
private boolean closed; private boolean closed;
public AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException { private AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException {
this.tmpFile = tmpFile; this.tmpFile = tmpFile;
this.zipFile = new ZipFile(tmpFile); this.zipFile = new ZipFile(tmpFile);
this.ci = ci; this.ci = ci;
@ -77,6 +79,11 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
return zipFile.entries(); return zipFile.entries();
} }
@Override
public ZipEntry getEntry(String path) {
return zipFile.getEntry(path);
}
@Override @Override
public InputStream getInputStream(ZipEntry entry) throws IOException { public InputStream getInputStream(ZipEntry entry) throws IOException {
InputStream is = zipFile.getInputStream(entry); InputStream is = zipFile.getInputStream(entry);
@ -106,14 +113,14 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
sr.nextBytes(ivBytes); sr.nextBytes(ivBytes);
sr.nextBytes(keyBytes); sr.nextBytes(keyBytes);
final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip"); final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip");
copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes); copyToFile(is, tmpFile, keyBytes, ivBytes);
IOUtils.closeQuietly(is); IOUtils.closeQuietly(is);
return fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes); return fileToSource(tmpFile, keyBytes, ivBytes);
} }
private static void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException { private static void copyToFile(InputStream is, File tmpFile, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId); SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, CipherAlgorithm.aes128.jceId);
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding"); Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, CipherAlgorithm.aes128, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, PADDING);
ZipInputStream zis = new ZipInputStream(is); ZipInputStream zis = new ZipInputStream(is);
FileOutputStream fos = new FileOutputStream(tmpFile); FileOutputStream fos = new FileOutputStream(tmpFile);
@ -146,9 +153,9 @@ public class AesZipFileZipEntrySource implements ZipEntrySource {
zis.close(); zis.close();
} }
private static AesZipFileZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException { private static AesZipFileZipEntrySource fileToSource(File tmpFile, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId); SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, CipherAlgorithm.aes128.jceId);
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding"); Cipher ciDec = CryptoFunctions.getCipher(skeySpec, CipherAlgorithm.aes128, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, PADDING);
return new AesZipFileZipEntrySource(tmpFile, ciDec); return new AesZipFileZipEntrySource(tmpFile, ciDec);
} }

View File

@ -48,6 +48,7 @@ public class EncryptedTempData {
private static POILogger LOG = POILogFactory.getLogger(EncryptedTempData.class); private static POILogger LOG = POILogFactory.getLogger(EncryptedTempData.class);
private final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128; private final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
private final static String PADDING = "PKCS5Padding";
private final SecretKeySpec skeySpec; private final SecretKeySpec skeySpec;
private final byte[] ivBytes; private final byte[] ivBytes;
private final File tempFile; private final File tempFile;
@ -63,12 +64,12 @@ public class EncryptedTempData {
} }
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, null); Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, PADDING);
return new CipherOutputStream(new FileOutputStream(tempFile), ciEnc); return new CipherOutputStream(new FileOutputStream(tempFile), ciEnc);
} }
public InputStream getInputStream() throws IOException { public InputStream getInputStream() throws IOException {
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, null); Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, PADDING);
return new CipherInputStream(new FileInputStream(tempFile), ciDec); return new CipherInputStream(new FileInputStream(tempFile), ciDec);
} }

View File

@ -24,9 +24,9 @@ import java.io.FileOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.poi.openxml4j.opc.internal.ZipHelper; import org.apache.poi.openxml4j.opc.internal.ZipHelper;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.DocumentHelper;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
@ -41,14 +41,13 @@ import org.w3c.dom.Document;
*/ */
public final class XSSFDump { public final class XSSFDump {
private XSSFDump() {}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
for (int i = 0; i < args.length; i++) { for (String arg : args) {
System.out.println("Dumping " + args[i]); System.out.println("Dumping " + arg);
ZipFile zip = ZipHelper.openZipFile(args[i]); try (ZipSecureFile zip = ZipHelper.openZipFile(arg)) {
try {
dump(zip); dump(zip);
} finally {
zip.close();
} }
} }
} }
@ -72,7 +71,7 @@ public final class XSSFDump {
} }
public static void dump(ZipFile zip) throws Exception { public static void dump(ZipSecureFile zip) throws Exception {
String zipname = zip.getName(); String zipname = zip.getName();
int sep = zipname.lastIndexOf('.'); int sep = zipname.lastIndexOf('.');
File root = new File(zipname.substring(0, sep)); File root = new File(zipname.substring(0, sep));
@ -90,8 +89,7 @@ public final class XSSFDump {
} }
File f = new File(root, entry.getName()); File f = new File(root, entry.getName());
OutputStream out = new FileOutputStream(f); try (final OutputStream out = new FileOutputStream(f)) {
try {
if (entry.getName().endsWith(".xml") || entry.getName().endsWith(".vml") || entry.getName().endsWith(".rels")) { if (entry.getName().endsWith(".xml") || entry.getName().endsWith(".vml") || entry.getName().endsWith(".rels")) {
try { try {
Document doc = DocumentHelper.readDocument(zip.getInputStream(entry)); Document doc = DocumentHelper.readDocument(zip.getInputStream(entry));
@ -106,8 +104,6 @@ public final class XSSFDump {
} else { } else {
IOUtils.copy(zip.getInputStream(entry), out); IOUtils.copy(zip.getInputStream(entry), out);
} }
} finally {
out.close();
} }
} }
} }

View File

@ -36,6 +36,7 @@ import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.util.ZipArchiveThresholdInputStream;
import org.apache.poi.openxml4j.util.ZipEntrySource; import org.apache.poi.openxml4j.util.ZipEntrySource;
import org.apache.poi.openxml4j.util.ZipFileZipEntrySource; import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.SpreadsheetVersion;
@ -50,7 +51,13 @@ import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.SheetVisibility; import org.apache.poi.ss.usermodel.SheetVisibility;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.*; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.NotImplemented;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.Removal;
import org.apache.poi.util.TempFile;
import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFChartSheet; import org.apache.poi.xssf.usermodel.XSSFChartSheet;
import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFSheet;
@ -369,13 +376,17 @@ public class SXSSFWorkbook implements Workbook {
} }
protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException { protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException {
try {
try (ZipOutputStream zos = new ZipOutputStream(out)) { try (ZipOutputStream zos = new ZipOutputStream(out)) {
Enumeration<? extends ZipEntry> en = zipEntrySource.getEntries(); Enumeration<? extends ZipEntry> en = zipEntrySource.getEntries();
while (en.hasMoreElements()) { while (en.hasMoreElements()) {
ZipEntry ze = en.nextElement(); ZipEntry ze = en.nextElement();
zos.putNextEntry(new ZipEntry(ze.getName())); zos.putNextEntry(new ZipEntry(ze.getName()));
InputStream is = zipEntrySource.getInputStream(ze); try (final InputStream is = zipEntrySource.getInputStream(ze)) {
if (is instanceof ZipArchiveThresholdInputStream) {
// #59743 - disable Threshold handling for SXSSF copy
// as users tend to put too much repetitive data in when using SXSSF :)
((ZipArchiveThresholdInputStream)is).setGuardState(false);
}
XSSFSheet xSheet = getSheetFromZipEntryName(ze.getName()); XSSFSheet xSheet = getSheetFromZipEntryName(ze.getName());
// See bug 56557, we should not inject data into the special ChartSheets // See bug 56557, we should not inject data into the special ChartSheets
if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) { if (xSheet != null && !(xSheet instanceof XSSFChartSheet)) {
@ -386,7 +397,6 @@ public class SXSSFWorkbook implements Workbook {
} else { } else {
IOUtils.copy(is, zos); IOUtils.copy(is, zos);
} }
is.close();
} }
} }
} finally { } finally {

View File

@ -17,6 +17,7 @@
package org.apache.poi.openxml4j.opc; package org.apache.poi.openxml4j.opc;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -34,13 +35,14 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PushbackInputStream; import java.io.PushbackInputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@ -53,7 +55,6 @@ import org.apache.poi.POITextExtractor;
import org.apache.poi.POIXMLException; import org.apache.poi.POIXMLException;
import org.apache.poi.UnsupportedFileFormatException; import org.apache.poi.UnsupportedFileFormatException;
import org.apache.poi.extractor.ExtractorFactory; import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.openxml4j.OpenXML4JTestDataSamples; import org.apache.poi.openxml4j.OpenXML4JTestDataSamples;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
@ -66,6 +67,8 @@ import org.apache.poi.openxml4j.opc.internal.FileHelper;
import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart; import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
import org.apache.poi.openxml4j.opc.internal.ZipHelper; import org.apache.poi.openxml4j.opc.internal.ZipHelper;
import org.apache.poi.openxml4j.util.ZipSecureFile; import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.sl.usermodel.SlideShow;
import org.apache.poi.sl.usermodel.SlideShowFactory;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.DocumentHelper;
@ -74,17 +77,26 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.TempFile; import org.apache.poi.util.TempFile;
import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public final class TestPackage { public final class TestPackage {
private static final POILogger logger = POILogFactory.getLogger(TestPackage.class); private static final POILogger logger = POILogFactory.getLogger(TestPackage.class);
@Rule
public ExpectedException expectedEx = ExpectedException.none();
/** /**
* Test that just opening and closing the file doesn't alter the document. * Test that just opening and closing the file doesn't alter the document.
*/ */
@ -114,7 +126,7 @@ public final class TestPackage {
*/ */
@Test @Test
public void createGetsContentTypes() public void createGetsContentTypes()
throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException { throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException {
File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestCreatePackageTMP.docx"); File targetFile = OpenXML4JTestDataSamples.getOutputFile("TestCreatePackageTMP.docx");
// Zap the target file, in case of an earlier run // Zap the target file, in case of an earlier run
@ -596,7 +608,7 @@ public final class TestPackage {
} }
@Test @Test
public void getPartsByName() throws IOException, InvalidFormatException { public void getPartsByName() throws InvalidFormatException {
String filepath = OpenXML4JTestDataSamples.getSampleFileName("sample.docx"); String filepath = OpenXML4JTestDataSamples.getSampleFileName("sample.docx");
@SuppressWarnings("resource") @SuppressWarnings("resource")
@ -653,7 +665,7 @@ public final class TestPackage {
@Test @Test
public void replaceContentType() public void replaceContentType()
throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException { throws IOException, InvalidFormatException, SecurityException, IllegalArgumentException {
InputStream is = OpenXML4JTestDataSamples.openSampleStream("sample.xlsx"); InputStream is = OpenXML4JTestDataSamples.openSampleStream("sample.xlsx");
@SuppressWarnings("resource") @SuppressWarnings("resource")
OPCPackage p = OPCPackage.open(is); OPCPackage p = OPCPackage.open(is);
@ -760,34 +772,39 @@ public final class TestPackage {
} }
} }
@Test(expected=IOException.class) /**
* Zip bomb handling test
*
* see bug #50090 / #56865
*/
@Test
public void zipBombCreateAndHandle() public void zipBombCreateAndHandle()
throws IOException, EncryptedDocumentException, InvalidFormatException { throws IOException, EncryptedDocumentException, InvalidFormatException {
// #50090 / #56865 ByteArrayOutputStream bos = new ByteArrayOutputStream(2500000);
ZipFile zipFile = ZipHelper.openZipFile(OpenXML4JTestDataSamples.getSampleFile("sample.xlsx"));
try (ZipFile zipFile = ZipHelper.openZipFile(OpenXML4JTestDataSamples.getSampleFile("sample.xlsx"));
ZipOutputStream append = new ZipOutputStream(bos)) {
assertNotNull(zipFile); assertNotNull(zipFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream(2500000);
ZipOutputStream append = new ZipOutputStream(bos);
// first, copy contents from existing war // first, copy contents from existing war
Enumeration<? extends ZipEntry> entries = zipFile.entries(); Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
ZipEntry e2 = entries.nextElement(); final ZipEntry eIn = entries.nextElement();
ZipEntry e = new ZipEntry(e2.getName()); final ZipEntry eOut = new ZipEntry(eIn.getName());
e.setTime(e2.getTime()); eOut.setTime(eIn.getTime());
e.setComment(e2.getComment()); eOut.setComment(eIn.getComment());
e.setSize(e2.getSize()); eOut.setSize(eIn.getSize());
append.putNextEntry(e); append.putNextEntry(eOut);
if (!e.isDirectory()) { if (!eOut.isDirectory()) {
InputStream is = zipFile.getInputStream(e); try (InputStream is = zipFile.getInputStream(eIn)) {
if (e.getName().equals("[Content_Types].xml")) { if (eOut.getName().equals("[Content_Types].xml")) {
ByteArrayOutputStream bos2 = new ByteArrayOutputStream(); ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
IOUtils.copy(is, bos2); IOUtils.copy(is, bos2);
long size = bos2.size() - "</Types>".length(); long size = bos2.size() - "</Types>".length();
append.write(bos2.toByteArray(), 0, (int) size); append.write(bos2.toByteArray(), 0, (int) size);
byte spam[] = new byte[0x7FFF]; byte spam[] = new byte[0x7FFF];
for (int i=0; i<spam.length; i++) spam[i] = ' '; Arrays.fill(spam, (byte) ' ');
// 0x7FFF0000 is the maximum for 32-bit zips, but less still works // 0x7FFF0000 is the maximum for 32-bit zips, but less still works
while (size < 0x7FFF00) { while (size < 0x7FFF00) {
append.write(spam); append.write(spam);
@ -795,100 +812,129 @@ public final class TestPackage {
} }
append.write("</Types>".getBytes("UTF-8")); append.write("</Types>".getBytes("UTF-8"));
size += 8; size += 8;
e.setSize(size); eOut.setSize(size);
} else { } else {
IOUtils.copy(is, append); IOUtils.copy(is, append);
} }
is.close(); }
} }
append.closeEntry(); append.closeEntry();
} }
}
append.close(); expectedEx.expect(IOException.class);
zipFile.close(); expectedEx.expectMessage("Zip bomb detected!");
byte buf[] = bos.toByteArray(); try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bos.toByteArray()))) {
//noinspection UnusedAssignment
bos = null;
Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(buf));
wb.getSheetAt(0); wb.getSheetAt(0);
wb.close(); }
zipFile.close();
} }
@Test @Test
public void zipBombSampleFiles() throws IOException, OpenXML4JException, XmlException { public void testZipEntityExpansionTerminates() throws IOException, OpenXML4JException, XmlException {
openZipBombFile("poc-shared-strings.xlsx"); expectedEx.expect(IllegalStateException.class);
openZipBombFile("poc-xmlbomb.xlsx"); expectedEx.expectMessage("The text would exceed the max allowed overall size of extracted text.");
openZipBombFile("poc-xmlbomb-empty.xlsx"); openXmlBombFile("poc-shared-strings.xlsx");
} }
private void openZipBombFile(String file) throws IOException, OpenXML4JException, XmlException { @Test
public void testZipEntityExpansionSharedStringTableEvents() throws IOException, OpenXML4JException, XmlException {
boolean before = ExtractorFactory.getThreadPrefersEventExtractors();
ExtractorFactory.setThreadPrefersEventExtractors(true);
try { try {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook(file); expectedEx.expect(IllegalStateException.class);
wb.close(); expectedEx.expectMessage("The text would exceed the max allowed overall size of extracted text.");
openXmlBombFile("poc-shared-strings.xlsx");
} finally {
ExtractorFactory.setThreadPrefersEventExtractors(before);
}
}
try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx"))) {
@Test
public void testZipEntityExpansionExceedsMemory() throws IOException, OpenXML4JException, XmlException {
expectedEx.expect(POIXMLException.class);
expectedEx.expectMessage("Unable to parse xml bean");
expectedEx.expectCause(getCauseMatcher(SAXParseException.class, "The parser has encountered more than"));
openXmlBombFile("poc-xmlbomb.xlsx");
}
@Test
public void testZipEntityExpansionExceedsMemory2() throws IOException, OpenXML4JException, XmlException {
expectedEx.expect(POIXMLException.class);
expectedEx.expectMessage("Unable to parse xml bean");
expectedEx.expectCause(getCauseMatcher(SAXParseException.class, "The parser has encountered more than"));
openXmlBombFile("poc-xmlbomb-empty.xlsx");
}
private void openXmlBombFile(String file) throws IOException, OpenXML4JException, XmlException {
final double minInf = ZipSecureFile.getMinInflateRatio();
ZipSecureFile.setMinInflateRatio(0.002);
try (POITextExtractor extractor = ExtractorFactory.createExtractor(XSSFTestDataSamples.getSampleFile(file))) {
assertNotNull(extractor); assertNotNull(extractor);
extractor.getText(); extractor.getText();
} } finally {
ZipSecureFile.setMinInflateRatio(minInf);
fail("Should catch an exception because of a ZipBomb");
} catch (IllegalStateException e) {
if(!e.getMessage().contains("The text would exceed the max allowed overall size of extracted text.")) {
throw e;
}
} catch (POIXMLException e) {
checkForZipBombException(e);
} }
} }
@Test @Test
public void zipBombCheckSizes() throws IOException, EncryptedDocumentException, InvalidFormatException { public void zipBombCheckSizesWithinLimits() throws IOException, EncryptedDocumentException, InvalidFormatException {
File file = OpenXML4JTestDataSamples.getSampleFile("sample.xlsx"); getZipStatsAndConsume((max_size, min_ratio) -> {
try {
double min_ratio = Double.MAX_VALUE;
long max_size = 0;
ZipFile zf = ZipHelper.openZipFile(file);
assertNotNull(zf);
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
double ratio = (double)ze.getCompressedSize() / (double)ze.getSize();
min_ratio = Math.min(min_ratio, ratio);
max_size = Math.max(max_size, ze.getSize());
}
zf.close();
// use values close to, but within the limits // use values close to, but within the limits
ZipSecureFile.setMinInflateRatio(min_ratio - 0.002); ZipSecureFile.setMinInflateRatio(min_ratio - 0.002);
assertEquals(min_ratio - 0.002, ZipSecureFile.getMinInflateRatio(), 0.00001); assertEquals(min_ratio - 0.002, ZipSecureFile.getMinInflateRatio(), 0.00001);
ZipSecureFile.setMaxEntrySize(max_size + 1); ZipSecureFile.setMaxEntrySize(max_size + 1);
assertEquals(max_size + 1, ZipSecureFile.getMaxEntrySize()); assertEquals(max_size + 1, ZipSecureFile.getMaxEntrySize());
});
}
WorkbookFactory.create(file, null, true).close(); @Test
public void zipBombCheckSizesRatioTooSmall() throws IOException, EncryptedDocumentException, InvalidFormatException {
expectedEx.expect(POIXMLException.class);
expectedEx.expectMessage("You can adjust this limit via ZipSecureFile.setMinInflateRatio()");
getZipStatsAndConsume((max_size, min_ratio) -> {
// check ratio out of bounds // check ratio out of bounds
ZipSecureFile.setMinInflateRatio(min_ratio+0.002); ZipSecureFile.setMinInflateRatio(min_ratio+0.002);
try { });
WorkbookFactory.create(file, null, true).close();
// this is a bit strange, as there will be different exceptions thrown
// depending if this executed via "ant test" or within eclipse
// maybe a difference in JDK ...
} catch (InvalidFormatException | POIXMLException e) {
checkForZipBombException(e);
} }
@Test
public void zipBombCheckSizesSizeTooBig() throws IOException, EncryptedDocumentException, InvalidFormatException {
expectedEx.expect(POIXMLException.class);
expectedEx.expectMessage("You can adjust this limit via ZipSecureFile.setMaxEntrySize()");
getZipStatsAndConsume((max_size, min_ratio) -> {
// check max entry size ouf of bounds // check max entry size ouf of bounds
ZipSecureFile.setMinInflateRatio(min_ratio-0.002); ZipSecureFile.setMinInflateRatio(min_ratio-0.002);
ZipSecureFile.setMaxEntrySize(max_size-1); ZipSecureFile.setMaxEntrySize(max_size-100);
try { });
WorkbookFactory.create(file, null, true).close();
} catch (InvalidFormatException | POIXMLException e) {
checkForZipBombException(e);
} }
private void getZipStatsAndConsume(BiConsumer<Long,Double> ratioCon) throws IOException, InvalidFormatException {
// use a test file with a xml file bigger than 100k (ZipArchiveThresholdInputStream.GRACE_ENTRY_SIZE)
final File file = XSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx");
double min_ratio = Double.MAX_VALUE;
long max_size = 0;
try (ZipFile zf = ZipHelper.openZipFile(file)) {
assertNotNull(zf);
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement();
if (ze.getSize() == 0) {
continue;
}
// add zip entry header ~ 30 bytes
long size = ze.getSize()+30;
double ratio = ze.getCompressedSize() / (double)size;
min_ratio = Math.min(min_ratio, ratio);
max_size = Math.max(max_size, size);
}
}
ratioCon.accept(max_size, min_ratio);
//noinspection EmptyTryBlock,unused
try (Workbook wb = WorkbookFactory.create(file, null, true)) {
} finally { } finally {
// reset otherwise a lot of ooxml tests will fail // reset otherwise a lot of ooxml tests will fail
ZipSecureFile.setMinInflateRatio(0.01d); ZipSecureFile.setMinInflateRatio(0.01d);
@ -896,28 +942,6 @@ public final class TestPackage {
} }
} }
private void checkForZipBombException(Throwable e) {
// unwrap InvocationTargetException as they usually contain the nested exception in the "target" member
if(e instanceof InvocationTargetException) {
e = ((InvocationTargetException)e).getTargetException();
}
String msg = e.getMessage();
if(msg != null && (msg.startsWith("Zip bomb detected!") ||
msg.contains("The parser has encountered more than \"4,096\" entity expansions in this document;") ||
msg.contains("The parser has encountered more than \"4096\" entity expansions in this document;"))) {
return;
}
// recursively check the causes for the message as it can be nested further down in the exception-tree
if(e.getCause() != null && e.getCause() != e) {
checkForZipBombException(e.getCause());
return;
}
throw new IllegalStateException("Expected to catch an Exception because of a detected Zip Bomb, but did not find the related error message in the exception", e);
}
@Test @Test
public void testConstructors() throws IOException { public void testConstructors() throws IOException {
// verify the various ways to construct a ZipSecureFile // verify the various ways to construct a ZipSecureFile
@ -926,10 +950,6 @@ public final class TestPackage {
assertNotNull(zipFile.getName()); assertNotNull(zipFile.getName());
zipFile.close(); zipFile.close();
zipFile = new ZipSecureFile(file, ZipFile.OPEN_READ);
assertNotNull(zipFile.getName());
zipFile.close();
zipFile = new ZipSecureFile(file.getAbsolutePath()); zipFile = new ZipSecureFile(file.getAbsolutePath());
assertNotNull(zipFile.getName()); assertNotNull(zipFile.getName());
zipFile.close(); zipFile.close();
@ -948,7 +968,7 @@ public final class TestPackage {
// bug 60128 // bug 60128
@Test(expected=NotOfficeXmlFileException.class) @Test(expected=NotOfficeXmlFileException.class)
public void testCorruptFile() throws IOException, InvalidFormatException { public void testCorruptFile() throws InvalidFormatException {
File file = OpenXML4JTestDataSamples.getSampleFile("invalid.xlsx"); File file = OpenXML4JTestDataSamples.getSampleFile("invalid.xlsx");
OPCPackage.open(file, PackageAccess.READ); OPCPackage.open(file, PackageAccess.READ);
} }
@ -976,4 +996,148 @@ public final class TestPackage {
} }
} }
} }
@Test
public void testBug56479() throws Exception {
InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
OPCPackage p = OPCPackage.open(is);
// Check we found the contents of it
boolean foundCoreProps = false, foundDocument = false, foundTheme1 = false;
for (final PackagePart part : p.getParts()) {
final String partName = part.getPartName().toString();
final String contentType = part.getContentType();
if ("/docProps/core.xml".equals(partName)) {
assertEquals(ContentTypes.CORE_PROPERTIES_PART, contentType);
foundCoreProps = true;
}
if ("/word/document.xml".equals(partName)) {
assertEquals(XWPFRelation.DOCUMENT.getContentType(), contentType);
foundDocument = true;
}
if ("/word/theme/theme1.xml".equals(partName)) {
assertEquals(XWPFRelation.THEME.getContentType(), contentType);
foundTheme1 = true;
}
}
assertTrue("Core not found in " + p.getParts(), foundCoreProps);
assertFalse("Document should not be found in " + p.getParts(), foundDocument);
assertFalse("Theme1 should not found in " + p.getParts(), foundTheme1);
p.close();
is.close();
}
@Test
public void unparseableCentralDirectory() throws IOException {
File f = OpenXML4JTestDataSamples.getSampleFile("at.pzp.www_uploads_media_PP_Scheinecker-jdk6error.pptx");
SlideShow<?,?> ppt = SlideShowFactory.create(f, null, true);
ppt.close();
}
@Test
public void testClosingStreamOnException() throws IOException {
InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.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);
for (int i = 0; i < 100; i++) {
os.write(is.read());
}
os.flush();
os.close();
is.close();
// feed the corrupted zip file to OPCPackage
try {
OPCPackage.open(tmp, PackageAccess.READ);
} 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());
}
/**
* If ZipPackage is passed an invalid file, a call to close
* (eg from the OPCPackage open method) should tidy up the
* stream / file the broken file is being read from.
* See bug #60128 for more
*/
@Test(expected = NotOfficeXmlFileException.class)
public void testTidyStreamOnInvalidFile1() throws Exception {
openInvalidFile("SampleSS.ods", false);
}
@Test(expected = NotOfficeXmlFileException.class)
public void testTidyStreamOnInvalidFile2() throws Exception {
openInvalidFile("SampleSS.ods", true);
}
@Test(expected = NotOfficeXmlFileException.class)
public void testTidyStreamOnInvalidFile3() throws Exception {
openInvalidFile("SampleSS.txt", false);
}
@Test(expected = NotOfficeXmlFileException.class)
public void testTidyStreamOnInvalidFile4() throws Exception {
openInvalidFile("SampleSS.txt", true);
}
private static void openInvalidFile(final String name, final boolean useStream) throws IOException, InvalidFormatException {
// Spreadsheet has a good mix of alternate file types
final POIDataSamples files = POIDataSamples.getSpreadSheetInstance();
ZipPackage pkgTest = null;
try (final InputStream is = (useStream) ? files.openResourceAsStream(name) : null) {
try (final ZipPackage pkg = (useStream) ? new ZipPackage(is, PackageAccess.READ) : new ZipPackage(files.getFile(name), PackageAccess.READ)) {
pkgTest = pkg;
assertNotNull(pkg.getZipArchive());
// assertFalse(pkg.getZipArchive().isClosed());
pkg.getParts();
fail("Shouldn't work");
}
} finally {
if (pkgTest != null) {
assertNotNull(pkgTest.getZipArchive());
assertTrue(pkgTest.getZipArchive().isClosed());
}
}
}
@SuppressWarnings("SameParameterValue")
private static <T extends Throwable> AnyCauseMatcher<T> getCauseMatcher(Class<T> cause, String message) {
// junit is only using hamcrest-core, so instead of adding hamcrest-beans, we provide the throwable
// search with the basics...
// see https://stackoverflow.com/a/47703937/2066598
return new AnyCauseMatcher<>(cause, message);
}
private static class AnyCauseMatcher<T extends Throwable> extends TypeSafeMatcher<T> {
private final Class<T> expectedType;
private final String expectedMessage;
AnyCauseMatcher(Class<T> expectedType, String expectedMessage) {
this.expectedType = expectedType;
this.expectedMessage = expectedMessage;
}
@Override
protected boolean matchesSafely(final Throwable root) {
for (Throwable t = root; t != null; t = t.getCause()) {
if (t.getClass().isAssignableFrom(expectedType) && t.getMessage().contains(expectedMessage)) {
return true;
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("expects type ")
.appendValue(expectedType)
.appendText(" and a message ")
.appendValue(expectedMessage);
}
}
} }

View File

@ -1,246 +0,0 @@
/* ====================================================================
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.poi.openxml4j.opc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import org.apache.poi.POIDataSamples;
import org.apache.poi.POITextExtractor;
import org.apache.poi.POIXMLException;
import org.apache.poi.extractor.ExtractorFactory;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.openxml4j.OpenXML4JTestDataSamples;
import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
import org.apache.poi.sl.usermodel.SlideShow;
import org.apache.poi.sl.usermodel.SlideShowFactory;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.xmlbeans.XmlException;
import org.junit.Test;
public class TestZipPackage {
@Test
public void testBug56479() throws Exception {
InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.zip");
OPCPackage p = OPCPackage.open(is);
// Check we found the contents of it
boolean foundCoreProps = false, foundDocument = false, foundTheme1 = false;
for (final PackagePart part : p.getParts()) {
final String partName = part.getPartName().toString();
final String contentType = part.getContentType();
if ("/docProps/core.xml".equals(partName)) {
assertEquals(ContentTypes.CORE_PROPERTIES_PART, contentType);
foundCoreProps = true;
}
if ("/word/document.xml".equals(partName)) {
assertEquals(XWPFRelation.DOCUMENT.getContentType(), contentType);
foundDocument = true;
}
if ("/word/theme/theme1.xml".equals(partName)) {
assertEquals(XWPFRelation.THEME.getContentType(), contentType);
foundTheme1 = true;
}
}
assertTrue("Core not found in " + p.getParts(), foundCoreProps);
assertFalse("Document should not be found in " + p.getParts(), foundDocument);
assertFalse("Theme1 should not found in " + p.getParts(), foundTheme1);
p.close();
is.close();
}
@Test
public void testZipEntityExpansionTerminates() throws IOException {
try {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("poc-xmlbomb.xlsx");
wb.close();
fail("Should catch exception due to entity expansion limitations");
} catch (POIXMLException e) {
assertEntityLimitReached(e);
}
}
private void assertEntityLimitReached(Exception e) throws UnsupportedEncodingException {
ByteArrayOutputStream str = new ByteArrayOutputStream();
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(str, "UTF-8"))) {
e.printStackTrace(writer);
}
String string = new String(str.toByteArray(), "UTF-8");
assertTrue("Had: " + string, string.contains("The parser has encountered more than"));
}
@Test
public void testZipEntityExpansionExceedsMemory() throws Exception {
try {
Workbook wb = WorkbookFactory.create(XSSFTestDataSamples.openSamplePackage("poc-xmlbomb.xlsx"));
wb.close();
fail("Should catch exception due to entity expansion limitations");
} catch (POIXMLException e) {
assertEntityLimitReached(e);
}
try {
try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-xmlbomb.xlsx"))) {
assertNotNull(extractor);
try {
extractor.getText();
} catch (IllegalStateException e) {
// expected due to shared strings expansion
}
}
} catch (POIXMLException e) {
assertEntityLimitReached(e);
}
}
@Test
public void testZipEntityExpansionSharedStringTable() throws Exception {
Workbook wb = WorkbookFactory.create(XSSFTestDataSamples.openSamplePackage("poc-shared-strings.xlsx"));
wb.close();
try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx"))) {
assertNotNull(extractor);
try {
extractor.getText();
} catch (IllegalStateException e) {
// expected due to shared strings expansion
}
}
}
@Test
public void testZipEntityExpansionSharedStringTableEvents() throws Exception {
boolean before = ExtractorFactory.getThreadPrefersEventExtractors();
ExtractorFactory.setThreadPrefersEventExtractors(true);
try {
try (POITextExtractor extractor = ExtractorFactory.createExtractor(HSSFTestDataSamples.getSampleFile("poc-shared-strings.xlsx"))) {
assertNotNull(extractor);
try {
extractor.getText();
} catch (IllegalStateException e) {
// expected due to shared strings expansion
}
}
} catch (XmlException e) {
assertEntityLimitReached(e);
} finally {
ExtractorFactory.setThreadPrefersEventExtractors(before);
}
}
@Test
public void unparseableCentralDirectory() throws IOException {
File f = OpenXML4JTestDataSamples.getSampleFile("at.pzp.www_uploads_media_PP_Scheinecker-jdk6error.pptx");
SlideShow<?,?> ppt = SlideShowFactory.create(f, null, true);
ppt.close();
}
@Test
public void testClosingStreamOnException() throws IOException {
InputStream is = OpenXML4JTestDataSamples.openSampleStream("dcterms_bug_56479.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);
for (int i = 0; i < 100; i++) {
os.write(is.read());
}
os.flush();
os.close();
is.close();
// feed the corrupted zip file to OPCPackage
try {
OPCPackage.open(tmp, PackageAccess.READ);
} 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());
}
/**
* If ZipPackage is passed an invalid file, a call to close
* (eg from the OPCPackage open method) should tidy up the
* stream / file the broken file is being read from.
* See bug #60128 for more
*/
@Test
public void testTidyStreamOnInvalidFile() throws Exception {
// Spreadsheet has a good mix of alternate file types
POIDataSamples files = POIDataSamples.getSpreadSheetInstance();
File[] notValidF = new File[] {
files.getFile("SampleSS.ods"), files.getFile("SampleSS.txt")
};
InputStream[] notValidS = new InputStream[] {
files.openResourceAsStream("SampleSS.ods"), files.openResourceAsStream("SampleSS.txt")
};
for (File notValid : notValidF) {
ZipPackage pkg = new ZipPackage(notValid, PackageAccess.READ);
assertNotNull(pkg.getZipArchive());
assertFalse(pkg.getZipArchive().isClosed());
try {
pkg.getParts();
fail("Shouldn't work");
} catch (NotOfficeXmlFileException e) {
// expected here
}
pkg.close();
assertNotNull(pkg.getZipArchive());
assertTrue(pkg.getZipArchive().isClosed());
}
for (InputStream notValid : notValidS) {
ZipPackage pkg = new ZipPackage(notValid, PackageAccess.READ);
assertNotNull(pkg.getZipArchive());
assertFalse(pkg.getZipArchive().isClosed());
try {
pkg.getParts();
fail("Shouldn't work");
} catch (NotOfficeXmlFileException e) {
// expected here
}
pkg.close();
assertNotNull(pkg.getZipArchive());
assertTrue(pkg.getZipArchive().isClosed());
}
}
}

View File

@ -18,6 +18,7 @@
package org.apache.poi.openxml4j.opc; package org.apache.poi.openxml4j.opc;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -31,20 +32,26 @@ import java.util.TreeMap;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import org.junit.Assert;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
import org.apache.poi.util.IOUtils;
import org.junit.Assert;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.builder.Input;
import org.xmlunit.diff.Comparison;
import org.xmlunit.diff.ComparisonResult;
import org.xmlunit.diff.DefaultNodeMatcher;
import org.xmlunit.diff.Diff;
import org.xmlunit.diff.DifferenceEvaluator;
import org.xmlunit.diff.ElementSelectors;
/** /**
* Compare the contents of 2 zip files. * Compare the contents of 2 zip files.
*/ */
public class ZipFileAssert { public final class ZipFileAssert {
private ZipFileAssert() { private ZipFileAssert() {
} }
static final int BUFFER_SIZE = 2048; private static void equals(
protected static void equals(
TreeMap<String, ByteArrayOutputStream> file1, TreeMap<String, ByteArrayOutputStream> file1,
TreeMap<String, ByteArrayOutputStream> file2) { TreeMap<String, ByteArrayOutputStream> file2) {
Set<String> listFile1 = file1.keySet(); Set<String> listFile1 = file1.keySet();
@ -52,32 +59,37 @@ public class ZipFileAssert {
for (String fileName : listFile1) { for (String fileName : listFile1) {
// extract the contents for both // extract the contents for both
ByteArrayOutputStream contain2 = file2.get(fileName);
ByteArrayOutputStream contain1 = file1.get(fileName); ByteArrayOutputStream contain1 = file1.get(fileName);
ByteArrayOutputStream contain2 = file2.get(fileName);
assertNotNull(fileName + " not found in 2nd zip", contain2); assertNotNull(fileName + " not found in 2nd zip", contain2);
// no need to check for contain1. The key come from it // no need to check for contain1. The key come from it
if ((fileName.endsWith(".xml")) || fileName.endsWith(".rels")) { if (fileName.matches(".*\\.(xml|rels)$")) {
// we have a xml file // we have a xml file
// TODO final Diff diff = DiffBuilder.
// YK: the original OpenXML4J version attempted to compare xml using xmlunit (http://xmlunit.sourceforge.net), compare(Input.fromByteArray(contain1.toByteArray())).
// but POI does not depend on this library withTest(Input.fromByteArray(contain2.toByteArray())).
ignoreWhitespace().
checkForSimilar().
withDifferenceEvaluator(new IgnoreXMLDeclEvaluator()).
withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes, ElementSelectors.byNameAndText)).
build();
assertFalse(fileName+": "+diff.toString(), diff.hasDifferences());
} else { } else {
// not xml, may be an image or other binary format // not xml, may be an image or other binary format
Assert.assertEquals(fileName + " does not have the same size in both zip:", contain2.size(), contain1.size()); Assert.assertEquals(fileName + " does not have the same size in both zip:", contain1.size(), contain2.size());
assertArrayEquals("contents differ", contain1.toByteArray(), contain2.toByteArray()); assertArrayEquals("contents differ", contain1.toByteArray(), contain2.toByteArray());
} }
} }
} }
protected static TreeMap<String, ByteArrayOutputStream> decompress( private static TreeMap<String, ByteArrayOutputStream> decompress(
File filename) throws IOException { File filename) throws IOException {
// store the zip content in memory // store the zip content in memory
// let s assume it is not Go ;-) // let s assume it is not Go ;-)
TreeMap<String, ByteArrayOutputStream> zipContent = new TreeMap<>(); TreeMap<String, ByteArrayOutputStream> zipContent = new TreeMap<>();
byte data[] = new byte[BUFFER_SIZE];
/* Open file to decompress */ /* Open file to decompress */
FileInputStream file_decompress = new FileInputStream(filename); FileInputStream file_decompress = new FileInputStream(filename);
@ -89,20 +101,12 @@ public class ZipFileAssert {
/* Processing entries of the zip file */ /* Processing entries of the zip file */
ZipEntry entree; ZipEntry entree;
int count;
while ((entree = zis.getNextEntry()) != null) { while ((entree = zis.getNextEntry()) != null) {
/* Create a array for the current entry */ /* Create a array for the current entry */
ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
IOUtils.copy(zis, byteArray);
zipContent.put(entree.getName(), byteArray); zipContent.put(entree.getName(), byteArray);
/* copy in memory */
while ((count = zis.read(data, 0, BUFFER_SIZE)) != -1) {
byteArray.write(data, 0, count);
}
/* Flush the buffer */
byteArray.flush();
byteArray.close();
} }
zis.close(); zis.close();
@ -136,4 +140,29 @@ public class ZipFileAssert {
throw new AssertionFailedError(e.toString()); throw new AssertionFailedError(e.toString());
} }
} }
private static class IgnoreXMLDeclEvaluator implements DifferenceEvaluator {
public ComparisonResult evaluate(final Comparison comparison, final ComparisonResult outcome) {
if (outcome != ComparisonResult.EQUAL) {
// only evaluate differences
switch (comparison.getType()) {
case CHILD_NODELIST_SEQUENCE:
case XML_STANDALONE:
case NAMESPACE_PREFIX:
return ComparisonResult.SIMILAR;
case TEXT_VALUE:
switch (comparison.getControlDetails().getTarget().getParentNode().getNodeName()) {
case "dcterms:created":
case "dc:creator":
return ComparisonResult.SIMILAR;
}
break;
default:
break;
}
}
return outcome;
}
}
} }

View File

@ -17,11 +17,8 @@
package org.apache.poi.openxml4j.opc.internal.marshallers; package org.apache.poi.openxml4j.opc.internal.marshallers;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import static org.apache.poi.openxml4j.opc.PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_URI;
import org.apache.poi.openxml4j.opc.PackagingURIHelper; import static org.junit.Assert.assertTrue;
import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -29,8 +26,11 @@ import java.io.OutputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import static org.apache.poi.openxml4j.opc.PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_URI; import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import static org.junit.Assert.assertTrue; import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
import org.junit.Test;
public class TestZipPackagePropertiesMarshaller { public class TestZipPackagePropertiesMarshaller {
private PartMarshaller marshaller = new ZipPackagePropertiesMarshaller(); private PartMarshaller marshaller = new ZipPackagePropertiesMarshaller();
@ -58,7 +58,7 @@ public class TestZipPackagePropertiesMarshaller {
marshaller.marshall(new PackagePropertiesPart(null, PackagingURIHelper.createPartName(PACKAGE_RELATIONSHIPS_ROOT_URI)), marshaller.marshall(new PackagePropertiesPart(null, PackagingURIHelper.createPartName(PACKAGE_RELATIONSHIPS_ROOT_URI)),
new ZipOutputStream(new ByteArrayOutputStream()) { new ZipOutputStream(new ByteArrayOutputStream()) {
@Override @Override
public void putNextEntry(ZipEntry e) throws IOException { public void putNextEntry(final ZipEntry archiveEntry) throws IOException {
throw new IOException("TestException"); throw new IOException("TestException");
} }
}); });

View File

@ -17,7 +17,9 @@
package org.apache.poi.poifs.crypt; package org.apache.poi.poifs.crypt;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -26,6 +28,7 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.stream.IntStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -146,21 +149,27 @@ public class TestDecryptor {
// the test file contains a wrong ole entry size, produced by extenxls // the test file contains a wrong ole entry size, produced by extenxls
// the fix limits the available size and tries to read all entries // the fix limits the available size and tries to read all entries
File f = POIDataSamples.getPOIFSInstance().getFile("extenxls_pwd123.xlsx"); File f = POIDataSamples.getPOIFSInstance().getFile("extenxls_pwd123.xlsx");
NPOIFSFileSystem fs = new NPOIFSFileSystem(f, true);
try (NPOIFSFileSystem fs = new NPOIFSFileSystem(f, true)) {
EncryptionInfo info = new EncryptionInfo(fs); EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info); Decryptor d = Decryptor.getInstance(info);
d.verifyPassword("pwd123"); d.verifyPassword("pwd123");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ZipInputStream zis = new ZipInputStream(d.getDataStream(fs));
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
bos.reset();
IOUtils.copy(zis, bos);
assertEquals(ze.getSize(), bos.size());
}
zis.close(); final ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);
fs.close(); try (final ZipInputStream zis = new ZipInputStream(d.getDataStream(fs))) {
IntStream.of(3711, 1155, 445, 9376, 450, 588, 1337, 2593, 304, 7910).forEach(size -> {
try {
final ZipEntry ze = zis.getNextEntry();
assertNotNull(ze);
IOUtils.copy(zis, bos);
assertEquals(size, bos.size());
bos.reset();
} catch (IOException e) {
fail(e.getMessage());
}
});
}
}
} }
@Test @Test

View File

@ -24,6 +24,7 @@ import static org.apache.poi.POITestCase.assertEndsWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -245,7 +246,7 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
SXSSFWorkbook wb = new SXSSFWorkbook(); SXSSFWorkbook wb = new SXSSFWorkbook();
SXSSFSheet sh = wb.createSheet(); SXSSFSheet sh = wb.createSheet();
SheetDataWriter wr = sh.getSheetDataWriter(); SheetDataWriter wr = sh.getSheetDataWriter();
assertTrue(wr.getClass() == SheetDataWriter.class); assertSame(wr.getClass(), SheetDataWriter.class);
File tmp = wr.getTempFile(); File tmp = wr.getTempFile();
assertStartsWith(tmp.getName(), "poi-sxssf-sheet"); assertStartsWith(tmp.getName(), "poi-sxssf-sheet");
assertEndsWith(tmp.getName(), ".xml"); assertEndsWith(tmp.getName(), ".xml");
@ -256,7 +257,7 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
wb.setCompressTempFiles(true); wb.setCompressTempFiles(true);
sh = wb.createSheet(); sh = wb.createSheet();
wr = sh.getSheetDataWriter(); wr = sh.getSheetDataWriter();
assertTrue(wr.getClass() == GZIPSheetDataWriter.class); assertSame(wr.getClass(), GZIPSheetDataWriter.class);
tmp = wr.getTempFile(); tmp = wr.getTempFile();
assertStartsWith(tmp.getName(), "poi-sxssf-sheet-xml"); assertStartsWith(tmp.getName(), "poi-sxssf-sheet-xml");
assertEndsWith(tmp.getName(), ".gz"); assertEndsWith(tmp.getName(), ".gz");
@ -279,22 +280,10 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
public void gzipSheetdataWriter() throws IOException { public void gzipSheetdataWriter() throws IOException {
SXSSFWorkbook wb = new SXSSFWorkbook(); SXSSFWorkbook wb = new SXSSFWorkbook();
wb.setCompressTempFiles(true); wb.setCompressTempFiles(true);
int rowNum = 1000;
int sheetNum = 5;
for(int i = 0; i < sheetNum; i++){
Sheet sh = wb.createSheet("sheet" + i);
for(int j = 0; j < rowNum; j++){
Row row = sh.createRow(j);
Cell cell1 = row.createCell(0);
cell1.setCellValue(new CellReference(cell1).formatAsString());
Cell cell2 = row.createCell(1); final int rowNum = 1000;
cell2.setCellValue(i); final int sheetNum = 5;
populateData(wb, 1000, 5);
Cell cell3 = row.createCell(2);
cell3.setCellValue(j);
}
}
XSSFWorkbook xwb = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb); XSSFWorkbook xwb = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
for(int i = 0; i < sheetNum; i++){ for(int i = 0; i < sheetNum; i++){
@ -319,10 +308,24 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
wb.close(); wb.close();
} }
protected static void assertWorkbookDispose(SXSSFWorkbook wb) private static void assertWorkbookDispose(SXSSFWorkbook wb)
{ {
int rowNum = 1000; populateData(wb, 1000, 5);
int sheetNum = 5;
for (Sheet sheet : wb) {
SXSSFSheet sxSheet = (SXSSFSheet) sheet;
assertTrue(sxSheet.getSheetDataWriter().getTempFile().exists());
}
assertTrue(wb.dispose());
for (Sheet sheet : wb) {
SXSSFSheet sxSheet = (SXSSFSheet) sheet;
assertFalse(sxSheet.getSheetDataWriter().getTempFile().exists());
}
}
private static void populateData(Workbook wb, final int rowNum, final int sheetNum) {
for(int i = 0; i < sheetNum; i++){ for(int i = 0; i < sheetNum; i++){
Sheet sh = wb.createSheet("sheet" + i); Sheet sh = wb.createSheet("sheet" + i);
for(int j = 0; j < rowNum; j++){ for(int j = 0; j < rowNum; j++){
@ -337,18 +340,6 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
cell3.setCellValue(j); cell3.setCellValue(j);
} }
} }
for (Sheet sheet : wb) {
SXSSFSheet sxSheet = (SXSSFSheet) sheet;
assertTrue(sxSheet.getSheetDataWriter().getTempFile().exists());
}
assertTrue(wb.dispose());
for (Sheet sheet : wb) {
SXSSFSheet sxSheet = (SXSSFSheet) sheet;
assertFalse(sxSheet.getSheetDataWriter().getTempFile().exists());
}
} }
@Test @Test
@ -440,42 +431,10 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
} }
} }
@Ignore("Just a local test for http://stackoverflow.com/questions/33627329/apache-poi-streaming-api-using-xssf-template")
@Test @Test
public void testTemplateFile() throws IOException { public void closeDoesNotModifyWorkbook() throws IOException {
XSSFWorkbook workBook = XSSFTestDataSamples.openSampleWorkbook("sample.xlsx");
SXSSFWorkbook streamingWorkBook = new SXSSFWorkbook(workBook,10);
Sheet sheet = streamingWorkBook.getSheet("Sheet1");
for(int rowNum = 10;rowNum < 1000000;rowNum++) {
Row row = sheet.createRow(rowNum);
for(int cellNum = 0;cellNum < 700;cellNum++) {
Cell cell = row.createCell(cellNum);
cell.setCellValue("somevalue");
}
if(rowNum % 100 == 0) {
System.out.print(".");
if(rowNum % 10000 == 0) {
System.out.println(rowNum);
}
}
}
FileOutputStream fos = new FileOutputStream("C:\\temp\\streaming.xlsx");
streamingWorkBook.write(fos);
fos.close();
streamingWorkBook.close();
workBook.close();
}
@Test
public void closeDoesNotModifyWorkbook() throws IOException, InvalidFormatException {
final String filename = "SampleSS.xlsx"; final String filename = "SampleSS.xlsx";
final File file = POIDataSamples.getSpreadSheetInstance().getFile(filename); final File file = POIDataSamples.getSpreadSheetInstance().getFile(filename);
SXSSFWorkbook wb = null;
XSSFWorkbook xwb = null;
// Some tests commented out because close() modifies the file // Some tests commented out because close() modifies the file
// See bug 58779 // See bug 58779
@ -489,19 +448,11 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
//assertCloseDoesNotModifyFile(filename, wb); //assertCloseDoesNotModifyFile(filename, wb);
// InputStream // InputStream
FileInputStream fis = new FileInputStream(file);
try { try (FileInputStream fis = new FileInputStream(file);
xwb = new XSSFWorkbook(fis); XSSFWorkbook xwb = new XSSFWorkbook(fis);
wb = new SXSSFWorkbook(xwb); SXSSFWorkbook wb = new SXSSFWorkbook(xwb)) {
assertCloseDoesNotModifyFile(filename, wb); assertCloseDoesNotModifyFile(filename, wb);
} finally {
if (xwb != null) {
xwb.close();
}
if (wb != null) {
wb.close();
}
fis.close();
} }
// OPCPackage // OPCPackage
@ -531,7 +482,6 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
System.arraycopy(prefix, 0, useless, 0, prefix.length); System.arraycopy(prefix, 0, useless, 0, prefix.length);
String ul = new String(useless); String ul = new String(useless);
r.createCell(col, CellType.STRING).setCellValue(ul); r.createCell(col, CellType.STRING).setCellValue(ul);
ul = null;
} }
} }
@ -573,12 +523,12 @@ public final class TestSXSSFWorkbook extends BaseTestXWorkbook {
xssf = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray())); xssf = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()));
s = xssf.getSheet(sheetName); s = xssf.getSheet(sheetName);
assertEquals(10, s.getLastRowNum()); assertEquals(10, s.getLastRowNum());
assertEquals(true, s.getRow(0).getCell(0).getBooleanCellValue()); assertTrue(s.getRow(0).getCell(0).getBooleanCellValue());
assertEquals("Test Row 9", s.getRow(9).getCell(2).getStringCellValue()); assertEquals("Test Row 9", s.getRow(9).getCell(2).getStringCellValue());
} }
@Test @Test
public void test56557() throws IOException, InvalidFormatException { public void test56557() throws IOException {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("56557.xlsx"); Workbook wb = XSSFTestDataSamples.openSampleWorkbook("56557.xlsx");
// Using streaming XSSFWorkbook makes the output file invalid // Using streaming XSSFWorkbook makes the output file invalid