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:
parent
6dc8c3034e
commit
2d6380833a
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user