/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.util.compression; import java.io.IOException; import java.io.InputStream; import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import java.util.zip.Deflater; import java.util.zip.DeflaterInputStream; /** * Implements an input stream for compressing input data in the GZIP compression format. */ public class GZIPCompressorInputStream extends DeflaterInputStream { // GZIP header magic number. private final static int GZIP_MAGIC = 0x8b1f; // Writes GZIP member header. private final static byte[] HEADER = {(byte) GZIP_MAGIC, // Magic number (short) (byte) (GZIP_MAGIC >> 8), // Magic number (short) Deflater.DEFLATED, // Compression method (CM) 0, // Flags (FLG) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Modification time MTIME (int) 0, // Extra flags (XFLG) 0 // Operating system (OS) }; // Trailer length in bytes. private final static int TRAILER_LENGTH = 8; // If true, the GZIP trailer has been written. private boolean trailerWritten = false; // Internal buffer for GZIP header and trailer. private Buffer buffer; /** * Helper inner class containing the length and position of the internal buffer. */ private class Buffer { public byte[] data; public int position; public int length; /** * Creates a new buffer initializing it with the GZIP header content. */ public Buffer() { data = new byte[Math.max(HEADER.length, TRAILER_LENGTH)]; System.arraycopy(HEADER, 0, data, 0, HEADER.length); position = 0; length = HEADER.length; } /** * Returns the amount of bytes that are left to be read from the buffer. * * @return The byte counts to be read. */ int getByteCountRemainder() { return length - position; } } /** * Creates a new {@link GZIPCompressorInputStream} from an uncompressed {@link InputStream}. * * @param in The uncompressed {@link InputStream}. */ public GZIPCompressorInputStream(InputStream in) { super(new CheckedInputStream(in, new CRC32()), new Deflater(Deflater.DEFAULT_COMPRESSION, true)); buffer = new Buffer(); } public int read(byte b[], int off, int len) throws IOException { // Check if there are bytes left to be read from the internal buffer. This is used to provide the header // or trailer, and always takes precedence. int count; if (buffer.getByteCountRemainder() > 0) { // Write data from the internal buffer into b. count = Math.min(len, buffer.getByteCountRemainder()); System.arraycopy(buffer.data, buffer.position, b, off, count); // Advance the internal buffer position as "count" bytes have already been read. buffer.position += count; return count; } // Attempt to read compressed input data. count = super.read(b, off, len); if (count > 0) { return count; } /* * If the stream has reached completion, write out the GZIP trailer and re-attempt the read */ if (count <= 0 && !trailerWritten) { buffer.position = 0; buffer.length = writeTrailer(buffer.data, buffer.position); trailerWritten = true; return read(b, off, len); } else { return count; } } /** * Writes GZIP member trailer to a byte array, starting at a given offset. * * @param buf The buffer to write the trailer to. * @param offset The offset from which to start writing. * @return The amount of bytes that were written. * @throws IOException If an I/O error is produced. */ private int writeTrailer(byte[] buf, int offset) throws IOException { int count = writeInt((int) ((CheckedInputStream) this.in).getChecksum().getValue(), buf, offset); // CRC-32 of uncompr. data count += writeInt(def.getTotalIn(), buf, offset + 4); // Number of uncompr. bytes return count; } /** * Writes integer in Intel byte order to a byte array, starting at a given offset. * * @param i The integer to write. * @param buf The buffer to write the integer to. * @param offset The offset from which to start writing. * @return The amount of bytes written. * @throws IOException If an I/O error is produced. */ private int writeInt(int i, byte[] buf, int offset) throws IOException { int count = writeShort(i & 0xffff, buf, offset); count += writeShort((i >> 16) & 0xffff, buf, offset + 2); return count; } /** * Writes short integer in Intel byte order to a byte array, starting at a given offset. * * @param s The short to write. * @param buf The buffer to write the integer to. * @param offset The offset from which to start writing. * @return The amount of bytes written. * @throws IOException If an I/O error is produced. */ private int writeShort(int s, byte[] buf, int offset) throws IOException { buf[offset] = (byte) (s & 0xff); buf[offset + 1] = (byte) ((s >> 8) & 0xff); return 2; } }