static int decodeHeaders(final Buffer requestContent, int offset, final AjpHttpRequest req) {
    // Decode headers
    final MimeHeaders headers = req.getHeaders();

    final int hCount = readShort(requestContent, offset);
    offset += 2;

    for (int i = 0; i < hCount; i++) {
      String hName;

      // Header names are encoded as either an integer code starting
      // with 0xA0, or as a normal string (in which case the first
      // two bytes are the length).
      int isc = readShort(requestContent, offset);
      int hId = isc & 0xFF;

      DataChunk valueDC;
      isc &= 0xFF00;
      if (0xA000 == isc) {
        offset += 2;
        hName = AjpConstants.headerTransArray[hId - 1];
        valueDC = headers.addValue(hName);
      } else {
        // reset hId -- if the header currently being read
        // happens to be 7 or 8 bytes long, the code below
        // will think it's the content-type header or the
        // content-length header - SC_REQ_CONTENT_TYPE=7,
        // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
        // behaviour.  see bug 5861 for more information.
        hId = -1;

        final int headerNameLen = readShort(requestContent, offset);
        offset += 2;
        valueDC = headers.addValue(requestContent, offset, headerNameLen);
        // Don't forget to skip the terminating \0 (that's why "+ 1")
        offset += headerNameLen + 1;
      }

      offset = getBytesToDataChunk(requestContent, offset, valueDC);

      // Get the last added header name (the one we need)
      final DataChunk headerNameDC = headers.getName(headers.size() - 1);

      if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH
          || (hId == -1 && headerNameDC.equalsIgnoreCase("Content-Length"))) {
        // just read the content-length header, so set it
        final long cl = Ascii.parseLong(valueDC);
        if (cl < Integer.MAX_VALUE) {
          req.setContentLength((int) cl);
        }
      } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE
          || (hId == -1 && headerNameDC.equalsIgnoreCase("Content-Type"))) {
        // just read the content-type header, so set it
        req.setContentType(valueDC.toString());
      }
    }

    return offset;
  }
  private static int setStringAttributeValue(
      final AjpHttpRequest req, final String key, final Buffer buffer, int offset) {

    final DataChunk tmpDataChunk = req.tmpDataChunk;

    offset = getBytesToDataChunk(buffer, offset, tmpDataChunk);
    final String value = tmpDataChunk.toString();

    tmpDataChunk.recycle();

    req.setAttribute(key, value);
    return offset;
  }
  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"));
  }
  private static int decodeAttributes(
      final Buffer requestContent,
      int offset,
      final AjpHttpRequest req,
      final boolean tomcatAuthentication) {

    final DataChunk tmpDataChunk = req.tmpDataChunk;

    boolean moreAttr = true;

    while (moreAttr) {
      final byte attributeCode = requestContent.get(offset++);
      if (attributeCode == AjpConstants.SC_A_ARE_DONE) {
        return offset;
      }

      /* Special case ( XXX in future API make it separate type !)
       */
      if (attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE) {
        // Bug 1326: it's an Integer.
        req.setAttribute(SSLSupport.KEY_SIZE_KEY, readShort(requestContent, offset));
        offset += 2;
      }

      if (attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE) {
        // 2 strings ???...
        offset = setStringAttribute(req, requestContent, offset);
      }

      // 1 string attributes
      switch (attributeCode) {
        case AjpConstants.SC_A_CONTEXT:
          // nothing
          offset = skipBytes(requestContent, offset);
          break;

        case AjpConstants.SC_A_REMOTE_USER:
          if (tomcatAuthentication) {
            // ignore server
            offset = skipBytes(requestContent, offset);
          } else {
            offset = getBytesToDataChunk(requestContent, offset, req.remoteUser());
          }
          break;

        case AjpConstants.SC_A_AUTH_TYPE:
          if (tomcatAuthentication) {
            // ignore server
            offset = skipBytes(requestContent, offset);
          } else {
            offset = getBytesToDataChunk(requestContent, offset, req.authType());
          }
          break;

        case AjpConstants.SC_A_QUERY_STRING:
          offset = getBytesToDataChunk(requestContent, offset, req.getQueryStringDC());
          break;

        case AjpConstants.SC_A_JVM_ROUTE:
          offset = getBytesToDataChunk(requestContent, offset, req.instanceId());
          break;

        case AjpConstants.SC_A_SSL_CERT:
          req.setSecure(true);
          // SSL certificate extraction is costy, initialize on demand
          offset = getBytesToDataChunk(requestContent, offset, req.sslCert());
          break;

        case AjpConstants.SC_A_SSL_CIPHER:
          req.setSecure(true);
          offset =
              setStringAttributeValue(req, SSLSupport.CIPHER_SUITE_KEY, requestContent, offset);
          break;

        case AjpConstants.SC_A_SSL_SESSION:
          req.setSecure(true);
          offset = setStringAttributeValue(req, SSLSupport.SESSION_ID_KEY, requestContent, offset);
          break;

        case AjpConstants.SC_A_SECRET:
          offset = getBytesToDataChunk(requestContent, offset, tmpDataChunk);

          req.setSecret(tmpDataChunk.toString());
          tmpDataChunk.recycle();

          break;

        case AjpConstants.SC_A_STORED_METHOD:
          offset = getBytesToDataChunk(requestContent, offset, req.getMethodDC());
          break;

        default:
          break; // ignore, we don't know about it - backward compat
      }
    }

    return offset;
  }