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

com.senzing.io.ChunkedEncodingInputStream Maven / Gradle / Ivy

package com.senzing.io;

import java.io.*;
import java.nio.file.FileSystemNotFoundException;

/**
 * Provides a {@link FilterInputStream} implementation that will wrap an
 * {@link InputStream} that provides data encoded with chunked transfer encoding
 * and then decode the chunked transfer encoding.
 */
public class ChunkedEncodingInputStream extends FilterInputStream {
  /**
   * The current chunk.
   */
  private ByteArrayInputStream currentChunk = null;

  /**
   * Whether or not we have hit EOF.
   */
  private boolean eof = false;

  /**
   * Constructs with the specified {@link InputStream}.
   *
   * @param inputStream The {@link InputStream} to wrap.
   */
  public ChunkedEncodingInputStream(InputStream inputStream) {
    super(inputStream);
  }

  /**
   * Overridden to return the number of bytes on the current chunk and if the
   * next chunk is available and there is no current chunk to read the next
   * chunk and return how many bytes are available.
   */
  @Override
  public int available() throws IOException {
    // check if we do not have a current chunk
    if (this.currentChunk == null) {
      // check if the underlying input stream has a chunk available
      int available = this.in.available();
      if (available > 0) {
        // if a chunk is available, read it -- this should not block
        this.readChunk();
      } else {
        // if no chunk is available return zero (0)
        return 0;
      }
    }
    // if we read a chunk return how many available, otherwise zero (0)
    return this.currentChunk == null ? 0 : this.currentChunk.available();
  }

  /**
   * Closes the underlying {@link InputStream}.
   *
   * @throws IOException If a failure occurs.
   */
  @Override
  public void close() throws IOException  {
    this.in.close();
  }

  /**
   * Implemented to throw {@link UnsupportedOperationException} since mark
   * is not supported.
   *
   * @throws UnsupportedOperationException Whenever this method is called.
   */
  @Override
  public void mark(int readLimit) throws UnsupportedOperationException {
    throw new UnsupportedOperationException(
        "Mark is not supported when decoding chunked transfer encoding");
  }

  /**
   * Overridden to return false to indicate that mark and reset are
   * not supported.
   *
   * @return false to indicate that mark and reset are not supported.
   */
  @Override
  public boolean markSupported() {
    return false;
  }

  /**
   * Reads the next byte.
   *
   * @throws IOException If a failure occurs.
   */
  @Override
  public int read() throws IOException {
    if (this.currentChunk == null || this.currentChunk.available() == 0) {
      this.readChunk();
    }
    return (this.currentChunk == null) ? -1 : this.currentChunk.read();
  }

  /**
   * Reads the next bytes and populates the buffer.
   *
   * @throws IOException If a failure occurs.
   */
  @Override
  public int read(byte[] buffer, int offset, int length) throws IOException {
    if (this.currentChunk == null || this.currentChunk.available() == 0) {
      this.readChunk();
    }
    return (this.currentChunk == null)
        ? -1 : this.currentChunk.read(buffer, offset, length);
  }

  /**
   * Implemented to throw {@link UnsupportedOperationException} since mark
   * is not supported.
   *
   * @throws UnsupportedOperationException Whenever this method is called.
   */
  @Override
  public void reset() throws UnsupportedOperationException {
    throw new UnsupportedOperationException(
        "Reset is not supported when decoding chunked transfer encoding");
  }

  /**
   * Skips the specified number of bytes.
   *
   * @param n The number of bytes to skip.
   *
   * @throws IOException If a failure occurs.
   */
  @Override
  public long skip(long n) throws IOException {
    long skipped = 0;
    while (!this.eof && skipped < n) {
      if (this.currentChunk == null || this.currentChunk.available() == 0) {
        this.readChunk();
      }
      if (this.currentChunk != null) {
        skipped += this.currentChunk.skip(n - skipped);
      }
    }
    return skipped;
  }

  /**
   * Reads the next chunkm, discarding the current chunk if any.
   */
  private boolean readChunk() throws IOException {
    this.currentChunk = null;
    StringBuilder sb = new StringBuilder();
    boolean cr = false;
    for (int readByte = this.in.read();
         readByte >= 0;
         readByte = this.in.read())
    {
      // if we have a carriage return then look for a line-feed
      if (cr) {
        if (readByte != ((int) '\n')) {
          throw new IOException(
              "Bad chunked encoding.  Encountered CR without subsequent LF: "
              + sb.toString());
        }

        // we should have the chunk size so break here
        break;

      } else if (readByte == ((int) '\n')) {
        throw new IOException(
            "Bad chunked encoding.  Encountered LF without preceding CR: "
                + sb.toString());
      }

      // look for carriage return character
      if (readByte == ((int) '\r')) {
        cr = true;
        continue;
      }

      // append the character
      sb.append((char) readByte);
    }

    // check if we reached EOF before the last chunk
    if (!cr) {
      this.eof = true;
      throw new IOException(
          "Bad chunked encoding.  Unexpected EOF (" + sb.length() + "): " + sb);
    }

    // get the chunk line
    String chunkLine = sb.toString();
    int index = chunkLine.indexOf(';');
    String chunkSize = (index <= 0) ? chunkLine : chunkLine.substring(0, index);
    int readCount = Integer.parseInt(chunkSize, 16);

    // check if final chunk was received
    if (readCount == 0) {
      this.eof = true;
    }

    // get the chunk bytes
    byte[] chunkBytes = null;
    if (readCount > 0) {
      chunkBytes = this.in.readNBytes(readCount);

      if (chunkBytes.length != readCount) {
        this.eof = true;
        throw new IOException(
            "Bad chunked encoding.  Unexpected EOF while reading chunk of size "
                + readCount + " (only " + chunkBytes.length + " read): " + chunkLine);
      }
    }

    // read the next 2 bytes and assert that they are CRLF
    int trailerByte = this.in.read();
    if (trailerByte < 0) {
      throw new IOException(
          "Bad chunked encoding.  EOF prior to trailing CR following chunk: "
          + Integer.toString(readCount, 16).toUpperCase());
    }
    if (trailerByte != '\r') {
      throw new IOException(
          "Bad chunked encoding.  Expected trailing CR following chunk ("
          + Integer.toString(readCount, 16).toUpperCase()
              + "), but got code point: " + trailerByte);
    }
    trailerByte = this.in.read();
    if (trailerByte < 0) {
      throw new IOException(
          "Bad chunked encoding.  EOF prior to trailing LF following chunk: "
              + Integer.toString(readCount, 16).toUpperCase());
    }
    if (trailerByte != '\n') {
      throw new IOException(
          "Bad chunked encoding.  Expected trailing LF following chunk ("
              + Integer.toString(readCount, 16).toUpperCase()
              + "), but got code point: " + trailerByte);
    }

    // create the next chunk stream
    this.currentChunk = (chunkBytes == null)
        ? null : new ByteArrayInputStream(chunkBytes);
    return (this.currentChunk != null);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy