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

de.schlichtherle.truezip.zip.RawZipOutputStream Maven / Gradle / Ivy

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

import de.schlichtherle.truezip.crypto.param.AesKeyStrength;
import de.schlichtherle.truezip.io.DecoratingOutputStream;
import de.schlichtherle.truezip.io.LEDataOutputStream;
import static de.schlichtherle.truezip.util.HashMaps.initialCapacity;
import static de.schlichtherle.truezip.zip.Constants.*;
import static de.schlichtherle.truezip.zip.ExtraField.WINZIP_AES_ID;
import static de.schlichtherle.truezip.zip.WinZipAesEntryExtraField.VV_AE_1;
import static de.schlichtherle.truezip.zip.WinZipAesEntryExtraField.VV_AE_2;
import static de.schlichtherle.truezip.zip.WinZipAesUtils.overhead;
import static de.schlichtherle.truezip.zip.ZipEntry.*;
import static de.schlichtherle.truezip.zip.ZipParametersUtils.parameters;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.Deflater;
import java.util.zip.ZipException;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.NotThreadSafe;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;

/**
 * Provides unsafe (raw) access to a ZIP file using unsynchronized
 * methods and shared {@link ZipEntry} instances.
 * 

* Warning: This class is not intended for public use * - its API may change at will without prior notification! * * @param the type of the ZIP entries. * @see RawZipFile * @author Christian Schlichtherle */ @NotThreadSafe public abstract class RawZipOutputStream extends DecoratingOutputStream implements Iterable { private final LEDataOutputStream dos; /** The charset to use for entry names and comments. */ private final Charset charset; /** Default compression method for next entry. */ private int method; /** Default compression level for the methods DEFLATED and BZIP2. */ private int level; /** The encoded file comment. */ private @CheckForNull byte[] comment; /** * The list of ZIP entries started to be written so far. * HashMaps entry names to zip entries. */ private final Map entries; /** Start of central directory. */ private long cdOffset; private boolean finished; /** Current ZIP entry. */ private @Nullable ZipEntry entry; private @Nullable OutputMethod processor; /** * Constructs a raw ZIP output stream which decorates the given output * stream and optionally apppends to the given raw ZIP file. * * @param out The output stream to write the ZIP file to. * If {@code appendee} is not {@code null}, then this must be set * up so that it appends to the same ZIP file from which * {@code appendee} is reading. * @param appendee the nullable raw ZIP file to append to. * This may already be closed. * @param param the parameters for writing the ZIP file. * @since TrueZIP 7.3 */ @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") protected RawZipOutputStream( final @WillCloseWhenClosed OutputStream out, final @CheckForNull @WillNotClose RawZipFile appendee, final ZipOutputStreamParameters param) { super(newLEDataOutputStream(out, appendee)); this.dos = (LEDataOutputStream) this.delegate; if (null != appendee) { this.charset = appendee.getRawCharset(); this.comment = appendee.getRawComment(); final Map entries = new LinkedHashMap( initialCapacity(appendee.size() + param.getOverheadSize())); entries.putAll(appendee.getRawEntries()); this.entries = entries; } else { this.charset = param.getCharset(); this.entries = new LinkedHashMap( initialCapacity(param.getOverheadSize())); } setMethod0(param.getMethod()); setLevel0(param.getLevel()); } @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") private static LEDataOutputStream newLEDataOutputStream( final @WillCloseWhenClosed OutputStream out, final @CheckForNull @WillNotClose RawZipFile appendee) { if (null == out) throw new NullPointerException(); return null != appendee ? new AppendingLEDataOutputStream(out, appendee) : out instanceof LEDataOutputStream ? (LEDataOutputStream) out : new LEDataOutputStream(out); } private byte[] encode(String string) { return string.getBytes(charset); } private String decode(byte[] bytes) { return new String(bytes, charset); } /** * Returns the character set which is used for * encoding entry names and the file comment. * * @since TrueZIP 7.3 */ public Charset getRawCharset() { return charset; } /** * Returns the name of the character set which is used for * encoding entry names and the file comment. */ public String getCharset() { return charset.name(); } /** * Returns the number of ZIP entries written so far. */ public int size() { return entries.size(); } /** * Returns an enumeration of all entries written to this ZIP file * so far. * Note that the enumerated entries are shared with this class. * It is illegal to put more entries into this ZIP output stream * concurrently or modify the state of the enumerated entries. * * @deprecated Use {@link #iterator()} instead. */ @Deprecated public Enumeration entries() { return Collections.enumeration(entries.values()); } /** * Returns an iteration of all entries written to this ZIP file so * far. * Note that the iterated entries are shared with this instance. * It is illegal to put more entries into this ZIP output stream * concurrently or modify the state of the iterated entries. */ @Override public Iterator iterator() { return Collections.unmodifiableCollection(entries.values()).iterator(); } /** * Returns the entry for the given name or {@code null} if no entry with * this name exists. * Note that the returned entry is shared with this instance. * It is illegal to change its state! * * @param name the name of the ZIP entry. */ public E getEntry(String name) { return entries.get(name); } /** * Returns the file comment. * * @return The file comment. */ public @Nullable String getComment() { final byte[] comment = this.comment; //return null == comment ? null : new String(comment, charset); return null == comment ? null : decode(comment); } /** * Sets the file comment. * * @param comment the file comment. * @throws IllegalArgumentException if the encoded comment is longer than * {@link UShort#MAX_VALUE} bytes. */ public void setComment(final @CheckForNull String comment) { if (null != comment && !comment.isEmpty()) { final byte[] bytes = encode(comment); UShort.check(bytes.length); this.comment = bytes; } else { this.comment = null; } } /** * Returns the default compression method for subsequent entries. * This property is only used if a {@link ZipEntry} does not specify a * compression method. * The initial value is {@link ZipEntry#DEFLATED}. * * @see #setMethod * @see ZipEntry#getMethod */ public int getMethod() { return method; } /** * Sets the default compression method for entries. * This property is only used if a {@link ZipEntry} does not specify a * compression method. * Legal values are {@link ZipEntry#STORED}, {@link ZipEntry#DEFLATED} * and {@link ZipEntry#BZIP2}. * * @param method the default compression method for entries. * @throws IllegalArgumentException if the method is invalid. * @see #getMethod * @see ZipEntry#setMethod */ public void setMethod(final int method) { setMethod0(method); } private void setMethod0(final int method) { final ZipEntry test = new ZipEntry(""); test.setMethod(method); this.method = test.getMethod(); } /** * Returns the compression level for entries. * This property is only used if the effective compression method is * {@link ZipEntry#DEFLATED} or {@link ZipEntry#BZIP2}. * * @return The compression level for entries. * @see #setLevel */ public int getLevel() { return level; } /** * Sets the compression level for entries. * This property is only used if the effective compression method is * {@link ZipEntry#DEFLATED} or {@link ZipEntry#BZIP2}. * Legal values are {@link Deflater#DEFAULT_COMPRESSION} or range from * {@code Deflater#BEST_SPEED} to {@code Deflater#BEST_COMPRESSION}. * * @param level the compression level for entries. * @throws IllegalArgumentException if the compression level is invalid. * @see #getLevel */ public void setLevel(int level) { setLevel0(level); } private void setLevel0(int level) { if ((level < Deflater.BEST_SPEED || Deflater.BEST_COMPRESSION < level) && Deflater.DEFAULT_COMPRESSION != level) throw new IllegalArgumentException("Invalid compression level!"); this.level = level; } /** * Returns the parameters for encryption or authentication of entries. * * Returns The parameters for encryption or authentication of entries. * @since TrueZIP 7.3 */ protected abstract @CheckForNull ZipCryptoParameters getCryptoParameters(); /** * Returns the total number of (compressed) bytes this stream has written * to the underlying stream. */ public long length() { return this.dos.size(); } /** * Returns {@code true} if and only if this * {@code RawZipOutputStream} is currently writing a ZIP entry. */ public boolean isBusy() { return null != this.entry; } /** * Equivalent to * {@link #putNextEntry(ZipEntry, boolean) putNextEntry(entry, true)}. */ public final void putNextEntry(final E entry) throws IOException { putNextEntry(entry, true); } /** * Starts writing the next ZIP entry to the underlying stream. * Note that if two or more entries with the same name are written * consecutively to this stream, the last entry written will shadow * all other entries, i.e. all of them are written to the ZIP file * (and hence require space), but only the last will be listed in the * central directory. * This is unlike the genuine {@link java.util.zip.ZipOutputStream * java.util.zip.ZipOutputStream} which would throw a {@link ZipException} * in this method when another entry with the same name is to be written. * * @param entry The entry to write. * @param process Whether or not the entry contents should get processed, * e.g. deflated. * This should be set to {@code false} if and only if the * application is going to copy entries from an input ZIP file to * an output ZIP file. * The entries' CRC-32, compressed size and uncompressed * size properties must be set in advance. * @throws ZipException If and only if writing the entry is impossible * because the resulting file would not comply to the ZIP file * format specification. * @throws IOException On any I/O error. */ public void putNextEntry(final E entry, final boolean process) throws ZipException, IOException { closeEntry(); final OutputMethod method = newOutputMethod(entry, process); method.init(entry.clone()); // test! method.init(entry); this.delegate = method.start(); this.processor = method; // Store entry now so that a subsequent call to getEntry(...) returns // it. this.entries.put(entry.getName(), entry); this.entry = entry; } /** * Returns a new output method for the given entry. * Except the property "method", this method must not modify the * given entry. */ @SuppressWarnings("unchecked") private OutputMethod newOutputMethod( final ZipEntry entry, final boolean process) throws ZipException { // HC SVNT DRACONES! OutputMethod processor = new RawOutputMethod(process); if (!process) { assert UNKNOWN != entry.getCrc(); return processor; } int method = entry.getMethod(); if (UNKNOWN == method) entry.setRawMethod(method = getMethod()); boolean skipCrc = false; if (entry.isEncrypted() || WINZIP_AES == method) { ZipCryptoParameters param = getCryptoParameters(); if (WINZIP_AES == method) { param = parameters(WinZipAesParameters.class, param); final WinZipAesEntryExtraField field = (WinZipAesEntryExtraField) entry.getExtraField(WINZIP_AES_ID); if (null != field) { method = field.getMethod(); if (VV_AE_2 == field.getVendorVersion()) skipCrc = true; } } processor = newEncryptedOutputMethod((RawOutputMethod) processor, param); } switch (method) { case STORED: if (!skipCrc) processor = new Crc32CheckingOutputMethod(processor); break; case DEFLATED: processor = new DeflaterOutputMethod(processor); if (!skipCrc) processor = new Crc32UpdatingOutputMethod(processor); break; case BZIP2: processor = new BZip2OutputMethod(processor); if (!skipCrc) processor = new Crc32UpdatingOutputMethod(processor); break; default: throw new ZipException(entry.getName() + " (unsupported compression method " + method + ")"); } return processor; } /** * Returns a new {@code EncryptedOutputMethod}. * * @param processor the output method to decorate. * @param param the {@link ZipCryptoParameters} used to determine and * configure the type of the encrypted ZIP file. * If the run time class of this parameter matches multiple * parameter interfaces, it is at the discretion of this * implementation which one is picked and hence which type of * encrypted ZIP file is created. * If you need more control over this, pass in an instance which's * run time class just implements the * {@link ZipParametersProvider} interface. * Instances of this interface are queried to find crypto * parameters which match a known encrypted ZIP file type. * This algorithm is recursively applied. * @return A new {@code EncryptedOutputMethod}. * @throws ZipCryptoParametersException if {@code param} is {@code null} or * no suitable crypto parameters can get found. */ private EncryptedOutputMethod newEncryptedOutputMethod( final RawOutputMethod processor, @CheckForNull ZipParameters param) throws ZipParametersException { assert null != processor; while (null != param) { // Order is important here to support multiple interface implementations! if (param instanceof WinZipAesParameters) { return new WinZipAesOutputMethod(processor, (WinZipAesParameters) param); } else if (param instanceof ZipParametersProvider) { param = ((ZipParametersProvider) param) .get(ZipCryptoParameters.class); } else { break; } } throw new ZipParametersException("No suitable crypto parameters available!"); } /** * Writes all necessary data for this entry to the underlying stream. * * @throws ZipException If and only if writing the entry is impossible * because the resulting file would not comply to the ZIP file * format specification. * @throws IOException On any I/O error. */ public void closeEntry() throws IOException { final ZipEntry entry = this.entry; if (null == entry) return; this.processor.finish(); this.delegate.flush(); this.delegate = this.dos; this.processor = null; this.entry = null; } /** * Closes the current entry and writes the Central Directory to the * underlying output stream. *

* Notes: *

    *
  • The underlying stream is not closed.
  • *
  • Unlike Sun's implementation in J2SE 1.4.2, you may continue to use * this ZIP output stream with putNextEntry(...) and the like. * When you finally close the stream, the central directory will * contain all entries written.
  • *
* * @throws ZipException If and only if writing the entry is impossible * because the resulting file would not comply to the ZIP file * format specification. * @throws IOException On any I/O error. */ public void finish() throws IOException { if (this.finished) return; closeEntry(); final LEDataOutputStream dos = this.dos; this.cdOffset = dos.size(); final Iterator i = this.entries.values().iterator(); while (i.hasNext()) if (!writeCentralFileHeader(i.next())) i.remove(); writeEndOfCentralDirectory(); this.finished = true; } /** * Writes a Central File Header record. * * @return {@code false} if and only if the record has been skipped, * i.e. not written for some other reason than an I/O error. * @throws IOException On any I/O error. */ private boolean writeCentralFileHeader(final ZipEntry entry) throws IOException { final long csize = entry.getCompressedSize(); final long size = entry.getSize(); // This test MUST NOT include the CRC-32 because VV_AE_2 sets it to // UNKNOWN! if (UNKNOWN == (csize | size)) { // See http://java.net/jira/browse/TRUEZIP-144 : // The kernel may set any of these properties to UNKNOWN after the // entry content has already been written in order to signal that // this entry should not get included in the central directory. // E.g. this may happen with the GROW output option preference. return false; } final LEDataOutputStream dos = this.dos; // central file header signature 4 bytes (0x02014b50) dos.writeInt(CFH_SIG); // version made by 2 bytes dos.writeShort((entry.getRawPlatform() << 8) | 63); // version needed to extract 2 bytes dos.writeShort(entry.getRawVersionNeededToExtract()); // general purpose bit flag 2 bytes dos.writeShort(entry.getGeneralPurposeBitFlags()); // compression method 2 bytes dos.writeShort(entry.getRawMethod()); // last mod file time 2 bytes // last mod file date 2 bytes dos.writeInt((int) entry.getRawTime()); // crc-32 4 bytes dos.writeInt((int) entry.getRawCrc()); // compressed size 4 bytes dos.writeInt((int) entry.getRawCompressedSize()); // uncompressed size 4 bytes dos.writeInt((int) entry.getRawSize()); // file name length 2 bytes final byte[] name = encode(entry.getName()); dos.writeShort(name.length); // extra field length 2 bytes final byte[] extra = entry.getRawExtraFields(); dos.writeShort(extra.length); // file comment length 2 bytes final byte[] comment = getCommentEncoded(entry); dos.writeShort(comment.length); // disk number start 2 bytes dos.writeShort(0); // internal file attributes 2 bytes dos.writeShort(0); // external file attributes 4 bytes dos.writeInt((int) entry.getRawExternalAttributes()); // relative offset of local header 4 bytes dos.writeInt((int) entry.getRawOffset()); // file name (variable size) dos.write(name); // extra field (variable size) dos.write(extra); // file comment (variable size) dos.write(comment); return true; } private byte[] getCommentEncoded(final ZipEntry entry) { return encode(entry.getRawComment()); } /** * Writes the End Of Central Directory record. * * @throws IOException On any I/O error. */ private void writeEndOfCentralDirectory() throws IOException { final LEDataOutputStream dos = this.dos; final long cdEntries = entries.size(); final long cdOffset = this.cdOffset; final long cdSize = dos.size() - cdOffset; final boolean cdEntriesZip64 = cdEntries > UShort.MAX_VALUE || FORCE_ZIP64_EXT; final boolean cdSizeZip64 = cdSize > UInt .MAX_VALUE || FORCE_ZIP64_EXT; final boolean cdOffsetZip64 = cdOffset > UInt .MAX_VALUE || FORCE_ZIP64_EXT; final int cdEntries16 = cdEntriesZip64 ? UShort.MAX_VALUE : (int) cdEntries; final long cdSize32 = cdSizeZip64 ? UInt .MAX_VALUE : cdSize; final long cdOffset32 = cdOffsetZip64 ? UInt .MAX_VALUE : cdOffset; final boolean zip64 // ZIP64 extensions? = cdEntriesZip64 || cdSizeZip64 || cdOffsetZip64; if (zip64) { final long zip64eocdOffset // relative offset of the zip64 end of central directory record = dos.size(); // zip64 end of central dir // signature 4 bytes (0x06064b50) dos.writeInt(ZIP64_EOCDR_SIG); // size of zip64 end of central // directory record 8 bytes dos.writeLong(ZIP64_EOCDR_MIN_LEN - 12); // version made by 2 bytes dos.writeShort(63); // version needed to extract 2 bytes dos.writeShort(46); // due to potential use of BZIP2 compression // number of this disk 4 bytes dos.writeInt(0); // number of the disk with the // start of the central directory 4 bytes dos.writeInt(0); // total number of entries in the // central directory on this disk 8 bytes dos.writeLong(cdEntries); // total number of entries in the // central directory 8 bytes dos.writeLong(cdEntries); // size of the central directory 8 bytes dos.writeLong(cdSize); // offset of start of central // directory with respect to // the starting disk number 8 bytes dos.writeLong(cdOffset); // zip64 extensible data sector (variable size) // // zip64 end of central dir locator // signature 4 bytes (0x07064b50) dos.writeInt(ZIP64_EOCDL_SIG); // number of the disk with the // start of the zip64 end of // central directory 4 bytes dos.writeInt(0); // relative offset of the zip64 // end of central directory record 8 bytes dos.writeLong(zip64eocdOffset); // total number of disks 4 bytes dos.writeInt(1); } // end of central dir signature 4 bytes (0x06054b50) dos.writeInt(EOCDR_SIG); // number of this disk 2 bytes dos.writeShort(0); // number of the disk with the // start of the central directory 2 bytes dos.writeShort(0); // total number of entries in the // central directory on this disk 2 bytes dos.writeShort(cdEntries16); // total number of entries in // the central directory 2 bytes dos.writeShort(cdEntries16); // size of the central directory 4 bytes dos.writeInt((int) cdSize32); // offset of start of central // directory with respect to // the starting disk number 4 bytes dos.writeInt((int) cdOffset32); // .ZIP file comment length 2 bytes final byte[] comment = getRawComment(); dos.writeShort(comment.length); // .ZIP file comment (variable size) dos.write(comment); } private byte[] getRawComment() { final byte[] comment = this.comment; return null != comment ? comment : EMPTY; } /** * Closes this output stream and releases any system resources * associated with the stream. * This closes the open output stream writing to this ZIP file, * if any. * * @throws IOException On any I/O error. */ @Override public void close() throws IOException { finish(); this.delegate.close(); } /** Adjusts the number of written bytes in the offset for appending mode. */ private static final class AppendingLEDataOutputStream extends LEDataOutputStream { @CreatesObligation @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") AppendingLEDataOutputStream( final @WillCloseWhenClosed OutputStream out, final @WillNotClose RawZipFile appendee) { super(out); assert null != out; super.written = appendee.getOffsetMapper().unmap(appendee.length()); } } // AppendingLEDataOutputStream private final class RawOutputMethod implements OutputMethod { final boolean process; /** Start of entry data. */ private long dataStart; @Nullable ZipEntry entry; RawOutputMethod(final boolean process) { this.process = process; } @Override public void init(final ZipEntry entry) throws ZipException { { final long size = encode(entry.getName()).length + entry.getRawExtraFields().length + encode(entry.getRawComment()).length; if (UShort.MAX_VALUE < size) throw new ZipException(entry.getName() + " (the total size " + size + " for the name, extra fields and comment exceeds the maximum size " + UShort.MAX_VALUE + ")"); } if (STORED == entry.getMethod() || !this.process) { if (UNKNOWN == entry.getCrc()) throw new ZipException(entry.getName() + " (unknown CRC-32 value)"); if (UNKNOWN == entry.getCompressedSize()) throw new ZipException(entry.getName() + " (unknown compressed size)"); if (UNKNOWN == entry.getSize()) throw new ZipException(entry.getName() + " (unknown uncompressed size)"); } if (UNKNOWN == entry.getPlatform()) entry.setRawPlatform(PLATFORM_FAT); if (UNKNOWN == entry.getTime()) entry.setTime(System.currentTimeMillis()); this.entry = entry; } /** * Writes the Local File Header. */ @Override public OutputStream start() throws IOException { final LEDataOutputStream dos = RawZipOutputStream.this.dos; final long offset = dos.size(); final ZipEntry entry = this.entry; final boolean encrypted = entry.isEncrypted(); final boolean dd = entry.isDataDescriptorRequired(); // Compose General Purpose Bit Flag. // See appendix D of PKWARE's ZIP File Format Specification. final boolean utf8 = UTF8.equals(charset); final int general = (encrypted ? GPBF_ENCRYPTED : 0) | (dd ? GPBF_DATA_DESCRIPTOR : 0) | (utf8 ? GPBF_UTF8 : 0); // Start changes. RawZipOutputStream.this.finished = false; // local file header signature 4 bytes (0x04034b50) dos.writeInt(LFH_SIG); // version needed to extract 2 bytes dos.writeShort(entry.getRawVersionNeededToExtract()); // general purpose bit flag 2 bytes dos.writeShort(general); // compression method 2 bytes dos.writeShort(entry.getRawMethod()); // last mod file time 2 bytes // last mod file date 2 bytes dos.writeInt((int) entry.getRawTime()); // crc-32 4 bytes // compressed size 4 bytes // uncompressed size 4 bytes if (dd) { dos.writeInt(0); dos.writeInt(0); dos.writeInt(0); } else { dos.writeInt((int) entry.getRawCrc()); dos.writeInt((int) entry.getRawCompressedSize()); dos.writeInt((int) entry.getRawSize()); } // file name length 2 bytes final byte[] name = encode(entry.getName()); dos.writeShort(name.length); // extra field length 2 bytes final byte[] extra = entry.getRawExtraFields(); dos.writeShort(extra.length); // file name (variable size) dos.write(name); // extra field (variable size) dos.write(extra); // Commit changes. entry.setGeneralPurposeBitFlags(general); entry.setRawOffset(offset); // Update data start. this.dataStart = dos.size(); return dos; } /** * Checks the compressed entry size and optionally writes the Data * Descriptor. */ @Override public void finish() throws IOException { final LEDataOutputStream dos = RawZipOutputStream.this.dos; final long csize = dos.size() - this.dataStart; final ZipEntry entry = this.entry; assert UNKNOWN != entry.getCrc(); assert UNKNOWN != entry.getSize(); if (entry.getGeneralPurposeBitFlag(GPBF_DATA_DESCRIPTOR)) { entry.setRawCompressedSize(csize); // data descriptor signature 4 bytes (0x08074b50) dos.writeInt(DD_SIG); // crc-32 4 bytes dos.writeInt((int) entry.getRawCrc()); // compressed size 4 or 8 bytes // uncompressed size 4 or 8 bytes if (entry.isZip64ExtensionsRequired()) { dos.writeLong(csize); dos.writeLong(entry.getSize()); } else { dos.writeInt((int) entry.getRawCompressedSize()); dos.writeInt((int) entry.getRawSize()); } } else if (entry.getCompressedSize() != csize) { throw new ZipException(entry.getName() + " (expected compressed entry size " + entry.getCompressedSize() + ", but is actually " + csize + ")"); } } } // RawOutputMethod private abstract class EncryptedOutputMethod extends DecoratingOutputMethod { EncryptedOutputMethod(RawOutputMethod processor) { super(processor); } } // EncryptedOutputMethod private final class WinZipAesOutputMethod extends EncryptedOutputMethod { final WinZipAesParameters generalParam; boolean suppressCrc; @Nullable WinZipAesEntryParameters entryParam; @Nullable WinZipAesEntryOutputStream out; @Nullable ZipEntry entry; WinZipAesOutputMethod( RawOutputMethod processor, final WinZipAesParameters param) { super(processor); assert null != param; this.generalParam = param; } @Override public void init(final ZipEntry entry) throws ZipException { // HC SVNT DRACONES! final WinZipAesEntryParameters entryParam = new WinZipAesEntryParameters(this.generalParam, entry); final AesKeyStrength keyStrength = entryParam.getKeyStrength(); this.entryParam = entryParam; WinZipAesEntryExtraField field = null; int method = entry.getMethod(); long csize = entry.getCompressedSize(); if (WINZIP_AES == method) { field = (WinZipAesEntryExtraField) entry.getExtraField( WINZIP_AES_ID); if (null != field) { method = field.getMethod(); if (UNKNOWN != csize) csize -= overhead(field.getKeyStrength()); entry.setRawMethod(method); // restore for delegate.init(*) } } if (null == field) field = new WinZipAesEntryExtraField(); field.setKeyStrength(keyStrength); field.setMethod(method); final long size = entry.getSize(); if (20 <= size && BZIP2 != method) { field.setVendorVersion(VV_AE_1); } else { field.setVendorVersion(VV_AE_2); this.suppressCrc = true; } entry.addExtraField(field); if (UNKNOWN != csize) { csize += overhead(keyStrength); entry.setRawCompressedSize(csize); } if (this.suppressCrc) { final long crc = entry.getCrc(); entry.setRawCrc(0); this.delegate.init(entry); entry.setCrc(crc); } else { this.delegate.init(entry); } entry.setRawMethod(WINZIP_AES); this.entry = entry; } @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") public OutputStream start() throws IOException { // see DeflatedOutputMethod.finish(). final ZipEntry entry = this.entry; final OutputMethod delegate = this.delegate; final WinZipAesEntryParameters entryParam = this.entryParam; assert null != entryParam; assert null == this.out; if (suppressCrc) { final long crc = entry.getCrc(); entry.setRawCrc(0); this.out = new WinZipAesEntryOutputStream( (LEDataOutputStream) delegate.start(), entryParam); entry.setCrc(crc); } else { this.out = new WinZipAesEntryOutputStream( (LEDataOutputStream) delegate.start(), entryParam); } return this.out; } @Override public void finish() throws IOException { // see DeflatedOutputMethod.finish(). assert null != this.out; this.out.finish(); if (this.suppressCrc) { final ZipEntry entry = this.entry; entry.setRawCrc(0); this.delegate.finish(); // Set to UNKNOWN in order to signal to // Crc32CheckingOutputMethod that it should not check it and // signal to writeCentralFileHeader() that it should write 0. entry.setCrc(UNKNOWN); } else { this.delegate.finish(); } } } // WinZipAesOutputMethod private final class BZip2OutputMethod extends DecoratingOutputMethod { @Nullable BZip2CompressorOutputStream cout; @Nullable LEDataOutputStream dout; @Nullable ZipEntry entry; BZip2OutputMethod(OutputMethod processor) { super(processor); } @Override public void init(final ZipEntry entry) throws ZipException { entry.setCompressedSize(UNKNOWN); this.delegate.init(entry); this.entry = entry; } @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") public OutputStream start() throws IOException { assert null == this.cout; assert null == this.dout; OutputStream out = this.delegate.start(); final long size = this.entry.getSize(); final int blockSize = UNKNOWN != size ? BZip2CompressorOutputStream.chooseBlockSize(size) : getBZip2BlockSize(); out = this.cout = new BZip2CompressorOutputStream(out, blockSize); return this.dout = new LEDataOutputStream(out); } int getBZip2BlockSize() { final int level = RawZipOutputStream.this.getLevel(); if (BZip2CompressorOutputStream.MIN_BLOCKSIZE <= level && level <= BZip2CompressorOutputStream.MAX_BLOCKSIZE) return level; return BZip2CompressorOutputStream.MAX_BLOCKSIZE; } @Override public void finish() throws IOException { this.dout.flush(); // superfluous - should not buffer this.cout.finish(); this.entry.setRawSize(this.dout.size()); this.delegate.finish(); } } // BZip2OutputMethod private final class DeflaterOutputMethod extends DecoratingOutputMethod { @Nullable ZipDeflaterOutputStream out; @Nullable ZipEntry entry; DeflaterOutputMethod(OutputMethod processor) { super(processor); } @Override public void init(final ZipEntry entry) throws ZipException { entry.setCompressedSize(UNKNOWN); this.delegate.init(entry); this.entry = entry; } @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") public OutputStream start() throws IOException { assert null == this.out; return this.out = new ZipDeflaterOutputStream( this.delegate.start(), RawZipOutputStream.this.getLevel(), MAX_FLATER_BUF_LENGTH); } @Override public void finish() throws IOException { this.out.finish(); final Deflater deflater = this.out.getDeflater(); final ZipEntry entry = this.entry; //entry.setRawCompressedSize(deflater.getBytesWritten()); entry.setRawSize(deflater.getBytesRead()); deflater.end(); this.delegate.finish(); } } // DeflaterOutputMethod private abstract class Crc32OutputMethod extends DecoratingOutputMethod { @Nullable Crc32OutputStream out; Crc32OutputMethod(OutputMethod processor) { super(processor); } @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings("OBL_UNSATISFIED_OBLIGATION") public OutputStream start() throws IOException { assert null == this.out; return this.out = new Crc32OutputStream(this.delegate.start()); } @Override public abstract void finish() throws IOException; } // Crc32OutputMethod private final class Crc32CheckingOutputMethod extends Crc32OutputMethod { Crc32CheckingOutputMethod(OutputMethod processor) { super(processor); } @Override public void finish() throws IOException { this.delegate.finish(); final ZipEntry entry = RawZipOutputStream.this.entry; final long expectedCrc = entry.getCrc(); if (UNKNOWN != expectedCrc) { final long actualCrc = this.out.getChecksum().getValue(); if (expectedCrc != actualCrc) throw new CRC32Exception(entry.getName(), expectedCrc, actualCrc); } } } // Crc32CheckingOutputMethod private final class Crc32UpdatingOutputMethod extends Crc32OutputMethod { Crc32UpdatingOutputMethod(OutputMethod processor) { super(processor); } @Override public void finish() throws IOException { final ZipEntry entry = RawZipOutputStream.this.entry; final long crc = this.out.getChecksum().getValue(); entry.setRawCrc(crc); this.delegate.finish(); } } // Crc32UpdatingOutputMethod }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy