All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.truezip.fs.archive.zip.ZipDriver Maven / Gradle / Ivy

/*
 * Copyright (C) 2005-2013 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package de.schlichtherle.truezip.fs.archive.zip;

import de.schlichtherle.truezip.entry.Entry;
import static de.schlichtherle.truezip.entry.Entry.Access.WRITE;
import static de.schlichtherle.truezip.entry.Entry.Size.DATA;
import de.schlichtherle.truezip.entry.Entry.Type;
import static de.schlichtherle.truezip.entry.Entry.Type.DIRECTORY;
import de.schlichtherle.truezip.fs.*;
import static de.schlichtherle.truezip.fs.FsOutputOption.*;
import de.schlichtherle.truezip.key.KeyManagerProvider;
import de.schlichtherle.truezip.key.KeyProvider;
import de.schlichtherle.truezip.key.sl.KeyManagerLocator;
import de.schlichtherle.truezip.rof.ReadOnlyFile;
import de.schlichtherle.truezip.socket.*;
import de.schlichtherle.truezip.util.BitField;
import de.schlichtherle.truezip.util.HashMaps;
import de.schlichtherle.truezip.util.JSE7;
import de.schlichtherle.truezip.zip.*;
import static de.schlichtherle.truezip.zip.ZipEntry.*;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Deflater;
import javax.annotation.CheckForNull;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.Immutable;

/**
 * An archive driver for ZIP files.
 * By default, ZIP files use the IBM437 character set for the encoding of entry
 * names and comments (unless the General Purpose Bit 11 is present in
 * accordance with appendix D of the
 * ZIP File Format Specification).
 * They also apply the date/time conversion rules according to
 * {@link DateTimeConverter#ZIP}.
 * This configuration pretty much constraints the applicability of this driver
 * to North American and Western European countries.
 * However, this driver generally provides best interoperability with third
 * party tools like the Windows Explorer, WinZip, 7-Zip etc.
 * To some extent this applies even outside these countries.
 * Therefore, while you should use this driver to access plain old ZIP files,
 * you should not use it for custom application file formats - use the
 * {@link JarDriver} instead in this case.
 * 

* This driver does not check the CRC value of any entries in existing * archives - use {@link CheckedZipDriver} instead. *

* Sub-classes must be thread-safe and should be immutable! * * @author Christian Schlichtherle */ @Immutable public class ZipDriver extends FsCharsetArchiveDriver implements ZipOutputStreamParameters, ZipFileParameters { private static final Logger logger = Logger.getLogger( ZipDriver.class.getName(), ZipDriver.class.getName()); /** * The character set for entry names and comments in "traditional" * ZIP files, which is {@code "IBM437"}. */ private static final Charset ZIP_CHARSET = Charset.forName("IBM437"); private final IOPool ioPool; /** * Constructs a new ZIP driver. * This constructor uses {@link #ZIP_CHARSET} for encoding entry names * and comments. * * @param ioPoolProvider the provider for I/O entry pools for allocating * temporary I/O entries (buffers). */ public ZipDriver(IOPoolProvider ioPoolProvider) { this(ioPoolProvider, ZIP_CHARSET); } /** * Constructs a new ZIP driver. * * @param provider the provider for the I/O buffer pool. * @param charset the character set for encoding entry names and comments. */ protected ZipDriver(IOPoolProvider provider, Charset charset) { super(charset); if (null == (this.ioPool = provider.get())) throw new NullPointerException(); } /** * Returns the key provider sync strategy. * The implementation in the class {@link ZipDriver} returns * {@link KeyProviderSyncStrategy#RESET_CANCELLED_KEY}. * * @return The key provider sync strategy. */ protected KeyProviderSyncStrategy getKeyProviderSyncStrategy() { return KeyProviderSyncStrategy.RESET_CANCELLED_KEY; } /** * Returns the provider for key managers for accessing protected resources * (encryption). *

* The implementation in {@link ZipDriver} always returns * {@link KeyManagerLocator#SINGLETON}. * When overriding this method, subsequent calls must return the same * object. * * @return The provider for key managers for accessing protected resources * (encryption). * @since TrueZIP 7.3. */ protected KeyManagerProvider getKeyManagerProvider() { return KeyManagerLocator.SINGLETON; } final @CheckForNull ZipCryptoParameters zipCryptoParameters(ZipInputShop input) { return zipCryptoParameters(input.getModel(), input.getRawCharset()); } final @CheckForNull ZipCryptoParameters zipCryptoParameters(ZipOutputShop output) { return zipCryptoParameters(output.getModel(), output.getRawCharset()); } /** * Returns the ZIP crypto parameters for the given file system model * and character set or {@code null} if not available. * To enable the use of this method when writing an archive entry with the * client APIs, you must use {@link FsOutputOption#ENCRYPT}. *

* The implementation in the class {@link ZipDriver} returns * {@code new KeyManagerZipCryptoParameters(getKeyManagerProvider(), mountPointUri(model), charset)}. * * @param model the file system model. * @param charset charset the character set used for encoding entry names * and the file comment in the ZIP file. * @return The ZIP crypto parameters for the given file system model * and character set or {@code null} if not available. * @since TrueZIP 7.3 */ protected @CheckForNull ZipCryptoParameters zipCryptoParameters( FsModel model, Charset charset) { return new KeyManagerZipCryptoParameters(this, model, charset); } /** * A template method which derives the URI which represents the mount point * of the given file system model as the base resource URI for looking up * {@link KeyProvider}s. *

* The implementation in the class {@link ZipDriver} returns the * expression {@code model.getMountPoint().toHierarchicalUri()} * in order to improve the readability of the URI in comparison to the * expression {@code model.getMountPoint().toUri()}. * * @param model the file system model. * @return The URI which represents the file system model's mount point. * @see #TRUEZIP-72 */ public URI mountPointUri(FsModel model) { return model.getMountPoint().toHierarchicalUri(); } /** * A template method which derives the resource URI for looking up a * {@link KeyProvider} from the given file system model and entry name. *

* The implementation in the class {@code ZipDriver} ignores the given * entry name and just returns the expression {@code mountPointUri(model)} * in order to lookup the same key provider for all entries in a ZIP file. *

* An alternative implementation in a sub-class could return the expression * {@code mountPointUri(model).resolve("/" + name)} instead. * * @param model the file system model. * @param name the entry name. * @return The URI for looking up a {@link KeyProvider}. */ public URI resourceUri(FsModel model, String name) { //return mountPointUri(model).resolve("/" + name); return mountPointUri(model); } /** * {@inheritDoc} * * @return The implementation in the class {@link ZipDriver} returns * {@code true} because when reading a ZIP file sequentially, * each ZIP entry should "override" any previously read * ZIP entry with an equal name. * This holds true even if the central directory is used to access * the ZIP entries in random order. * @since TrueZIP 7.3 */ @Override public boolean getRedundantContentSupport() { return true; } /** * {@inheritDoc} * * @return The implementation in the class {@link ZipDriver} returns * {@code true} because when reading a ZIP file sequentially, * each ZIP entry should "override" any previously read * ZIP entry with an equal name. * This holds true even if the central directory is used to access * the ZIP entries in random order. * @since TrueZIP 7.3 */ @Override public boolean getRedundantMetaDataSupport() { return true; } /** * Whether or not the content of the given entry shall get * checked/authenticated when reading it. * If this method returns {@code true} and the check fails, * then an {@link IOException} gets thrown. * * @return {@code entry.isEncrypted()}. * @since TrueZIP 7.3 */ protected boolean check( @WillNotClose ZipInputShop input, ZipDriverEntry entry) { return entry.isEncrypted(); } final boolean process( @WillNotClose ZipInputShop input, ZipDriverEntry local, ZipDriverEntry peer) { return process(local, peer); } final boolean process( @WillNotClose ZipOutputShop output, ZipDriverEntry local, ZipDriverEntry peer) { return process(peer, local); } /** * Returns {@code true} if and only if the content of the given input * target entry needs processing when it gets copied to the given output * target entry. * This method gets called twice (once on each side of a copy operation) * and should return {@code false} unless both target entries can mutually * agree on transferring raw (unprocessed) content. * Note that it is an error to compare the properties of the target entries * because this method may get called before the local output target gets * mutated to compare equal with the peer input target! *

* The implementation in the class {@link ZipDriver} returns * {@code local.isEncrypted() || peer.isEncrypted()} in order to cover the * typical case that the cipher keys of both targets are not the same. * Note that there is no secure way to explicitly test for this. * * @param input the input target entry for copying the contents. * @param output the output target entry for copying the contents. * @return Whether the content to get copied from the input target entry * to the output target entry needs to get processed or can get * sent in raw format. * @since TrueZIP 7.3 */ protected boolean process(ZipDriverEntry input, ZipDriverEntry output) { return input.isEncrypted() || output.isEncrypted(); } @Override protected final IOPool getPool() { return ioPool; } /** * {@inheritDoc} *

* The implementation in the class {@link ZipDriver} * returns {@code false}. * * @return {@code false} */ @Override public boolean getPreambled() { return false; } /** * {@inheritDoc} *

* The implementation in the class {@link ZipDriver} * returns {@code false}. * * @return {@code false} */ @Override public boolean getPostambled() { return false; } /** * {@inheritDoc} *

* The implementation in the class {@link ZipDriver} * returns {@code HashMaps#OVERHEAD_SIZE}. * * @since TrueZIP 7.3 * @return {@code HashMaps#OVERHEAD_SIZE} * @deprecated This method is reserved for future use - do not use * or override this method! */ @Deprecated @Override public int getOverheadSize() { return HashMaps.OVERHEAD_SIZE; } /** * {@inheritDoc} *

* The implementation in the class {@link ZipDriver} * returns {@code ZipEntry#DEFLATED}. * * @return {@code ZipEntry#DEFLATED} */ @Override public int getMethod() { return DEFLATED; } /** * {@inheritDoc} *

* The implementation in the class {@link ZipDriver} * returns {@code Deflater#BEST_COMPRESSION}. * * @return {@code Deflater#BEST_COMPRESSION} */ @Override public int getLevel() { return Deflater.BEST_COMPRESSION; } /** * {@inheritDoc} *

* The implementation in the class {@link FsArchiveDriver} calls * {@link #superNewController} a * partial file system controller chain and passes the result to * {@link #decorate} for further decoration. */ @Override public FsController newController(FsModel model, FsController parent) { return decorate(superNewController(model, parent)); } /** * Returns a partial file system controller chain by calling * {@link FsCharsetArchiveDriver#newController(FsModel, FsController)} on * the super class. * * @deprecated since TrueZIP 7.6 - override {@link #decorate} instead. */ @Deprecated protected final FsController superNewController(FsModel model, FsController parent) { return super.newController(model, parent); } /** * A hook which decorates the given file system controller chain with some * more file system controller(s). *

* The implementation in the class {@link ZipDriver} returns the expression * {@code new ZipKeyController(controller, this)}. * Overridde this method in order to return just the given * {@code controller} if you are overriding * {@link #zipCryptoParameters(FsModel, Charset)} and do not want to use * a locatable key manager to resolve passwords for WinZip AES encryption. * * @param the file system model used by the given controller. * @param controller the file system controller to decorate or return. * Note that this controller may throw {@link RuntimeException}s * for non-local control flow! * @return The decorated file system controller or simply * {@code controller}. * @since TrueZIP 7.6 */ public FsController decorate( FsController controller) { return new ZipKeyController(controller, this); } @Override public ZipDriverEntry newEntry( String name, final Type type, final @CheckForNull Entry template, final BitField mknod) throws CharConversionException { name = toZipOrTarEntryName(name, type); final ZipDriverEntry entry; if (template instanceof ZipEntry) { entry = newEntry(name, (ZipEntry) template); } else { entry = newEntry(name); if (null != template) { entry.setTime(template.getTime(WRITE)); entry.setSize(template.getSize(DATA)); } } if (DIRECTORY != type) { if (UNKNOWN == entry.getMethod()) { final int method; if (mknod.get(COMPRESS)) method = DEFLATED; else if (mknod.get(STORE)) method = STORED; else method = getMethod(); entry.setMethod(method); if (STORED != method) entry.setCompressedSize(UNKNOWN); } if (mknod.get(ENCRYPT)) entry.setEncrypted(true); } return entry; } /** * Returns a new ZIP archive entry with the given {@code name}. * * @param name the entry name. * @return {@code new ZipDriverEntry(name)} */ @Override public ZipDriverEntry newEntry(String name) { return new ZipDriverEntry(name); } /** * Returns a new ZIP archive entry with the given {@code name} and all * other properties copied from the given template. * * @param name the entry name. * @return {@code new ZipDriverEntry(name, template)} */ public ZipDriverEntry newEntry(String name, ZipEntry template) { return new ZipDriverEntry(name, template); } /** * {@inheritDoc} *

* The implementation in the class {@link ZipDriver} acquires a read only * file from the given socket and forwards the call to * {@link #newInputShop}. */ @Override public InputShop newInputShop( final FsModel model, final InputSocket input) throws IOException { if (null == model) throw new NullPointerException(); final ReadOnlyFile rof = input.newReadOnlyFile(); try { return newInputShop(model, rof); } catch (final IOException ex) { try { rof.close(); } catch (final IOException ex2) { if (JSE7.AVAILABLE) ex.addSuppressed(ex2); } throw ex; } } @CreatesObligation protected InputShop newInputShop( FsModel model, @WillCloseWhenClosed ReadOnlyFile rof) throws IOException { assert null != model; final ZipInputShop input = new ZipInputShop(this, model, rof); try { input.recoverLostEntries(); } catch (final IOException ex) { logger.log(Level.WARNING, "junkInTheTrunk.warning", new Object[] { mountPointUri(model), input.getPostambleLength(), }); logger.log(Level.FINE, "junkInTheTrunk.fine", ex); } return input; } /** * This implementation modifies {@code options} in the following way before * it forwards the call to {@code controller}: *

    *
  1. {@link FsOutputOption#STORE} is set. *
  2. If {@link FsOutputOption#GROW} is set, {@link FsOutputOption#APPEND} * gets set too, and {@link FsOutputOption#CACHE} gets cleared. *
*

* The resulting output socket is then wrapped in a private nested class * for an upcast in {@link #newOutputShop}. * Thus, when overriding this method, {@link #newOutputShop} should get * overridden, too. * Otherwise, a class cast exception will get thrown in * {@link #newOutputShop}. */ @Override public OptionOutputSocket getOutputSocket( final FsController controller, final FsEntryName name, BitField options, final @CheckForNull Entry template) { // Leave FsOutputOption.COMPRESS untouched - the driver shall be given // opportunity to apply its own preferences to sort out such a conflict. options = options.set(STORE); if (options.get(GROW)) options = options.set(APPEND).clear(CACHE); return new OptionOutputSocket( controller.getOutputSocket(name, options, template), options); } /** * This implementation first checks if {@link FsOutputOption#GROW} is set * for the given {@code output} socket. * If this is the case and the given {@code source} is not {@code null}, * then it's marked for appending to it. * Then, an output stream is acquired from the given {@code output} socket * and the parameters are forwarded to {@link #newOutputShop(FsModel, OptionOutputSocket, ZipInputShop)} * and the result gets wrapped in a new {@link MultiplexedOutputShop} * which uses the current {@link #getPool}. */ @Override public final OutputShop newOutputShop( final FsModel model, final OutputSocket output, final InputShop source) throws IOException { if (null == model) throw new NullPointerException(); return newOutputShop0( model, (OptionOutputSocket) output, (ZipInputShop) source); } @CreatesObligation private OutputShop newOutputShop0( final FsModel model, final OptionOutputSocket output, final @CheckForNull @WillNotClose ZipInputShop source) throws IOException { final BitField options = output.getOptions(); if (null != source) source.setAppendee(options.get(GROW)); return newOutputShop(model, output, source); } @CreatesObligation protected OutputShop newOutputShop( final FsModel model, final OptionOutputSocket output, final @CheckForNull @WillNotClose ZipInputShop source) throws IOException { assert null != model; final OutputStream out = output.newOutputStream(); try { return newOutputShop(model, out, source); } catch (final IOException ex) { try { out.close(); } catch (final IOException ex2) { if (JSE7.AVAILABLE) ex.addSuppressed(ex2); } throw ex; } } @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") protected OutputShop newOutputShop( FsModel model, @WillCloseWhenClosed OutputStream out, @CheckForNull @WillNotClose ZipInputShop source) throws IOException { return new MultiplexedOutputShop( new ZipOutputShop(this, model, out, source), getPool()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy