| /* ==================================================================== |
| 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)); |
| } |
| } |