123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- /* ====================================================================
- 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.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.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.Collections;
- import java.util.List;
- import java.util.stream.Collectors;
-
- import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
- import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
- import org.apache.commons.compress.archivers.zip.ZipFile;
- import org.apache.poi.UnsupportedFileFormatException;
- import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
- import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
- import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
- import org.apache.poi.openxml4j.exceptions.ODFNotOfficeXmlFileException;
- import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
- import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException;
- import org.apache.poi.openxml4j.opc.internal.ContentTypeManager;
- import org.apache.poi.openxml4j.opc.internal.FileHelper;
- import org.apache.poi.openxml4j.opc.internal.MemoryPackagePart;
- import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
- import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
- import org.apache.poi.openxml4j.opc.internal.ZipHelper;
- 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.ZipFileZipEntrySource;
- import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.POILogFactory;
- import org.apache.poi.util.POILogger;
- import org.apache.poi.util.TempFile;
-
- /**
- * Physical zip package.
- */
- public final class ZipPackage extends OPCPackage {
- private static final String MIMETYPE = "mimetype";
- private static final String SETTINGS_XML = "settings.xml";
-
- private static final POILogger LOG = POILogFactory.getLogger(ZipPackage.class);
-
- /**
- * Zip archive, as either a file on disk,
- * or a stream
- */
- private final ZipEntrySource zipArchive;
-
- /**
- * Constructor. Creates a new, empty ZipPackage.
- */
- public ZipPackage() {
- super(defaultPackageAccess);
- this.zipArchive = null;
-
- try {
- this.contentTypeManager = new ZipContentTypeManager(null, this);
- } catch (InvalidFormatException e) {
- LOG.log(POILogger.WARN,"Could not parse ZipPackage", e);
- }
- }
-
- /**
- * Constructor. Opens a Zip based Open XML document from
- * an InputStream.
- *
- * @param in
- * Zip input stream to load.
- * @param access
- * The package access mode.
- * @throws IllegalArgumentException
- * If the specified input stream is not an instance of
- * ZipInputStream.
- * @throws IOException
- * if input stream cannot be opened, read, or closed
- */
- ZipPackage(InputStream in, PackageAccess access) throws IOException {
- super(access);
- ZipArchiveThresholdInputStream zis = ZipHelper.openZipStream(in); // NOSONAR
- try {
- this.zipArchive = new ZipInputStreamZipEntrySource(zis);
- } catch (final IOException e) {
- IOUtils.closeQuietly(zis);
- throw e;
- }
- }
-
- /**
- * Constructor. Opens a Zip based Open XML document from a file.
- *
- * @param path
- * The path of the file to open or create.
- * @param access
- * The package access mode.
- * @throws InvalidOperationException If the zip file cannot be opened.
- */
- ZipPackage(String path, PackageAccess access) throws InvalidOperationException {
- this(new File(path), access);
- }
-
- /**
- * Constructor. Opens a Zip based Open XML document from a File.
- *
- * @param file
- * The file to open or create.
- * @param access
- * The package access mode.
- * @throws InvalidOperationException If the zip file cannot be opened.
- */
- ZipPackage(File file, PackageAccess access) throws InvalidOperationException {
- super(access);
-
- ZipEntrySource ze;
- try {
- final ZipFile zipFile = ZipHelper.openZipFile(file); // NOSONAR
- ze = new ZipFileZipEntrySource(zipFile);
- } catch (IOException e) {
- // probably not happening with write access - not sure how to handle the default read-write access ...
- if (access == PackageAccess.WRITE) {
- throw new InvalidOperationException("Can't open the specified file: '" + file + "'", e);
- }
- // YK: this is incorrect and the exception below is never thrown.
- // The could below should catch "archive is not a ZIP archive"
- 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)");
- ze = openZipEntrySourceStream(file);
- }
- this.zipArchive = ze;
- }
-
- private static ZipEntrySource openZipEntrySourceStream(File file) throws InvalidOperationException {
- final FileInputStream fis;
- // Acquire a resource that is needed to read the next level of openZipEntrySourceStream
- try {
- // open the file input stream
- fis = new FileInputStream(file); // NOSONAR
- } catch (final FileNotFoundException e) {
- // If the source cannot be acquired, abort (no resources to free at this level)
- throw new InvalidOperationException("Can't open the specified file input stream from file: '" + file + "'", e);
- }
-
- // If an error occurs while reading the next level of openZipEntrySourceStream, free the acquired resource
- try {
- // read from the file input stream
- return openZipEntrySourceStream(fis);
- } catch (final InvalidOperationException|UnsupportedFileFormatException e) {
- // abort: close the zip input stream
- IOUtils.closeQuietly(fis);
- throw e;
- } catch (final Exception e) {
- // abort: close the file input stream
- IOUtils.closeQuietly(fis);
- throw new InvalidOperationException("Failed to read the file input stream from file: '" + file + "'", e);
- }
- }
-
- private static ZipEntrySource openZipEntrySourceStream(FileInputStream fis) throws InvalidOperationException {
- final ZipArchiveThresholdInputStream zis;
- // Acquire a resource that is needed to read the next level of openZipEntrySourceStream
- try {
- // open the zip input stream
- zis = ZipHelper.openZipStream(fis); // NOSONAR
- } catch (final IOException e) {
- // If the source cannot be acquired, abort (no resources to free at this level)
- throw new InvalidOperationException("Could not open the file input stream", e);
- }
-
- // If an error occurs while reading the next level of openZipEntrySourceStream, free the acquired resource
- try {
- // read from the zip input stream
- return openZipEntrySourceStream(zis);
- } catch (final InvalidOperationException|UnsupportedFileFormatException e) {
- // abort: close the zip input stream
- IOUtils.closeQuietly(zis);
- throw e;
- } catch (final Exception e) {
- // abort: close the zip input stream
- IOUtils.closeQuietly(zis);
- throw new InvalidOperationException("Failed to read the zip entry source stream", e);
- }
- }
-
- 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
- try {
- // open the zip entry source stream
- return new ZipInputStreamZipEntrySource(zis);
- } catch (IOException e) {
- throw new InvalidOperationException("Could not open the specified zip entry source stream", e);
- }
- }
-
- /**
- * Constructor. Opens a Zip based Open XML document from
- * a custom ZipEntrySource, typically an open archive
- * from another system
- *
- * @param zipEntry
- * Zip data to load.
- * @param access
- * The package access mode.
- */
- ZipPackage(ZipEntrySource zipEntry, PackageAccess access) {
- super(access);
- this.zipArchive = zipEntry;
- }
-
- /**
- * 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
- * archive and look for all parts contain inside it.
- *
- * @return All parts contain in this package.
- * @throws InvalidFormatException if the package is not valid.
- */
- @Override
- protected PackagePartCollection getPartsImpl() throws InvalidFormatException {
- final PackagePartCollection newPartList = new PackagePartCollection();
-
- if (zipArchive == null) {
- return newPartList;
- }
-
- // First we need to parse the content type part
- final ZipArchiveEntry contentTypeEntry =
- zipArchive.getEntry(CONTENT_TYPES_PART_NAME);
- if (contentTypeEntry != null) {
- if (this.contentTypeManager != null) {
- throw new InvalidFormatException("ContentTypeManager can only be created once. This must be a cyclic relation?");
- }
- try {
- this.contentTypeManager = new ZipContentTypeManager(
- zipArchive.getInputStream(contentTypeEntry), this);
- } catch (IOException e) {
- throw new InvalidFormatException(e.getMessage(), e);
- }
- } else {
- // Is it a different Zip-based format?
- final boolean hasMimetype = zipArchive.getEntry(MIMETYPE) != null;
- final boolean hasSettingsXML = zipArchive.getEntry(SETTINGS_XML) != null;
- if (hasMimetype && hasSettingsXML) {
- throw new ODFNotOfficeXmlFileException(
- "The supplied data appears to be in ODF (Open Document) Format. " +
- "Formats like these (eg ODS, ODP) are not supported, try Apache ODFToolkit");
- }
- if (!zipArchive.getEntries().hasMoreElements()) {
- throw new NotOfficeXmlFileException(
- "No valid entries or contents found, this is not a valid OOXML " +
- "(Office Open XML) file");
- }
- // Fallback exception
- throw new InvalidFormatException(
- "Package should contain a content type part [M1.13]");
- }
-
- // Now create all the relationships
- // (Need to create relationships before other
- // parts, otherwise we might create a part before
- // its relationship exists, and then it won't tie up)
- final List<EntryTriple> entries =
- Collections.list(zipArchive.getEntries()).stream()
- .map(zae -> new EntryTriple(zae, contentTypeManager))
- .filter(mm -> mm.partName != null)
- .sorted()
- .collect(Collectors.toList());
-
- for (final EntryTriple et : entries) {
- et.register(newPartList);
- }
-
- return newPartList;
- }
-
- private class EntryTriple implements Comparable<EntryTriple> {
- final ZipArchiveEntry zipArchiveEntry;
- final PackagePartName partName;
- final String contentType;
-
- EntryTriple(final ZipArchiveEntry zipArchiveEntry, final ContentTypeManager contentTypeManager) {
- this.zipArchiveEntry = zipArchiveEntry;
-
- final String entryName = zipArchiveEntry.getName();
- PackagePartName ppn = null;
- try {
- // We get an error when we parse [Content_Types].xml
- // because it's not a valid URI.
- ppn = (CONTENT_TYPES_PART_NAME.equalsIgnoreCase(entryName)) ? null
- : PackagingURIHelper.createPartName(ZipHelper.getOPCNameFromZipItemName(entryName));
- } catch (Exception e) {
- // We assume we can continue, even in degraded mode ...
- LOG.log(POILogger.WARN,"Entry " + entryName + " is not valid, so this part won't be add to the package.", e);
- }
-
- 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);
- }
- }
-
- /**
- * Create a new MemoryPackagePart from the specified URI and content type
- *
- *
- * aram partName The part URI.
- *
- * @param contentType
- * The part content type.
- * @return The newly created zip package part, else <b>null</b>.
- */
- @Override
- protected PackagePart createPartImpl(PackagePartName partName,
- String contentType, boolean loadRelationships) {
- if (contentType == null) {
- throw new IllegalArgumentException("contentType");
- }
-
- if (partName == null) {
- throw new IllegalArgumentException("partName");
- }
-
- try {
- return new MemoryPackagePart(this, partName, contentType, loadRelationships);
- } catch (InvalidFormatException e) {
- LOG.log(POILogger.WARN, e);
- return null;
- }
- }
-
- /**
- * Delete a part from the package
- *
- * @throws IllegalArgumentException
- * Throws if the part URI is nulll or invalid.
- */
- @Override
- protected void removePartImpl(PackagePartName partName) {
- if (partName == null) {
- throw new IllegalArgumentException("partUri");
- }
- }
-
- /**
- * Flush the package. Do nothing.
- */
- @Override
- protected void flushImpl() {
- // Do nothing
- }
-
- /**
- * Close and save the package.
- *
- * @see #close()
- */
- @Override
- protected void closeImpl() throws IOException {
- // Flush the package
- flush();
-
- if (this.originalPackagePath == null || this.originalPackagePath.isEmpty()) {
- return;
- }
-
- // Save the content
- File targetFile = new File(this.originalPackagePath);
- if (!targetFile.exists()) {
- throw new InvalidOperationException(
- "Can't close a package not previously open with the open() method !");
- }
-
- // Case of a package previously open
- String tempFileName = generateTempFileName(FileHelper.getDirectory(targetFile));
- File tempFile = TempFile.createTempFile(tempFileName, ".tmp");
-
- // Save the final package to a temporary file
- boolean success = false;
- try {
- save(tempFile);
- success = true;
- } finally {
- // Close the current zip file, so we can overwrite it on all platforms
- IOUtils.closeQuietly(this.zipArchive);
- try {
- // Copy the new file over the old one if save() succeed
- if(success) {
- FileHelper.copyFile(tempFile, targetFile);
- }
- } finally {
- // Either the save operation succeed or not, we delete the temporary file
- if (!tempFile.delete()) {
- LOG.log(POILogger.WARN, "The temporary file: '"
- + targetFile.getAbsolutePath()
- + "' cannot be deleted ! Make sure that no other application use it.");
- }
- }
- }
- }
-
- /**
- * Create a unique identifier to be use as a temp file name.
- *
- * @return A unique identifier use to be use as a temp file name.
- */
- private synchronized String generateTempFileName(File directory) {
- File tmpFilename;
- do {
- tmpFilename = new File(directory.getAbsoluteFile() + File.separator
- + "OpenXML4J" + System.nanoTime());
- } while (tmpFilename.exists());
- return FileHelper.getFilename(tmpFilename.getAbsoluteFile());
- }
-
- /**
- * Close the package without saving the document. Discard all the changes
- * made to this package.
- */
- @Override
- protected void revertImpl() {
- try {
- if (this.zipArchive != null) {
- this.zipArchive.close();
- }
- } catch (IOException e) {
- // Do nothing, user dont have to know
- }
- }
-
- /**
- * Save this package into the specified stream
- *
- *
- * @param outputStream
- * The stream use to save this package.
- *
- * @see #save(OutputStream)
- */
- @Override
- public void saveImpl(OutputStream outputStream) {
- // Check that the document was open in write mode
- throwExceptionIfReadOnly();
-
- final ZipArchiveOutputStream zos = (outputStream instanceof ZipArchiveOutputStream)
- ? (ZipArchiveOutputStream) outputStream : new ZipArchiveOutputStream(outputStream);
-
- try {
- // If the core properties part does not exist in the part list,
- // we save it as well
- if (this.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES).size() == 0 &&
- this.getPartsByRelationshipType(PackageRelationshipTypes.CORE_PROPERTIES_ECMA376).size() == 0 ) {
- LOG.log(POILogger.DEBUG,"Save core properties part");
-
- // Ensure that core properties are added if missing
- getPackageProperties();
- // Add core properties to part list ...
- addPackagePart(this.packageProperties);
- // ... and to add its relationship ...
- this.relationships.addRelationship(this.packageProperties
- .getPartName().getURI(), TargetMode.INTERNAL,
- PackageRelationshipTypes.CORE_PROPERTIES, null);
- // ... and the content if it has not been added yet.
- if (!this.contentTypeManager
- .isContentTypeRegister(ContentTypes.CORE_PROPERTIES_PART)) {
- this.contentTypeManager.addContentType(
- this.packageProperties.getPartName(),
- ContentTypes.CORE_PROPERTIES_PART);
- }
- }
-
- // Save content type part.
- LOG.log(POILogger.DEBUG,"Save content types part");
- this.contentTypeManager.save(zos);
-
- // Save package relationships part.
- LOG.log(POILogger.DEBUG,"Save package relationships");
- ZipPartMarshaller.marshallRelationshipPart(this.getRelationships(),
- PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_PART_NAME,
- zos);
-
- // Save parts.
- for (PackagePart part : getParts()) {
- // If the part is a relationship part, we don't save it, it's
- // the source part that will do the job.
- if (part.isRelationshipPart()) {
- continue;
- }
-
- final PackagePartName ppn = part.getPartName();
- LOG.log(POILogger.DEBUG,"Save part '" + ZipHelper.getZipItemNameFromOPCName(ppn.getName()) + "'");
- final PartMarshaller marshaller = partMarshallers.get(part._contentType);
-
- final PartMarshaller pm = (marshaller != null) ? marshaller : defaultPartMarshaller;
- if (!pm.marshall(part, zos)) {
- String errMsg = "The part " + ppn.getURI() + " failed to be saved in the stream with marshaller ";
- throw new OpenXML4JException(errMsg + pm);
- }
- }
-
- zos.finish();
- } catch (OpenXML4JRuntimeException e) {
- // no need to wrap this type of Exception
- throw e;
- } catch (Exception e) {
- throw new OpenXML4JRuntimeException(
- "Fail to save: an error occurs while saving the package : "
- + e.getMessage(), e);
- }
- }
-
- /**
- * Get the zip archive
- *
- * @return The zip archive.
- */
- public ZipEntrySource getZipArchive() {
- return zipArchive;
- }
- }
|