private CompositeBuffer toCompositeInputContentBuffer() {
    if (!inputContentBuffer.isComposite()) {
      final CompositeBuffer compositeBuffer =
          CompositeBuffer.newBuffer(connection.getMemoryManager());

      compositeBuffer.allowBufferDispose(true);
      compositeBuffer.allowInternalBuffersDispose(true);

      int posAlign = 0;

      if (readAheadLimit > 0) { // the simple inputContentBuffer is marked
        // make the marked data still available
        inputContentBuffer.position(inputContentBuffer.position() - readCount);
        posAlign = readCount;

        markPos = 0; // for the CompositeBuffer markPos is 0
      }

      compositeBuffer.append(inputContentBuffer);
      compositeBuffer.position(posAlign);

      inputContentBuffer = compositeBuffer;
    }

    return (CompositeBuffer) inputContentBuffer;
  }
 /** {@inheritDoc} */
 @Override
 public byte[] getResponseBodyAsBytes() throws IOException {
   final Buffer responseBody = getResponseBody0();
   final byte[] responseBodyBytes = new byte[responseBody.remaining()];
   final int origPos = responseBody.position();
   responseBody.get(responseBodyBytes);
   responseBody.position(origPos);
   return responseBodyBytes;
 }
 /** {@inheritDoc} */
 public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException {
   charset = calculateCharset(charset);
   final Buffer responseBody = getResponseBody0();
   final int len = Math.min(responseBody.remaining(), maxLength);
   final int pos = responseBody.position();
   return responseBody.toStringContent(getCharset(charset), pos, len + pos);
 }
  /**
   * Supported with binary and character data.
   *
   * @see java.io.InputStream#mark(int)
   * @see java.io.Reader#mark(int)
   */
  public void mark(final int readAheadLimit) {

    if (readAheadLimit > 0) {
      markPos = inputContentBuffer.position();
      readCount = 0;
      this.readAheadLimit = readAheadLimit;
    }
  }
  /**
   * Skips the specified number of bytes/characters.
   *
   * @see java.io.InputStream#skip(long)
   * @see java.io.Reader#skip(long)
   */
  public long skip(final long n) throws IOException {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log("InputBuffer %s skip %s bytes. Ready content: %s", this, n, inputContentBuffer);
    }

    if (closed) {
      throw new IOException();
    }

    if (!processingChars) {
      if (n <= 0) {
        return 0L;
      }

      if (!inputContentBuffer.hasRemaining()) {
        if (fill((int) n) == -1) {
          return -1;
        }
      }
      if (inputContentBuffer.remaining() < n) {
        fill((int) n);
      }

      long nlen = Math.min(inputContentBuffer.remaining(), n);
      inputContentBuffer.position(inputContentBuffer.position() + (int) nlen);

      if (!checkMarkAfterRead(n)) {
        inputContentBuffer.shrink();
      }

      return nlen;
    } else {
      if (n < 0) { // required by java.io.Reader.skip()
        throw new IllegalArgumentException();
      }
      if (n == 0) {
        return 0L;
      }
      final CharBuffer skipBuffer = CharBuffer.allocate((int) n);
      if (fillChars((int) n, skipBuffer) == -1) {
        return 0;
      }
      return Math.min(skipBuffer.remaining(), n);
    }
  }
  /**
   * Used to convert pre-read (buffered) bytes to chars.
   *
   * @param requestedLen how much content should attempt to be read
   * @return the number of chars actually read
   */
  private int fillAvailableChars(final int requestedLen, final CharBuffer dst) {

    final CharsetDecoder decoderLocal = getDecoder();
    final ByteBuffer bb = inputContentBuffer.toByteBuffer();
    final int oldBBPos = bb.position();

    int producedChars = 0;
    int consumedBytes = 0;

    int producedCharsNow;
    int consumedBytesNow;
    CoderResult result;

    int remaining = requestedLen;

    do {
      final int charPos = dst.position();
      final int bbPos = bb.position();
      result = decoderLocal.decode(bb, dst, false);

      producedCharsNow = dst.position() - charPos;
      consumedBytesNow = bb.position() - bbPos;

      producedChars += producedCharsNow;
      consumedBytes += consumedBytesNow;

      remaining -= producedCharsNow;

    } while (remaining > 0
        && (producedCharsNow > 0 || consumedBytesNow > 0)
        && bb.hasRemaining()
        && result == CoderResult.UNDERFLOW);

    bb.position(oldBBPos);
    inputContentBuffer.position(inputContentBuffer.position() + consumedBytes);

    if (readAheadLimit == -1) {
      inputContentBuffer.shrink();
    }

    return producedChars;
  }
  @Override
  public void process(Buffer source, HeaderFieldTable.DecTable table, HeaderListener handler) {
    ObjectHolder<String> s = new ObjectHolder<>();
    String name;
    int beginning = source.position();
    byte b = source.get();
    if ((b & 0b111111) == 0) {
      readString(source, s);
      name = s.getObj();
    } else {
      source.position(beginning);
      int index = readInteger(source, 6);
      HeaderField e = table.get(index);
      name = e.getName();
    }
    readString(source, s);
    String value = s.getObj();
    HeaderField f = new HeaderField(name, value);
    table.put(f);

    handler.onDecodedHeader(name, value);
  }
  /**
   * Only supported with binary data.
   *
   * @see java.io.InputStream#reset()
   */
  public void reset() throws IOException {

    if (closed) {
      throw new IOException();
    }

    if (readAheadLimit == -1) {
      throw new IOException("Mark not set");
    }

    readCount = 0;
    inputContentBuffer.position(markPos);
  }
  public Result find(PUContext puc, FilterChainContext fcc) {
    final Buffer buffer = fcc.getMessage();
    if (buffer.remaining() >= signature.length) {
      final int start = buffer.position();

      for (int i = 0; i < signature.length; i++) {
        if (buffer.get(start + i) != signature[i]) {
          return Result.NOT_FOUND;
        }
      }

      return Result.FOUND;
    }

    return Result.NEED_MORE_DATA;
  }
        @Override
        public void notifyDirectUpdate() {
          if (type == Type.Buffer) {
            final int start = getStart();
            final int end = getEnd();

            final byte[] bytes = new byte[end - start];

            final Buffer currentBuffer = getBufferChunk().getBuffer();
            final int pos = currentBuffer.position();
            final int lim = currentBuffer.limit();

            Buffers.setPositionLimit(currentBuffer, start, end);
            currentBuffer.get(bytes);
            Buffers.setPositionLimit(currentBuffer, pos, lim);

            setBytes(bytes);
          }
        }
  static void decodeRequest(
      final Buffer requestContent, final AjpHttpRequest req, final boolean tomcatAuthentication)
      throws IOException {
    // FORWARD_REQUEST handler

    int offset = requestContent.position();

    // Translate the HTTP method code to a String.
    byte methodCode = requestContent.get(offset++);
    if (methodCode != AjpConstants.SC_M_JK_STORED) {
      String mName = AjpConstants.methodTransArray[(int) methodCode - 1];
      req.getMethodDC().setString(mName);
    }

    offset = getBytesToDataChunk(requestContent, offset, req.getProtocolDC());
    final int requestURILen = readShort(requestContent, offset);
    if (!isNullLength(requestURILen)) {
      req.getRequestURIRef().init(requestContent, offset + 2, offset + 2 + requestURILen);
    }
    // Don't forget to skip the terminating \0 (that's why "+ 1")
    offset += 2 + requestURILen + 1;

    offset = getBytesToDataChunk(requestContent, offset, req.remoteAddr());

    offset = getBytesToDataChunk(requestContent, offset, req.remoteHostRaw());

    offset = getBytesToDataChunk(requestContent, offset, req.localName());

    req.setLocalPort(readShort(requestContent, offset));
    offset += 2;

    final boolean isSSL = requestContent.get(offset++) != 0;
    req.setSecure(isSSL);
    req.getResponse().setSecure(isSSL);

    offset = decodeHeaders(requestContent, offset, req);

    decodeAttributes(requestContent, offset, req, tomcatAuthentication);

    req.setUnparsedHostHeader(req.getHeaders().getValue("host"));
  }
  public static Buffer appendContentAndTrim(
      final MemoryManager memoryManager, Buffer dstBuffer, Buffer httpContentBuffer) {
    Buffer resultBuffer = null;
    do {
      Buffer contentRemainder = null;
      if (httpContentBuffer.remaining() > MAX_BODY_CHUNK_CONTENT_SIZE) {
        contentRemainder =
            httpContentBuffer.split(httpContentBuffer.position() + MAX_BODY_CHUNK_CONTENT_SIZE);
      }

      final Buffer encodedContentChunk =
          appendContentChunkAndTrim(memoryManager, dstBuffer, httpContentBuffer);
      resultBuffer = Buffers.appendBuffers(memoryManager, resultBuffer, encodedContentChunk);

      // dstBuffer use only once, when it comes from caller
      dstBuffer = null;
      httpContentBuffer = contentRemainder;
    } while (httpContentBuffer != null && httpContentBuffer.hasRemaining());

    return resultBuffer;
  }
  /**
   * @param size the requested size of the {@link Buffer} to be returned.
   * @return the {@link Buffer} of a given size, which represents a chunk of the underlying {@link
   *     Buffer} which contains incoming request data. This method detaches the returned {@link
   *     Buffer}, so user code becomes responsible for handling its life-cycle.
   */
  public Buffer readBuffer(final int size) {
    if (LOGGER.isLoggable(LOGGER_LEVEL)) {
      log(
          "InputBuffer %s readBuffer(size), size: %s. Ready content: %s",
          this, size, inputContentBuffer);
    }

    final int remaining = inputContentBuffer.remaining();
    if (size > remaining) {
      throw new IllegalStateException("Can not read more bytes than available");
    }

    final Buffer buffer;
    if (size == remaining) {
      buffer = inputContentBuffer;
      inputContentBuffer = Buffers.EMPTY_BUFFER;
    } else {
      final Buffer tmpBuffer = inputContentBuffer.split(inputContentBuffer.position() + size);
      buffer = inputContentBuffer;
      inputContentBuffer = tmpBuffer;
    }

    return buffer;
  }
 public void setReasonPhrase(final Buffer reason) {
   reasonPhraseC.setBuffer(reason, reason.position(), reason.limit());
 }
  public static Buffer encodeHeaders(
      final MemoryManager mm, final HttpResponsePacket httpResponsePacket) {
    Buffer encodedBuffer = mm.allocate(4096);
    int startPos = encodedBuffer.position();
    // Skip 4 bytes for the Ajp header
    encodedBuffer.position(startPos + 4);

    encodedBuffer.put(AjpConstants.JK_AJP13_SEND_HEADERS);
    encodedBuffer.putShort((short) httpResponsePacket.getStatus());
    final byte[] tempBuffer = httpResponsePacket.getTempHeaderEncodingBuffer();
    if (httpResponsePacket.isCustomReasonPhraseSet()) {
      encodedBuffer =
          putBytes(
              mm,
              encodedBuffer,
              HttpStatus.filter(httpResponsePacket.getReasonPhraseDC()),
              tempBuffer);
    } else {
      encodedBuffer =
          putBytes(mm, encodedBuffer, httpResponsePacket.getHttpStatus().getReasonPhraseBytes());
    }

    if (httpResponsePacket.isAcknowledgement()) {
      // If it's acknoledgment packet - don't encode the headers
      // Serialize 0 num_headers
      encodedBuffer = putShort(mm, encodedBuffer, 0);
    } else {
      final MimeHeaders headers = httpResponsePacket.getHeaders();
      final String contentType = httpResponsePacket.getContentType();
      if (contentType != null) {
        headers.setValue("Content-Type").setString(contentType);
      }
      final String contentLanguage = httpResponsePacket.getContentLanguage();
      if (contentLanguage != null) {
        headers.setValue("Content-Language").setString(contentLanguage);
      }
      final long contentLength = httpResponsePacket.getContentLength();
      if (contentLength >= 0) {
        final Buffer contentLengthBuffer = getLongAsBuffer(mm, contentLength);
        headers
            .setValue("Content-Length")
            .setBuffer(
                contentLengthBuffer, contentLengthBuffer.position(), contentLengthBuffer.limit());
      }

      final int numHeaders = headers.size();

      encodedBuffer = putShort(mm, encodedBuffer, numHeaders);

      for (int i = 0; i < numHeaders; i++) {
        final DataChunk headerName = headers.getName(i);
        encodedBuffer = putBytes(mm, encodedBuffer, headerName, tempBuffer);

        final DataChunk headerValue = headers.getValue(i);
        encodedBuffer = putBytes(mm, encodedBuffer, headerValue, tempBuffer);
      }
    }

    // Add Ajp message header
    encodedBuffer.put(startPos, (byte) 'A');
    encodedBuffer.put(startPos + 1, (byte) 'B');
    encodedBuffer.putShort(startPos + 2, (short) (encodedBuffer.position() - startPos - 4));

    return encodedBuffer;
  }
  /**
   * Used to convert bytes to chars.
   *
   * @param requestedLen how much content should attempt to be read
   * @return the number of chars actually read
   * @throws IOException if an I/O error occurs while reading content
   */
  private int fillChars(final int requestedLen, final CharBuffer dst) throws IOException {

    int read = 0;

    // 1) Check pre-decoded singleCharBuf
    if (dst != singleCharBuf && singleCharBuf.hasRemaining()) {
      dst.put(singleCharBuf.get());
      read = 1;
    }

    // 2) Decode available byte[] -> char[]
    if (inputContentBuffer.hasRemaining()) {
      read += fillAvailableChars(requestedLen - read, dst);
    }

    if (read >= requestedLen) {
      dst.flip();
      return read;
    }

    // 3) If we don't expect more data - return what we've read so far
    if (!httpHeader.isExpectContent()) {
      dst.flip();
      return read > 0 ? read : -1;
    }

    // 4) Try to read more data (we may block)
    CharsetDecoder decoderLocal = getDecoder();

    boolean isNeedMoreInput =
        false; // true, if content in composite buffer is not enough to produce even 1 char
    boolean last = false;

    while (read < requestedLen && httpHeader.isExpectContent()) {

      if (isNeedMoreInput || !inputContentBuffer.hasRemaining()) {
        final HttpContent c = blockingRead();
        updateInputContentBuffer(c.getContent());
        last = c.isLast();

        c.recycle();
        isNeedMoreInput = false;
      }

      final ByteBuffer bytes = inputContentBuffer.toByteBuffer();

      final int bytesPos = bytes.position();
      final int dstPos = dst.position();

      final CoderResult result = decoderLocal.decode(bytes, dst, false);

      final int producedChars = dst.position() - dstPos;
      final int consumedBytes = bytes.position() - bytesPos;

      read += producedChars;

      if (consumedBytes > 0) {
        bytes.position(bytesPos);
        inputContentBuffer.position(inputContentBuffer.position() + consumedBytes);
        if (readAheadLimit == -1) {
          inputContentBuffer.shrink();
        }
      } else {
        isNeedMoreInput = true;
      }

      if (last || result == CoderResult.OVERFLOW) {
        break;
      }
    }

    dst.flip();

    if (last && read == 0) {
      read = -1;
    }
    return read;
  }