/** * Flushes this output stream, forcing any pending buffered output bytes to be written. * * @throws IOException if an I/O error occurs or this stream is already closed */ public void flush() throws IOException { ensureOpen(); // Finish decompressing and writing pending output data if (!inf.finished()) { try { while (!inf.finished() && !inf.needsInput()) { int n; // Decompress pending output data n = inf.inflate(buf, 0, buf.length); if (n < 1) { break; } // Write the uncompressed output data block out.write(buf, 0, n); } super.flush(); } catch (DataFormatException ex) { // Improperly formatted compressed (ZIP) data String msg = ex.getMessage(); if (msg == null) { msg = "Invalid ZLIB data format"; } throw new ZipException(msg); } } }
/** * Reads uncompressed data into an array of bytes. If <code>len</code> is not zero, the method * will block until some input can be decompressed; otherwise, no bytes are read and <code>0 * </code> is returned. * * @param b the buffer into which the data is read * @param off the start offset in the destination array <code>b</code> * @param len the maximum number of bytes read * @return the actual number of bytes read, or -1 if the end of the compressed input is reached or * a preset dictionary is needed * @exception NullPointerException If <code>b</code> is <code>null</code>. * @exception IndexOutOfBoundsException If <code>off</code> is negative, <code>len</code> is * negative, or <code>len</code> is greater than <code>b.length - off</code> * @exception ZipException if a ZIP format error has occurred * @exception IOException if an I/O error has occurred */ public int read(byte[] b, int off, int len) throws IOException { ensureOpen(); if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } try { int n; while ((n = inf.inflate(b, off, len)) == 0) { if (inf.finished() || inf.needsDictionary()) { reachEOF = true; return -1; } if (inf.needsInput()) { fill(); } } return n; } catch (DataFormatException e) { String s = e.getMessage(); throw new ZipException(s != null ? s : "Invalid ZLIB data format"); } }
@Override public TransformationResult getMoreData(byte opCode, boolean fin, int rsv, ByteBuffer dest) throws IOException { // Control frames are never compressed and may appear in the middle of // a WebSocket method. Pass them straight through. if (Util.isControl(opCode)) { return next.getMoreData(opCode, fin, rsv, dest); } if (!Util.isContinuation(opCode)) { // First frame in new message skipDecompression = (rsv & RSV_BITMASK) == 0; } // Pass uncompressed frames straight through. if (skipDecompression) { return next.getMoreData(opCode, fin, rsv, dest); } int written; boolean usedEomBytes = false; while (dest.remaining() > 0) { // Space available in destination. Try and fill it. try { written = inflater.inflate(dest.array(), dest.arrayOffset() + dest.position(), dest.remaining()); } catch (DataFormatException e) { throw new IOException(sm.getString("perMessageDeflate.deflateFailed"), e); } dest.position(dest.position() + written); if (inflater.needsInput() && !usedEomBytes) { if (dest.hasRemaining()) { readBuffer.clear(); TransformationResult nextResult = next.getMoreData(opCode, fin, (rsv ^ RSV_BITMASK), readBuffer); inflater.setInput(readBuffer.array(), readBuffer.arrayOffset(), readBuffer.position()); if (TransformationResult.UNDERFLOW.equals(nextResult)) { return nextResult; } else if (TransformationResult.END_OF_FRAME.equals(nextResult) && readBuffer.position() == 0) { if (fin) { inflater.setInput(EOM_BYTES); usedEomBytes = true; } else { return TransformationResult.END_OF_FRAME; } } } } else if (written == 0) { if (fin && (isServer && !clientContextTakeover || !isServer && !serverContextTakeover)) { inflater.reset(); } return TransformationResult.END_OF_FRAME; } } return TransformationResult.OVERFLOW; }
/** * @param compressed_size or use null if not known * @param uncompressed_size or use null if not known * @return uncompressed ByteArrayOutputStream * @throws IOException * @throws DataFormatException */ private ByteArrayOutputStream unCompress(Integer compressed_size, Integer uncompressed_size) throws IOException, DataFormatException { byte[] uncompressed_data = null; byte[] input_data = null; ByteArrayOutputStream ret = new ByteArrayOutputStream(); Inflater decompresser = new Inflater(false); long first_seek = fileChannel.position(); Boolean uncompressing = true; while (uncompressing) { if (decompresser.needsInput()) { input_data = new byte[(compressed_size != null) ? compressed_size.intValue() : 1024]; fileChannel.read(ByteBuffer.wrap(input_data)); decompresser.setInput(input_data, 0, input_data.length); } uncompressed_data = new byte [(uncompressed_size != null) ? uncompressed_size.intValue() : (input_data.length * 4)]; decompresser.inflate(uncompressed_data); int op = (int) (decompresser.getBytesWritten() - (long) ret.size()); if (op > 0) ret.write(uncompressed_data, 0, op); if (decompresser.finished()) uncompressing = false; } fileChannel.position( (first_seek + decompresser.getBytesRead())); // move file pointer to start of next stream decompresser.end(); return ret; }
/** * Writes an array of bytes to the uncompressed output stream. * * @param b buffer containing compressed data to decompress and write to the output stream * @param off starting offset of the compressed data within {@code b} * @param len number of bytes to decompress from {@code b} * @throws IndexOutOfBoundsException if {@code off < 0}, or if {@code len < 0}, or if {@code len > * b.length - off} * @throws IOException if an I/O error occurs or this stream is already closed * @throws NullPointerException if {@code b} is null * @throws ZipException if a compression (ZIP) format error occurs */ public void write(byte[] b, int off, int len) throws IOException { // Sanity checks ensureOpen(); if (b == null) { throw new NullPointerException("Null buffer for read"); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } // Write uncompressed data to the output stream try { for (; ; ) { int n; // Fill the decompressor buffer with output data if (inf.needsInput()) { int part; if (len < 1) { break; } part = (len < 512 ? len : 512); inf.setInput(b, off, part); off += part; len -= part; } // Decompress and write blocks of output data do { n = inf.inflate(buf, 0, buf.length); if (n > 0) { out.write(buf, 0, n); } } while (n > 0); // Check the decompressor if (inf.finished()) { break; } if (inf.needsDictionary()) { throw new ZipException("ZLIB dictionary missing"); } } } catch (DataFormatException ex) { // Improperly formatted compressed (ZIP) data String msg = ex.getMessage(); if (msg == null) { msg = "Invalid ZLIB data format"; } throw new ZipException(msg); } }
/** * Reads from the compressed stream and stores the resulting uncompressed data into the byte * array. * * @return number of bytes read, or -1 upon EOF */ public int read(byte[] b, int off, int len) throws IOException { if (len <= 0 || off < 0 || off + len > b.length) return 0; if (_eof) return -1; // Read from uncompressed stream if (!_isGzip) return _in.read(b, off, len); try { int sublen; int length = 0; while (length < len) { if (_inflater.needsInput()) { _readBufferSize = _in.read(_readBuffer, 0, _readBuffer.length); if (_readBufferSize < 0) break; _inflater.setInput(_readBuffer, 0, _readBufferSize); } sublen = _inflater.inflate(b, off + length, len - length); _crc.update(b, off + length, sublen); _inputSize += sublen; _totalInputSize += sublen; length += sublen; // Unread gzip trailer and possibly beginning of appended gzip data. if (_inflater.finished()) { int remaining = _inflater.getRemaining(); _in.unread(_readBuffer, _readBufferSize - remaining, remaining); readTrailer(); int secondPart = read(b, off + length, len - length); return secondPart > 0 ? length + secondPart : length; } } return length; } catch (DataFormatException e) { throw new IOException(e.getMessage()); } }
int inflate(Inflater inf, long pos, byte[] dstbuf, int dstoff) throws DataFormatException { int ptr = (int) (pos - start); int in = Math.min(INFLATE_STRIDE, block.length - ptr); if (dstoff < dstbuf.length) in = Math.min(in, dstbuf.length - dstoff); inf.setInput(block, ptr, in); for (; ; ) { int out = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff); if (out == 0) { if (inf.needsInput()) { ptr += in; in = Math.min(INFLATE_STRIDE, block.length - ptr); if (in == 0) return dstoff; inf.setInput(block, ptr, in); continue; } return dstoff; } dstoff += out; } }
/* * feed inflater more bytes in order to get some * decompressed output. * returns number of bytes actually got */ private int doRead(byte[] buf, int offset, int len) throws DataFormatException, IOException { int read = 0; int n = 0; byte[] inputBuffer = new byte[len]; while (len > 0) { n = inflater.inflate(buf, offset, len); if (n == 0) { if (inflater.finished()) { break; } else if (inflater.needsInput()) { // feeding int m = input.read(inputBuffer); if (m == -1) { // it shouldn't be here, throw exception throw new DataFormatException("Input is over while inflater still expecting data"); } else { // feed the data in inflater.setInput(inputBuffer, 0, m); n = inflater.inflate(buf, offset, len); if (n > 0) { read += n; offset += n; len -= n; } } } else { // it shouldn't be here, throw throw new DataFormatException("Inflater is neither finished nor needing input."); } } else { read += n; offset += n; len -= n; } } return read; }
@Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { try { ByteBufferList transformed = new ByteBufferList(); ByteBuffer output = ByteBuffer.allocate(bb.remaining() * 2); int totalInflated = 0; int totalRead = 0; while (bb.size() > 0) { ByteBuffer b = bb.remove(); if (b.hasRemaining()) { totalRead = +b.remaining(); mInflater.setInput(b.array(), b.arrayOffset() + b.position(), b.remaining()); do { int inflated = mInflater.inflate( output.array(), output.arrayOffset() + output.position(), output.remaining()); totalInflated += inflated; output.position(output.position() + inflated); if (!output.hasRemaining()) { output.limit(output.position()); output.position(0); transformed.add(output); Assert.assertNotSame(totalRead, 0); int newSize = output.capacity() * 2; output = ByteBuffer.allocate(newSize); } } while (!mInflater.needsInput() && !mInflater.finished()); } } output.limit(output.position()); output.position(0); transformed.add(output); Util.emitAllData(this, transformed); } catch (Exception ex) { report(ex); } }
public boolean needsInput() { return inflater.needsInput(); }
/** * {@inheritDoc} * * <p>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. * * <p>This method never returns null. * * <p>The given {@code buffer}'s position will be modified to reflect the bytes consumed during * the decoding. * * <p>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); } }
/** * Returns the non-null InputStream that should be returned to by all requests to {@link * #getContent()}. * * @return a non-null InputStream * @throws IOException if there was a problem */ @Override InputStream decorate(final InputStream wrapped) throws IOException { /* * A zlib stream will have a header. * * CMF | FLG [| DICTID ] | ...compressed data | ADLER32 | * * * CMF is one byte. * * * FLG is one byte. * * * DICTID is four bytes, and only present if FLG.FDICT is set. * * Sniff the content. Does it look like a zlib stream, with a CMF, etc? c.f. RFC1950, * section 2.2. http://tools.ietf.org/html/rfc1950#page-4 * * We need to see if it looks like a proper zlib stream, or whether it is just a deflate * stream. RFC2616 calls zlib streams deflate. Confusing, isn't it? That's why some servers * implement deflate Content-Encoding using deflate streams, rather than zlib streams. * * We could start looking at the bytes, but to be honest, someone else has already read * the RFCs and implemented that for us. So we'll just use the JDK libraries and exception * handling to do this. If that proves slow, then we could potentially change this to check * the first byte - does it look like a CMF? What about the second byte - does it look like * a FLG, etc. */ /* We read a small buffer to sniff the content. */ final byte[] peeked = new byte[6]; final PushbackInputStream pushback = new PushbackInputStream(wrapped, peeked.length); final int headerLength = pushback.read(peeked); if (headerLength == -1) { throw new IOException("Unable to read the response"); } /* We try to read the first uncompressed byte. */ final byte[] dummy = new byte[1]; final Inflater inf = new Inflater(); try { int n; while ((n = inf.inflate(dummy)) == 0) { if (inf.finished()) { /* Not expecting this, so fail loudly. */ throw new IOException("Unable to read the response"); } if (inf.needsDictionary()) { /* Need dictionary - then it must be zlib stream with DICTID part? */ break; } if (inf.needsInput()) { inf.setInput(peeked); } } if (n == -1) { throw new IOException("Unable to read the response"); } /* * We read something without a problem, so it's a valid zlib stream. Just need to reset * and return an unused InputStream now. */ pushback.unread(peeked, 0, headerLength); return new DeflateStream(pushback, new Inflater()); } catch (final DataFormatException e) { /* Presume that it's an RFC1951 deflate stream rather than RFC1950 zlib stream and try * again. */ pushback.unread(peeked, 0, headerLength); return new DeflateStream(pushback, new Inflater(true)); } finally { inf.end(); } }