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

com.pdd.pop.ext.glassfish.grizzly.compression.zip.GZipDecoder Maven / Gradle / Ivy

There is a newer version: 1.10.85
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2012 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.pdd.pop.ext.glassfish.grizzly.compression.zip;

import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import com.pdd.pop.ext.glassfish.grizzly.AbstractTransformer;
import com.pdd.pop.ext.glassfish.grizzly.Buffer;
import com.pdd.pop.ext.glassfish.grizzly.TransformationException;
import com.pdd.pop.ext.glassfish.grizzly.TransformationResult;
import com.pdd.pop.ext.glassfish.grizzly.attributes.AttributeStorage;
import com.pdd.pop.ext.glassfish.grizzly.memory.Buffers;
import com.pdd.pop.ext.glassfish.grizzly.memory.ByteBufferArray;
import com.pdd.pop.ext.glassfish.grizzly.memory.MemoryManager;

/**
 * This class implements a {@link com.pdd.pop.ext.glassfish.grizzly.Transformer} which decodes data
 * represented in the GZIP format.
 *
 * @author Alexey Stashok
 */
public class GZipDecoder extends AbstractTransformer {
    protected enum DecodeStatus {
        INITIAL, FEXTRA1, FEXTRA2, FNAME, FCOMMENT, FHCRC, PAYLOAD, TRAILER, DONE
    }

    private static final int GZIP_MAGIC = 0x8b1f;

    /*
     * File header flags.
     */
    private final static int FTEXT	= 1;	// Extra text
    private final static int FHCRC	= 2;	// Header CRC
    private final static int FEXTRA	= 4;	// Extra field
    private final static int FNAME	= 8;	// File name
    private final static int FCOMMENT	= 16;	// File comment

    private final int bufferSize;

    public GZipDecoder() {
        this(512);
    }

    public GZipDecoder(int bufferSize) {
        this.bufferSize = bufferSize;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        return "gzip-decoder";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasInputRemaining(AttributeStorage storage, Buffer input) {
        return input.hasRemaining();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected GZipInputState createStateObject() {
        return new GZipInputState();
    }

    @Override
    protected TransformationResult transformImpl(
            AttributeStorage storage, Buffer input) throws TransformationException {
        final MemoryManager memoryManager = obtainMemoryManager(storage);

        final GZipInputState state = (GZipInputState) obtainStateObject(storage);

        if (!state.isInitialized()) {
            if (!initializeInput(input, state)) {
                return TransformationResult.createIncompletedResult(input);
            }
        }

        Buffer decodedBuffer = null;

        if (state.getDecodeStatus() == DecodeStatus.PAYLOAD) {
            if (input.hasRemaining()) {
                decodedBuffer = decodeBuffer(memoryManager, input, state);
            }
        }

        if (state.getDecodeStatus() == DecodeStatus.TRAILER && input.hasRemaining()) {
            if (decodeTrailer(input, state)) {
                state.setDecodeStatus(DecodeStatus.DONE);
                state.setInitialized(false);
            }
        }

        final boolean hasRemainder = input.hasRemaining();

        if (decodedBuffer == null || !decodedBuffer.hasRemaining()) {
            return TransformationResult.createIncompletedResult(hasRemainder ? input : null);
        }

        return TransformationResult.createCompletedResult(decodedBuffer,
                hasRemainder ? input : null);
    }

    private Buffer decodeBuffer(MemoryManager memoryManager, Buffer buffer,
            GZipInputState state) {

        final Inflater inflater = state.getInflater();
        final CRC32 inCrc32 = state.getCrc32();

        final ByteBufferArray byteBufferArray = buffer.toByteBufferArray();
        final ByteBuffer[] byteBuffers = byteBufferArray.getArray();
        final int size = byteBufferArray.size();

        Buffer resultBuffer = null;

        for (int i = 0; i < size; i++) {
            final ByteBuffer byteBuffer = byteBuffers[i];
            final int len = byteBuffer.remaining();

            final byte[] array;
            final int offset;
            if (byteBuffer.hasArray()) {
                array = byteBuffer.array();
                offset = byteBuffer.arrayOffset() + byteBuffer.position();
            } else {
                // @TODO allocate byte array via MemoryUtils
                array = new byte[len];
                offset = 0;
                byteBuffer.get(array);
                byteBuffer.position(byteBuffer.position() - len);
            }

            inflater.setInput(array, offset, len);

            int lastInflated;
            do {
                final Buffer decodedBuffer = memoryManager.allocate(bufferSize);
                final ByteBuffer decodedBB = decodedBuffer.toByteBuffer();
                final byte[] decodedArray = decodedBB.array();
                final int decodedArrayOffs = decodedBB.arrayOffset() + decodedBB.position();

                try {
                    lastInflated = inflater.inflate(decodedArray, decodedArrayOffs, bufferSize);
                } catch (DataFormatException e) {
                    decodedBuffer.dispose();
                    String s = e.getMessage();
                    throw new IllegalStateException(s != null ? s : "Invalid ZLIB data format");
                }

                if (lastInflated > 0) {
                    inCrc32.update(decodedArray, decodedArrayOffs, lastInflated);
                    decodedBuffer.position(lastInflated);
                    decodedBuffer.trim();
                    resultBuffer = Buffers.appendBuffers(memoryManager,
                            resultBuffer, decodedBuffer);
                } else {
                    decodedBuffer.dispose();
                    if (inflater.finished() || inflater.needsDictionary()) {
                        final int remainder = inflater.getRemaining();

                        final int remaining = byteBuffer.remaining();

                        byteBufferArray.restore();
                        byteBufferArray.recycle();

                        buffer.position(
                                buffer.position() + remaining - remainder);

                        state.setDecodeStatus(DecodeStatus.TRAILER);
                        return resultBuffer;
                    }
                }
            } while (lastInflated > 0);

            final int remaining = byteBuffer.remaining();

            byteBufferArray.restore();
            byteBufferArray.recycle();

            buffer.position(buffer.position() + remaining);
        }

        return resultBuffer;
    }

    private boolean initializeInput(final Buffer buffer,
            final GZipInputState state) {

        Inflater inflater = state.getInflater();
        if (inflater == null) {
            inflater = new Inflater(true);
            final CRC32 crc32 = new CRC32();
            crc32.reset();
            state.setInflater(inflater);
            state.setCrc32(crc32);
        } else if (state.getDecodeStatus() == DecodeStatus.DONE) {
            state.setDecodeStatus(DecodeStatus.INITIAL);
            inflater.reset();
            state.getCrc32().reset();
        }
        if (!parseHeader(buffer, state)) {
            return false;
        }

        state.getCrc32().reset();
        state.setInitialized(true);

        return true;
    }

    /*
     * Reads GZIP member header.
     */
    private boolean parseHeader(Buffer buffer, GZipInputState state) {

        final CRC32 crc32 = state.getCrc32();

        DecodeStatus decodeStatus;
        while((decodeStatus = state.getDecodeStatus()) != DecodeStatus.PAYLOAD) {

            switch (decodeStatus) {
                case INITIAL: {
                    if (buffer.remaining() < 10) {
                        return false;
                    }

                    // Check header magic
                    if (getUShort(buffer, crc32) != GZIP_MAGIC) {
                        throw new IllegalStateException("Not in GZIP format");
                    }

                    // Check compression method
                    if (getUByte(buffer, crc32) != 8) {
                        throw new IllegalStateException("Unsupported compression method");
                    }
                    // Read flags
                    final int flg = getUByte(buffer, crc32);
                    state.setHeaderFlag(flg);

                    // Skip MTIME, XFL, and OS fields
                    skipBytes(buffer, 6, crc32);

                    state.setDecodeStatus(DecodeStatus.FEXTRA1);
                }

        	// Skip optional extra field
                case FEXTRA1: {
                    if ((state.getHeaderFlag() & FEXTRA) != FEXTRA) {
                        state.setDecodeStatus(DecodeStatus.FNAME);
                        break;
                    }

                    if (buffer.remaining() < 2) {
                        return false;
                    }

                    state.setHeaderParseStateValue(getUShort(buffer, crc32));
                    state.setDecodeStatus(DecodeStatus.FEXTRA2);
                }

                case FEXTRA2: {
                    final int fextraSize = state.getHeaderParseStateValue();
                    if (buffer.remaining() < fextraSize) {
                        return false;
                    }

                    skipBytes(buffer, fextraSize, crc32);
                    state.setHeaderParseStateValue(0);
                    state.setDecodeStatus(DecodeStatus.FNAME);
                }

        	// Skip optional file name
                case FNAME: {
                    if ((state.getHeaderFlag() & FNAME) == FNAME) {
                        boolean found = false;
                        while (buffer.hasRemaining()) {
                            if (getUByte(buffer, crc32) == 0) {
                                found = true;
                                break;
                            }
                        }

                        if (!found) return false;
                    }

                    state.setDecodeStatus(DecodeStatus.FCOMMENT);
                }

        	// Skip optional file comment
                case FCOMMENT: {
                    if ((state.getHeaderFlag() & FCOMMENT) == FCOMMENT) {
                        boolean found = false;
                        while (buffer.hasRemaining()) {
                            if (getUByte(buffer, crc32) == 0) {
                                found = true;
                                break;
                            }
                        }

                        if (!found) return false;
                    }

                    state.setDecodeStatus(DecodeStatus.FHCRC);
                }

        	// Check optional header CRC
                case FHCRC: {
                    if ((state.getHeaderFlag() & FHCRC) == FHCRC) {
                        if (buffer.remaining() < 2) {
                            return false;
                        }

                        final int myCrc = (int) state.getCrc32().getValue() & 0xffff;
                        final int passedCrc = getUShort(buffer, crc32);

                        if (myCrc != passedCrc) {
                            throw new IllegalStateException("Corrupt GZIP header");
                        }
                    }

                    state.setDecodeStatus(DecodeStatus.PAYLOAD);
                }
            }
        }

        return true;
    }

    /*
     * Reads GZIP member trailer.
     */
    private boolean decodeTrailer(Buffer buffer, GZipInputState state)
            throws TransformationException {

        if (buffer.remaining() < 8) {
            return false;
        }

        final Inflater inflater = state.getInflater();
        final CRC32 crc32 = state.getCrc32();
	// Uses left-to-right evaluation order
        final long inCrc32Value = crc32.getValue();
	if ((getUInt(buffer, crc32) != inCrc32Value) ||
	    // rfc1952; ISIZE is the input size modulo 2^32
	    (getUInt(buffer, crc32) != (inflater.getBytesWritten() & 0xffffffffL)))
	    throw new TransformationException("Corrupt GZIP trailer");

        return true;
    }
    
    private static long getUInt(Buffer buffer, CRC32 crc32) {
        final int short1 = getUShort(buffer, crc32);
        final int short2 = getUShort(buffer, crc32);
        return (((long) short2 << 16) | short1);
    }

    private static int getUShort(Buffer buffer, CRC32 crc32) {
        final int b1 = getUByte(buffer, crc32);
        final int b2 = getUByte(buffer, crc32);

        return (b2 << 8) | b1;
    }

    private static int getUByte(Buffer buffer, CRC32 crc32) {
	final byte b = buffer.get();
        crc32.update(b);

	return b & 0xff;
    }

    private static void skipBytes(Buffer buffer, int num, CRC32 crc32) {
        for (int i=0; i {
        private boolean isInitialized;

        /**
         * CRC-32 of uncompressed data.
         */
        private CRC32 crc32;

        /**
         * Decompressor for this stream.
         */
        private Inflater inflater;

        private DecodeStatus decodeStatus = DecodeStatus.INITIAL;

        private int headerFlag;

        private int headerParseStateValue;

        public boolean isInitialized() {
            return isInitialized;
        }

        public void setInitialized(boolean isInitialized) {
            this.isInitialized = isInitialized;
        }

        public Inflater getInflater() {
            return inflater;
        }

        public void setInflater(Inflater inflater) {
            this.inflater = inflater;
        }

        public CRC32 getCrc32() {
            return crc32;
        }

        public void setCrc32(CRC32 crc32) {
            this.crc32 = crc32;
        }

        public DecodeStatus getDecodeStatus() {
            return decodeStatus;
        }

        public void setDecodeStatus(DecodeStatus decodeStatus) {
            this.decodeStatus = decodeStatus;
        }

        public int getHeaderFlag() {
            return headerFlag;
        }

        public void setHeaderFlag(int headerFlag) {
            this.headerFlag = headerFlag;
        }

        public int getHeaderParseStateValue() {
            return headerParseStateValue;
        }

        public void setHeaderParseStateValue(int headerParseStateValue) {
            this.headerParseStateValue = headerParseStateValue;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy