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

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

Go to download

The file system driver family for ZIP and related archive file types. Add the JAR artifact of this module to the run time class path to make its file system drivers available for service location in the client API modules.

There is a newer version: 7.7.10
Show newest version
/*
 * Copyright (C) 2005-2013 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