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

org.eclipse.jetty.client.GZIPContentDecoder Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.client;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import java.util.zip.ZipException;

import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.Destroyable;

/**
 * {@link ContentDecoder} for the "gzip" encoding.
 */
public class GZIPContentDecoder implements ContentDecoder, Destroyable
{
    private final Inflater inflater = new Inflater(true);
    private final byte[] bytes;
    private byte[] output;
    private State state;
    private int size;
    private int value;
    private byte flags;

    public GZIPContentDecoder()
    {
        this(2048);
    }

    public GZIPContentDecoder(int bufferSize)
    {
        this.bytes = new byte[bufferSize];
        reset();
    }

    /**
     * {@inheritDoc}
     * 

If the decoding did not produce any output, for example because it consumed gzip header * or trailer bytes, it returns a buffer with zero capacity.

*

This method never returns null.

*

The given {@code buffer}'s position will be modified to reflect the bytes consumed during * the decoding.

*

The decoding may be finished without consuming the buffer completely if the buffer contains * gzip bytes plus other bytes (either plain or gzipped).

*/ @Override public ByteBuffer decode(ByteBuffer buffer) { try { while (buffer.hasRemaining()) { byte currByte = buffer.get(); switch (state) { case INITIAL: { buffer.position(buffer.position() - 1); state = State.ID; break; } case ID: { value += (currByte & 0xFF) << 8 * size; ++size; if (size == 2) { if (value != 0x8B1F) throw new ZipException("Invalid gzip bytes"); state = State.CM; } break; } case CM: { if ((currByte & 0xFF) != 0x08) throw new ZipException("Invalid gzip compression method"); state = State.FLG; break; } case FLG: { flags = currByte; state = State.MTIME; size = 0; value = 0; break; } case MTIME: { // Skip the 4 MTIME bytes ++size; if (size == 4) state = State.XFL; break; } case XFL: { // Skip XFL state = State.OS; break; } case OS: { // Skip OS state = State.FLAGS; break; } case FLAGS: { buffer.position(buffer.position() - 1); if ((flags & 0x04) == 0x04) { state = State.EXTRA_LENGTH; size = 0; value = 0; } else if ((flags & 0x08) == 0x08) state = State.NAME; else if ((flags & 0x10) == 0x10) state = State.COMMENT; else if ((flags & 0x2) == 0x2) { state = State.HCRC; size = 0; value = 0; } else state = State.DATA; break; } case EXTRA_LENGTH: { value += (currByte & 0xFF) << 8 * size; ++size; if (size == 2) state = State.EXTRA; break; } case EXTRA: { // Skip EXTRA bytes --value; if (value == 0) { // Clear the EXTRA flag and loop on the flags flags &= ~0x04; state = State.FLAGS; } break; } case NAME: { // Skip NAME bytes if (currByte == 0) { // Clear the NAME flag and loop on the flags flags &= ~0x08; state = State.FLAGS; } break; } case COMMENT: { // Skip COMMENT bytes if (currByte == 0) { // Clear the COMMENT flag and loop on the flags flags &= ~0x10; state = State.FLAGS; } break; } case HCRC: { // Skip HCRC ++size; if (size == 2) { // Clear the HCRC flag and loop on the flags flags &= ~0x02; state = State.FLAGS; } break; } case DATA: { buffer.position(buffer.position() - 1); while (true) { int decoded = inflate(bytes); if (decoded == 0) { if (inflater.needsInput()) { if (buffer.hasRemaining()) { byte[] input = new byte[buffer.remaining()]; buffer.get(input); inflater.setInput(input); } else { if (output != null) { ByteBuffer result = ByteBuffer.wrap(output); output = null; return result; } break; } } else if (inflater.finished()) { int remaining = inflater.getRemaining(); buffer.position(buffer.limit() - remaining); state = State.CRC; size = 0; value = 0; break; } else { throw new ZipException("Invalid inflater state"); } } else { if (output == null) { // Save the inflated bytes and loop to see if we have finished output = Arrays.copyOf(bytes, decoded); } else { // Accumulate inflated bytes and loop to see if we have finished byte[] newOutput = Arrays.copyOf(output, output.length + decoded); System.arraycopy(bytes, 0, newOutput, output.length, decoded); output = newOutput; } } } break; } case CRC: { value += (currByte & 0xFF) << 8 * size; ++size; if (size == 4) { // From RFC 1952, compliant decoders need not to verify the CRC state = State.ISIZE; size = 0; value = 0; } break; } case ISIZE: { value += (currByte & 0xFF) << 8 * size; ++size; if (size == 4) { if (value != inflater.getBytesWritten()) throw new ZipException("Invalid input size"); ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output); reset(); return result; } break; } default: throw new ZipException(); } } return BufferUtil.EMPTY_BUFFER; } catch (ZipException x) { throw new RuntimeException(x); } } private int inflate(byte[] bytes) throws ZipException { try { return inflater.inflate(bytes); } catch (DataFormatException x) { throw new ZipException(x.getMessage()); } } private void reset() { inflater.reset(); Arrays.fill(bytes, (byte)0); output = null; state = State.INITIAL; size = 0; value = 0; flags = 0; } @Override public void destroy() { inflater.end(); } protected boolean isFinished() { return state == State.INITIAL; } /** * Specialized {@link ContentDecoder.Factory} for the "gzip" encoding. */ public static class Factory extends ContentDecoder.Factory { private final int bufferSize; public Factory() { this(2048); } public Factory(int bufferSize) { super("gzip"); this.bufferSize = bufferSize; } @Override public ContentDecoder newContentDecoder() { return new GZIPContentDecoder(bufferSize); } } private enum State { INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy