ZipHelper.java

/**
 * **********************************************************************
 *
 * <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * <p>Use is subject to license terms.
 *
 * <p>Licensed 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. You can also obtain a copy of the License at
 * http://odftoolkit.org/docs/license.txt
 *
 * <p>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.
 *
 * <p>See the License for the specific language governing permissions and limitations under the
 * License.
 *
 * <p>**********************************************************************
 */
package org.odftoolkit.odfdom.pkg;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipException;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

class ZipHelper {

  private ZipFile mZipFile = null;
  private byte[] mZipBuffer = null;
  private OdfPackage mPackage = null;

  public ZipHelper(OdfPackage pkg, ZipFile zipFile) {
    mZipFile = zipFile;
    mZipBuffer = null;
    mPackage = pkg;
  }

  public ZipHelper(OdfPackage pkg, byte[] buffer) {
    mZipBuffer = buffer;
    mZipFile = null;
    mPackage = pkg;
  }

  public static ZipArchiveInputStream createZipInputStream(InputStream is) {
    return new ZipArchiveInputStream(is, StandardCharsets.UTF_8.toString(), true, true);
  }

  String entriesToMap(Map<String, ZipArchiveEntry> zipEntries) throws IOException, SAXException {
    String firstEntryName = null;
    if (mZipFile != null) {
      Enumeration<? extends ZipArchiveEntry> entries = mZipFile.getEntries();
      if (entries.hasMoreElements()) {
        ZipArchiveEntry zipEntry = entries.nextElement();
        if (zipEntry != null) {
          firstEntryName = zipEntry.getName();
          addZipEntry(zipEntry, zipEntries);
          while (entries.hasMoreElements()) {
            zipEntry = entries.nextElement();
            addZipEntry(zipEntry, zipEntries);
          }
        }
      }
    } else {
      ZipArchiveInputStream inputStream =
          createZipInputStream(new ByteArrayInputStream(mZipBuffer));
      ZipArchiveEntry zipEntry = null;
      try {
        zipEntry = inputStream.getNextZipEntry();
      } catch (ZipException e) {
        // Unit tests expect us to return an empty map in this case.
      }
      if (zipEntry != null) {
        firstEntryName = zipEntry.getName();
        addZipEntry(zipEntry, zipEntries);
        while (zipEntry != null) {
          addZipEntry(zipEntry, zipEntries);
          try {
            zipEntry = inputStream.getNextZipEntry();
          } catch (java.util.zip.ZipException e) {
            if (e.getMessage().contains("only DEFLATED entries can have EXT descriptor")) {
              Logger.getLogger(ZipHelper.class.getName())
                  .finer("ZIP seems to contain encoded parts!");
              throw e;
            }
            // JDK 6 -- the try/catch is workaround for a specific JDK 5 only problem
            if (!e.getMessage().contains("missing entry name")
                && !"1.5.0".equals(System.getProperty("Java.version"))) {
              Logger.getLogger(ZipHelper.class.getName()).finer("ZIP ENTRY not found");
              throw e;
            }
            // ToDo: Error: "only DEFLATED entries can have EXT descriptor"
            // ZipInputStream does not expect (and does not know how to handle) an EXT descriptor
            // when the associated data was not DEFLATED (i.e. was stored uncompressed, as-is).
          }
        }
      }
      inputStream.close();
    }
    return firstEntryName;
  }

  private void addZipEntry(ZipArchiveEntry zipEntry, Map<String, ZipArchiveEntry> zipEntries) {
    String filePath = OdfPackage.normalizePath(zipEntry.getName());
    ErrorHandler errorHandler = mPackage.getErrorHandler();
    if (errorHandler != null) {
      try {
        int zipMethod = zipEntry.getMethod();
        if (zipMethod != ZipArchiveEntry.STORED && zipMethod != ZipArchiveEntry.DEFLATED) {
          mPackage
              .getErrorHandler()
              .error(
                  new OdfValidationException(
                      OdfPackageConstraint.PACKAGE_ENTRY_USING_INVALID_COMPRESSION,
                      mPackage.getBaseURI(),
                      filePath));
        }
      } catch (SAXException ex) {
        Logger.getLogger(OdfPackage.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    zipEntries.put(filePath, zipEntry);
  }

  InputStream getInputStream(ZipArchiveEntry entry) throws IOException {
    if (mZipFile != null) {
      return mZipFile.getInputStream(entry);
    } else {
      ZipArchiveInputStream inputStream =
          createZipInputStream(new ByteArrayInputStream(mZipBuffer));
      ZipArchiveEntry zipEntry = inputStream.getNextZipEntry();
      while (zipEntry != null) {
        if (zipEntry.getName().equalsIgnoreCase(entry.getName())) {
          return readAsInputStream(inputStream);
        }
        zipEntry = inputStream.getNextZipEntry();
      }
      return null;
    }
  }

  private InputStream readAsInputStream(ZipArchiveInputStream inputStream) throws IOException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    if (outputStream != null) {
      byte[] buf = new byte[4096];
      int r = 0;
      while ((r = inputStream.read(buf, 0, 4096)) > -1) {
        outputStream.write(buf, 0, r);
      }
      inputStream.close();
    }
    return new ByteArrayInputStream(outputStream.toByteArray());
  }

  void close() throws IOException {
    if (mZipFile != null) {
      mZipFile.close();
    } else {
      mZipBuffer = null;
    }
  }
}