/**
  * 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");
   }
 }
  /**
   * 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);
    }
  }
 @Override
 public void decode(ChannelBuffer decompressed) throws Exception {
   try {
     int numBytes = decompressor.inflate(out);
     if (numBytes == 0 && decompressor.needsDictionary()) {
       decompressor.setDictionary(SPDY_DICT);
       numBytes = decompressor.inflate(out);
     }
     decompressed.writeBytes(out, 0, numBytes);
   } catch (DataFormatException e) {
     throw new SpdyProtocolException("Received invalid header block", e);
   }
 }
  /**
   * 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();
    }
  }