in java/org/apache/coyote/ajp/AjpProcessor.java [635:860]
private void prepareRequest() {
// Translate the HTTP method code to a String.
byte methodCode = requestHeaderMessage.getByte();
if (methodCode != Constants.SC_M_JK_STORED) {
String methodName = Constants.getMethodForCode(methodCode - 1);
request.method().setString(methodName);
}
requestHeaderMessage.getBytes(request.protocol());
requestHeaderMessage.getBytes(request.requestURI());
requestHeaderMessage.getBytes(request.remoteAddr());
requestHeaderMessage.getBytes(request.remoteHost());
requestHeaderMessage.getBytes(request.localName());
request.setLocalPort(requestHeaderMessage.getInt());
if (socketWrapper != null) {
request.peerAddr().setString(socketWrapper.getRemoteAddr());
}
boolean isSSL = requestHeaderMessage.getByte() != 0;
if (isSSL) {
request.scheme().setString("https");
}
// Decode headers
MimeHeaders headers = request.getMimeHeaders();
// Set this every time in case limit has been changed via JMX
headers.setLimit(protocol.getMaxHeaderCount());
boolean contentLengthSet = false;
int hCount = requestHeaderMessage.getInt();
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 = requestHeaderMessage.peekInt();
int hId = isc & 0xFF;
MessageBytes vMB;
isc &= 0xFF00;
if (0xA000 == isc) {
requestHeaderMessage.getInt(); // To advance the read position
hName = Constants.getHeaderForCode(hId - 1);
vMB = 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;
requestHeaderMessage.getBytes(tmpMB);
ByteChunk bc = tmpMB.getByteChunk();
vMB = headers.addValue(bc.getBuffer(), bc.getStart(), bc.getLength());
}
requestHeaderMessage.getBytes(vMB);
if (hId == Constants.SC_REQ_CONTENT_LENGTH || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
long cl = vMB.getLong();
if (contentLengthSet) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
setErrorState(ErrorState.CLOSE_CLEAN, null);
} else {
contentLengthSet = true;
// Set the content-length header for the request
request.setContentLength(cl);
}
} else if (hId == Constants.SC_REQ_CONTENT_TYPE || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
// just read the content-type header, so set it
ByteChunk bchunk = vMB.getByteChunk();
request.contentType().setBytes(bchunk.getBytes(), bchunk.getStart(), bchunk.getLength());
}
}
// Decode extra attributes
String secret = protocol.getSecret();
boolean secretPresentInRequest = false;
byte attributeCode;
while ((attributeCode = requestHeaderMessage.getByte()) != Constants.SC_A_ARE_DONE) {
switch (attributeCode) {
case Constants.SC_A_REQ_ATTRIBUTE -> {
requestHeaderMessage.getBytes(tmpMB);
String n = tmpMB.toString();
requestHeaderMessage.getBytes(tmpMB);
String v = tmpMB.toString();
/*
* AJP13 misses to forward the local IP address and the remote port. Allow the AJP connector to add
* this info via private request attributes. We will accept the forwarded data and remove it from
* the public list of request attributes.
*/
if (n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
request.localAddr().setString(v);
} else if (n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
try {
request.setRemotePort(Integer.parseInt(v));
} catch (NumberFormatException nfe) {
// Ignore invalid value
}
} else if (n.equals(Constants.SC_A_SSL_PROTOCOL)) {
request.setAttribute(SSLSupport.SECURE_PROTOCOL_KEY, v);
} else if (n.equals("JK_LB_ACTIVATION")) {
request.setAttribute(n, v);
} else if (jakartaAttributeMapping.containsKey(n)) {
// AJP uses the Java Servlet attribute names.
// Need to convert these to Jakarta Servlet.
request.setAttribute(jakartaAttributeMapping.get(n), v);
} else if (iisTlsAttributes.contains(n)) {
// Allow IIS TLS attributes
request.setAttribute(n, v);
} else {
// All 'known' attributes will be processed by the previous
// blocks. Any remaining attribute is an 'arbitrary' one.
Pattern pattern = protocol.getAllowedRequestAttributesPatternInternal();
if (pattern != null && pattern.matcher(n).matches()) {
request.setAttribute(n, v);
} else {
log.warn(sm.getString("ajpprocessor.unknownAttribute", n));
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
}
}
case Constants.SC_A_CONTEXT -> requestHeaderMessage.getBytes(tmpMB);
// nothing
case Constants.SC_A_SERVLET_PATH -> requestHeaderMessage.getBytes(tmpMB);
// nothing
case Constants.SC_A_REMOTE_USER -> {
boolean tomcatAuthorization = protocol.getTomcatAuthorization();
if (tomcatAuthorization || !protocol.getTomcatAuthentication()) {
// Implies tomcatAuthentication == false
requestHeaderMessage.getBytes(request.getRemoteUser());
request.setRemoteUserNeedsAuthorization(tomcatAuthorization);
} else {
// Ignore user information from reverse proxy
requestHeaderMessage.getBytes(tmpMB);
}
}
case Constants.SC_A_AUTH_TYPE -> {
if (protocol.getTomcatAuthorization() || !protocol.getTomcatAuthentication()) {
// Implies tomcatAuthentication == false
requestHeaderMessage.getBytes(request.getAuthType());
} else {
// Ignore user information from reverse proxy
requestHeaderMessage.getBytes(tmpMB);
}
}
case Constants.SC_A_QUERY_STRING -> requestHeaderMessage.getBytes(request.queryString());
case Constants.SC_A_JVM_ROUTE -> requestHeaderMessage.getBytes(tmpMB);
// nothing
case Constants.SC_A_SSL_CERT ->
// SSL certificate extraction is lazy, moved to JkCoyoteHandler
requestHeaderMessage.getBytes(certificates);
case Constants.SC_A_SSL_CIPHER -> {
requestHeaderMessage.getBytes(tmpMB);
request.setAttribute(SSLSupport.CIPHER_SUITE_KEY, tmpMB.toString());
}
case Constants.SC_A_SSL_SESSION -> {
requestHeaderMessage.getBytes(tmpMB);
request.setAttribute(SSLSupport.SESSION_ID_KEY, tmpMB.toString());
}
case Constants.SC_A_SSL_KEY_SIZE ->
request.setAttribute(SSLSupport.KEY_SIZE_KEY, Integer.valueOf(requestHeaderMessage.getInt()));
case Constants.SC_A_STORED_METHOD -> requestHeaderMessage.getBytes(request.method());
case Constants.SC_A_SECRET -> {
requestHeaderMessage.getBytes(tmpMB);
if (secret != null && !secret.isEmpty()) {
secretPresentInRequest = true;
if (!tmpMB.equals(secret)) {
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
}
}
default -> {
}
// Ignore unknown attribute for backward compatibility
}
}
// Check if secret was submitted if required
if (secret != null && !secret.isEmpty() && !secretPresentInRequest) {
response.setStatus(403);
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
// Check for a full URI (including protocol://host:port/)
ByteChunk uriBC = request.requestURI().getByteChunk();
if (uriBC.startsWithIgnoreCase("http", 0)) {
int pos = uriBC.indexOf("://", 0, 3, 4);
int uriBCStart = uriBC.getStart();
if (pos != -1) {
byte[] uriB = uriBC.getBytes();
int slashPos = uriBC.indexOf('/', pos + 3);
if (slashPos == -1) {
slashPos = uriBC.getLength();
// Set URI as "/"
request.requestURI().setBytes(uriB, uriBCStart + pos + 1, 1);
} else {
request.requestURI().setBytes(uriB, uriBCStart + slashPos, uriBC.getLength() - slashPos);
}
MessageBytes hostMB = headers.setValue("host");
hostMB.setBytes(uriB, uriBCStart + pos + 3, slashPos - pos - 3);
}
}
MessageBytes valueMB = request.getMimeHeaders().getValue("host");
parseHost(valueMB);
if (!getErrorState().isIoAllowed()) {
getAdapter().log(request, response, 0);
}
}