277 lines
9.8 KiB
Java
277 lines
9.8 KiB
Java
/* ====================================================================
|
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
|
contributor license agreements. See the NOTICE file distributed with
|
|
this work for additional information regarding copyright ownership.
|
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
|
(the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
==================================================================== */
|
|
|
|
package org.apache.poi.openxml4j.opc.internal;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.PushbackInputStream;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.util.Enumeration;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipFile;
|
|
import java.util.zip.ZipInputStream;
|
|
|
|
import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
|
|
import org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException;
|
|
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
|
|
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
|
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
|
|
import org.apache.poi.openxml4j.opc.ZipPackage;
|
|
import org.apache.poi.openxml4j.util.ZipSecureFile;
|
|
import org.apache.poi.openxml4j.util.ZipSecureFile.ThresholdInputStream;
|
|
import org.apache.poi.poifs.common.POIFSConstants;
|
|
import org.apache.poi.poifs.storage.HeaderBlockConstants;
|
|
import org.apache.poi.util.IOUtils;
|
|
import org.apache.poi.util.LittleEndian;
|
|
|
|
public final class ZipHelper {
|
|
/**
|
|
* Forward slash use to convert part name between OPC and zip item naming
|
|
* conventions.
|
|
*/
|
|
private final static String FORWARD_SLASH = "/";
|
|
|
|
/**
|
|
* Buffer to read data from file. Use big buffer to improve performaces. the
|
|
* InputStream class is reading only 8192 bytes per read call (default value
|
|
* set by sun)
|
|
*/
|
|
public static final int READ_WRITE_FILE_BUFFER_SIZE = 8192;
|
|
|
|
/**
|
|
* Prevent this class to be instancied.
|
|
*/
|
|
private ZipHelper() {
|
|
// Do nothing
|
|
}
|
|
|
|
/**
|
|
* Retrieve the zip entry of the core properties part.
|
|
*
|
|
* @throws OpenXML4JException
|
|
* Throws if internal error occurs.
|
|
*/
|
|
public static ZipEntry getCorePropertiesZipEntry(ZipPackage pkg) {
|
|
PackageRelationship corePropsRel = pkg.getRelationshipsByType(
|
|
PackageRelationshipTypes.CORE_PROPERTIES).getRelationship(0);
|
|
|
|
if (corePropsRel == null)
|
|
return null;
|
|
|
|
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
|
|
* the specified item name.
|
|
*
|
|
* @param zipItemName
|
|
* Zip item name to convert.
|
|
* @return An OPC compliant name.
|
|
*/
|
|
public static String getOPCNameFromZipItemName(String zipItemName) {
|
|
if (zipItemName == null)
|
|
throw new IllegalArgumentException("zipItemName");
|
|
if (zipItemName.startsWith(FORWARD_SLASH)) {
|
|
return zipItemName;
|
|
}
|
|
return FORWARD_SLASH + zipItemName;
|
|
}
|
|
|
|
/**
|
|
* Convert an OPC item name into a zip item name by removing any leading
|
|
* forward slash if it exist.
|
|
*
|
|
* @param opcItemName
|
|
* The OPC item name to convert.
|
|
* @return A zip item name without any leading slashes.
|
|
*/
|
|
public static String getZipItemNameFromOPCName(String opcItemName) {
|
|
if (opcItemName == null)
|
|
throw new IllegalArgumentException("opcItemName");
|
|
|
|
String retVal = opcItemName;
|
|
while (retVal.startsWith(FORWARD_SLASH))
|
|
retVal = retVal.substring(1);
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Convert an OPC item name into a zip URI by removing any leading forward
|
|
* slash if it exist.
|
|
*
|
|
* @param opcItemName
|
|
* The OPC item name to convert.
|
|
* @return A zip URI without any leading slashes.
|
|
*/
|
|
public static URI getZipURIFromOPCName(String opcItemName) {
|
|
if (opcItemName == null)
|
|
throw new IllegalArgumentException("opcItemName");
|
|
|
|
String retVal = opcItemName;
|
|
while (retVal.startsWith(FORWARD_SLASH))
|
|
retVal = retVal.substring(1);
|
|
try {
|
|
return new URI(retVal);
|
|
} catch (URISyntaxException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies that the given stream starts with a Zip structure.
|
|
*
|
|
* Warning - this will consume the first few bytes of the stream,
|
|
* you should push-back or reset the stream after use!
|
|
*/
|
|
public static void verifyZipHeader(InputStream stream)
|
|
throws NotOfficeXmlFileException, IOException {
|
|
// Grab the first 8 bytes
|
|
byte[] data = new byte[8];
|
|
IOUtils.readFully(stream, data);
|
|
|
|
// OLE2?
|
|
long signature = LittleEndian.getLong(data);
|
|
if (signature == HeaderBlockConstants._signature) {
|
|
throw new OLE2NotOfficeXmlFileException(
|
|
"The supplied data appears to be in the OLE2 Format. " +
|
|
"You are calling the part of POI that deals with OOXML "+
|
|
"(Office Open XML) Documents. You need to call a different " +
|
|
"part of POI to process this data (eg HSSF instead of XSSF)");
|
|
}
|
|
|
|
// Raw XML?
|
|
byte[] RAW_XML_FILE_HEADER = POIFSConstants.RAW_XML_FILE_HEADER;
|
|
if (data[0] == RAW_XML_FILE_HEADER[0] &&
|
|
data[1] == RAW_XML_FILE_HEADER[1] &&
|
|
data[2] == RAW_XML_FILE_HEADER[2] &&
|
|
data[3] == RAW_XML_FILE_HEADER[3] &&
|
|
data[4] == RAW_XML_FILE_HEADER[4]) {
|
|
throw new NotOfficeXmlFileException(
|
|
"The supplied data appears to be a raw XML file. " +
|
|
"Formats such as Office 2003 XML are not supported");
|
|
}
|
|
|
|
// Don't check for a Zip header, as to maintain backwards
|
|
// compatibility we need to let them seek over junk at the
|
|
// start before beginning processing.
|
|
|
|
// Put things back
|
|
if (stream instanceof PushbackInputStream) {
|
|
((PushbackInputStream)stream).unread(data);
|
|
} else if (stream.markSupported()) {
|
|
stream.reset();
|
|
} else if (stream instanceof FileInputStream) {
|
|
// File open check, about to be closed, nothing to do
|
|
} else {
|
|
// Oh dear... I hope you know what you're doing!
|
|
}
|
|
}
|
|
|
|
private static InputStream prepareToCheckHeader(InputStream stream) {
|
|
if (stream instanceof PushbackInputStream) {
|
|
return stream;
|
|
}
|
|
if (stream.markSupported()) {
|
|
stream.mark(8);
|
|
return stream;
|
|
}
|
|
return new PushbackInputStream(stream, 8);
|
|
}
|
|
|
|
/**
|
|
* Opens the specified stream as a secure zip
|
|
*
|
|
* @param stream
|
|
* The stream to open.
|
|
* @return The zip stream freshly open.
|
|
*/
|
|
@SuppressWarnings("resource")
|
|
public static ThresholdInputStream openZipStream(InputStream stream) throws IOException {
|
|
// Peek at the first few bytes to sanity check
|
|
InputStream checkedStream = prepareToCheckHeader(stream);
|
|
verifyZipHeader(checkedStream);
|
|
|
|
// Open as a proper zip stream
|
|
InputStream zis = new ZipInputStream(checkedStream);
|
|
return ZipSecureFile.addThreshold(zis);
|
|
}
|
|
|
|
/**
|
|
* Opens the specified file as a secure zip, or returns null if no
|
|
* such file exists
|
|
*
|
|
* @param file
|
|
* The file to open.
|
|
* @return The zip archive freshly open.
|
|
* @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
|
|
*/
|
|
public static ZipFile openZipFile(File file) throws IOException, NotOfficeXmlFileException {
|
|
if (!file.exists()) {
|
|
throw new FileNotFoundException("File does not exist");
|
|
}
|
|
if (file.isDirectory()) {
|
|
throw new IOException("File is a directory");
|
|
}
|
|
|
|
// Peek at the first few bytes to sanity check
|
|
FileInputStream input = new FileInputStream(file);
|
|
try {
|
|
verifyZipHeader(input);
|
|
} finally {
|
|
input.close();
|
|
}
|
|
|
|
// Open as a proper zip file
|
|
return new ZipSecureFile(file);
|
|
}
|
|
|
|
/**
|
|
* Retrieve and open as a secure zip file with the specified path.
|
|
*
|
|
* @param path
|
|
* The file path.
|
|
* @return The zip archive freshly open.
|
|
*/
|
|
public static ZipFile openZipFile(String path) throws IOException {
|
|
return openZipFile(new File(path));
|
|
}
|
|
}
|