From 1453b239f37aeeb2187b8ed4006986ca15784813 Mon Sep 17 00:00:00 2001 From: markt Date: Wed, 29 Sep 2010 15:21:53 +0000 Subject: [PATCH] Reduce code duplication in the APR connectors (prior to aligning with the Async refactoring) git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1002673 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/coyote/ajp/AbstractAjpProcessor.java | 781 +++++++++++++++++++++ java/org/apache/coyote/ajp/AjpAprProcessor.java | 762 +------------------- java/org/apache/coyote/ajp/AjpAprProtocol.java | 12 +- java/org/apache/coyote/ajp/AjpProcessor.java | 743 +------------------- java/org/apache/coyote/ajp/LocalStrings.properties | 2 + 5 files changed, 844 insertions(+), 1456 deletions(-) create mode 100644 java/org/apache/coyote/ajp/AbstractAjpProcessor.java diff --git a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java new file mode 100644 index 000000000..b1b45c5a2 --- /dev/null +++ b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java @@ -0,0 +1,781 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.coyote.ajp; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.apache.coyote.ActionCode; +import org.apache.coyote.ActionHook; +import org.apache.coyote.Adapter; +import org.apache.coyote.InputBuffer; +import org.apache.coyote.Request; +import org.apache.coyote.Response; +import org.apache.juli.logging.Log; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.HexUtils; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.HttpMessages; +import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.net.AbstractEndpoint; +import org.apache.tomcat.util.res.StringManager; + +/** + * Base class for AJP Processor implementations. + */ +public abstract class AbstractAjpProcessor implements ActionHook { + + protected abstract Log getLog(); + + /** + * The string manager for this package. + */ + protected static final StringManager sm = + StringManager.getManager(Constants.Package); + + // ----------------------------------------------------- Instance Variables + + + /** + * Async used + */ + protected boolean async = false; + + + /** + * Associated adapter. + */ + protected Adapter adapter = null; + + + /** + * Associated endpoint. + */ + protected AbstractEndpoint endpoint; + + + /** + * Request object. + */ + protected Request request = null; + + + /** + * Response object. + */ + protected Response response = null; + + + /** + * AJP packet size. + */ + protected int packetSize; + + /** + * Header message. Note that this header is merely the one used during the + * processing of the first message of a "request", so it might not be a + * request header. It will stay unchanged during the processing of the whole + * request. + */ + protected AjpMessage requestHeaderMessage = null; + + + /** + * Message used for response header composition. + */ + protected AjpMessage responseHeaderMessage = null; + + + /** + * Body message. + */ + protected AjpMessage bodyMessage = null; + + + /** + * Body message. + */ + protected MessageBytes bodyBytes = MessageBytes.newInstance(); + + + /** + * Error flag. + */ + protected boolean error = false; + + + /** + * Host name (used to avoid useless B2C conversion on the host name). + */ + protected char[] hostNameC = new char[0]; + + + /** + * Temp message bytes used for processing. + */ + protected MessageBytes tmpMB = MessageBytes.newInstance(); + + + /** + * Byte chunk for certs. + */ + protected MessageBytes certificates = MessageBytes.newInstance(); + + + /** + * End of stream flag. + */ + protected boolean endOfStream = false; + + + /** + * Body empty flag. + */ + protected boolean empty = true; + + + /** + * First read. + */ + protected boolean first = true; + + + /** + * Replay read. + */ + protected boolean replay = false; + + + /** + * Finished response. + */ + protected boolean finished = false; + + + // ------------------------------------------------------------- Properties + + + /** + * Use Tomcat authentication ? + */ + protected boolean tomcatAuthentication = true; + public boolean getTomcatAuthentication() { return tomcatAuthentication; } + public void setTomcatAuthentication(boolean tomcatAuthentication) { + this.tomcatAuthentication = tomcatAuthentication; + } + + + /** + * Required secret. + */ + protected String requiredSecret = null; + public void setRequiredSecret(String requiredSecret) { + this.requiredSecret = requiredSecret; + } + + + // --------------------------------------------------------- Public Methods + + + /** Get the request associated with this processor. + * + * @return The request + */ + public Request getRequest() { + return request; + } + + + /** + * Send an action to the connector. + * + * @param actionCode Type of the action + * @param param Action parameter + */ + @Override + public final void action(ActionCode actionCode, Object param) { + + if (actionCode == ActionCode.COMMIT) { + + if (response.isCommitted()) + return; + + // Validate and write response headers + try { + prepareResponse(); + } catch (IOException e) { + // Set error flag + error = true; + } + + } else if (actionCode == ActionCode.CLIENT_FLUSH) { + + if (!response.isCommitted()) { + // Validate and write response headers + try { + prepareResponse(); + } catch (IOException e) { + // Set error flag + error = true; + return; + } + } + + try { + flush(); + } catch (IOException e) { + // Set error flag + error = true; + } + + } else if (actionCode == ActionCode.CLOSE) { + // Close + async = false; + // End the processing of the current request, and stop any further + // transactions with the client + + try { + finish(); + } catch (IOException e) { + // Set error flag + error = true; + } + + } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) { + + if (!certificates.isNull()) { + ByteChunk certData = certificates.getByteChunk(); + X509Certificate jsseCerts[] = null; + ByteArrayInputStream bais = + new ByteArrayInputStream(certData.getBytes(), + certData.getStart(), + certData.getLength()); + // Fill the elements. + try { + CertificateFactory cf = + CertificateFactory.getInstance("X.509"); + while(bais.available() > 0) { + X509Certificate cert = (X509Certificate) + cf.generateCertificate(bais); + if(jsseCerts == null) { + jsseCerts = new X509Certificate[1]; + jsseCerts[0] = cert; + } else { + X509Certificate [] temp = new X509Certificate[jsseCerts.length+1]; + System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length); + temp[jsseCerts.length] = cert; + jsseCerts = temp; + } + } + } catch (java.security.cert.CertificateException e) { + getLog().error(sm.getString("ajpprocessor.certs.fail"), e); + return; + } + request.setAttribute(AbstractEndpoint.CERTIFICATE_KEY, jsseCerts); + } + + } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) { + + // Get remote host name using a DNS resolution + if (request.remoteHost().isNull()) { + try { + request.remoteHost().setString(InetAddress.getByName + (request.remoteAddr().toString()).getHostName()); + } catch (IOException iex) { + // Ignore + } + } + + } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) { + + // Copy from local name for now, which should simply be an address + request.localAddr().setString(request.localName().toString()); + + } else if (actionCode == ActionCode.REQ_SET_BODY_REPLAY) { + + // Set the given bytes as the content + ByteChunk bc = (ByteChunk) param; + int length = bc.getLength(); + bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length); + request.setContentLength(length); + first = false; + empty = false; + replay = true; + + } else { + actionInternal(actionCode, param); + } + } + + // Methods called by action() + protected abstract void actionInternal(ActionCode actionCode, Object param); + protected abstract void flush() throws IOException; + protected abstract void finish() throws IOException; + + + // ------------------------------------------------------ Connector Methods + + + /** + * Set the associated adapter. + * + * @param adapter the new adapter + */ + public void setAdapter(Adapter adapter) { + this.adapter = adapter; + } + + + /** + * Get the associated adapter. + * + * @return the associated adapter + */ + public Adapter getAdapter() { + return adapter; + } + + + // ------------------------------------------------------ Protected Methods + + + /** + * After reading the request headers, we have to setup the request filters. + */ + protected 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()); + + boolean isSSL = requestHeaderMessage.getByte() != 0; + if (isSSL) { + request.scheme().setString("https"); + } + + // Decode headers + MimeHeaders headers = request.getMimeHeaders(); + + int hCount = requestHeaderMessage.getInt(); + for(int i = 0 ; i < hCount ; i++) { + String hName = null; + + // 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 = null; + 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"))) { + // just read the content-length header, so set it + long cl = vMB.getLong(); + if(cl < Integer.MAX_VALUE) + request.setContentLength( (int)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.getOffset(), + bchunk.getLength()); + } + } + + // Decode extra attributes + boolean secret = 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 remotePort. + * Allow the AJP connector to add this info via + * a private request attribute. + * We will accept the forwarded data as the remote port, + * and remove it from the public list of request attributes. + */ + if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) { + try { + request.setRemotePort(Integer.parseInt(v)); + } catch (NumberFormatException nfe) { + // Ignore invalid value + } + } else { + request.setAttribute(n, v ); + } + break; + + case Constants.SC_A_CONTEXT : + requestHeaderMessage.getBytes(tmpMB); + // nothing + break; + + case Constants.SC_A_SERVLET_PATH : + requestHeaderMessage.getBytes(tmpMB); + // nothing + break; + + case Constants.SC_A_REMOTE_USER : + if (tomcatAuthentication) { + // ignore server + requestHeaderMessage.getBytes(tmpMB); + } else { + requestHeaderMessage.getBytes(request.getRemoteUser()); + } + break; + + case Constants.SC_A_AUTH_TYPE : + if (tomcatAuthentication) { + // ignore server + requestHeaderMessage.getBytes(tmpMB); + } else { + requestHeaderMessage.getBytes(request.getAuthType()); + } + break; + + case Constants.SC_A_QUERY_STRING : + requestHeaderMessage.getBytes(request.queryString()); + break; + + case Constants.SC_A_JVM_ROUTE : + requestHeaderMessage.getBytes(request.instanceId()); + break; + + case Constants.SC_A_SSL_CERT : + request.scheme().setString("https"); + // SSL certificate extraction is lazy, moved to JkCoyoteHandler + requestHeaderMessage.getBytes(certificates); + break; + + case Constants.SC_A_SSL_CIPHER : + request.scheme().setString("https"); + requestHeaderMessage.getBytes(tmpMB); + request.setAttribute(AbstractEndpoint.CIPHER_SUITE_KEY, + tmpMB.toString()); + break; + + case Constants.SC_A_SSL_SESSION : + request.scheme().setString("https"); + requestHeaderMessage.getBytes(tmpMB); + request.setAttribute(AbstractEndpoint.SESSION_ID_KEY, + tmpMB.toString()); + break; + + case Constants.SC_A_SSL_KEY_SIZE : + request.setAttribute(AbstractEndpoint.KEY_SIZE_KEY, + new Integer(requestHeaderMessage.getInt())); + break; + + case Constants.SC_A_STORED_METHOD: + requestHeaderMessage.getBytes(request.method()); + break; + + case Constants.SC_A_SECRET: + requestHeaderMessage.getBytes(tmpMB); + if (requiredSecret != null) { + secret = true; + if (!tmpMB.equals(requiredSecret)) { + response.setStatus(403); + adapter.log(request, response, 0); + error = true; + } + } + break; + + default: + // Ignore unknown attribute for backward compatibility + break; + + } + + } + + // Check if secret was submitted if required + if ((requiredSecret != null) && !secret) { + response.setStatus(403); + adapter.log(request, response, 0); + error = true; + } + + // 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(); + int slashPos = -1; + if (pos != -1) { + byte[] uriB = uriBC.getBytes(); + 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); + + } + + + /** + * Parse host. + */ + protected void parseHost(MessageBytes valueMB) { + + if (valueMB == null || valueMB.isNull()) { + // HTTP/1.0 + request.setServerPort(request.getLocalPort()); + try { + request.serverName().duplicate(request.localName()); + } catch (IOException e) { + response.setStatus(400); + adapter.log(request, response, 0); + error = true; + } + return; + } + + ByteChunk valueBC = valueMB.getByteChunk(); + byte[] valueB = valueBC.getBytes(); + int valueL = valueBC.getLength(); + int valueS = valueBC.getStart(); + int colonPos = -1; + if (hostNameC.length < valueL) { + hostNameC = new char[valueL]; + } + + boolean ipv6 = (valueB[valueS] == '['); + boolean bracketClosed = false; + for (int i = 0; i < valueL; i++) { + char b = (char) valueB[i + valueS]; + hostNameC[i] = b; + if (b == ']') { + bracketClosed = true; + } else if (b == ':') { + if (!ipv6 || bracketClosed) { + colonPos = i; + break; + } + } + } + + if (colonPos < 0) { + if (request.scheme().equalsIgnoreCase("https")) { + // 443 - Default HTTPS port + request.setServerPort(443); + } else { + // 80 - Default HTTTP port + request.setServerPort(80); + } + request.serverName().setChars(hostNameC, 0, valueL); + } else { + + request.serverName().setChars(hostNameC, 0, colonPos); + + int port = 0; + int mult = 1; + for (int i = valueL - 1; i > colonPos; i--) { + int charValue = HexUtils.getDec(valueB[i + valueS]); + if (charValue == -1) { + // Invalid character + error = true; + // 400 - Bad request + response.setStatus(400); + adapter.log(request, response, 0); + break; + } + port = port + (charValue * mult); + mult = 10 * mult; + } + request.setServerPort(port); + } + } + + + /** + * When committing the response, we have to validate the set of headers, as + * well as setup the response filters. + */ + protected void prepareResponse() + throws IOException { + + response.setCommitted(true); + + responseHeaderMessage.reset(); + responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); + + // HTTP header contents + responseHeaderMessage.appendInt(response.getStatus()); + String message = null; + if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER && + HttpMessages.isSafeInHttpHeader(response.getMessage())) { + message = response.getMessage(); + } + if (message == null){ + message = HttpMessages.getMessage(response.getStatus()); + } + if (message == null) { + // mod_jk + httpd 2.x fails with a null status message - bug 45026 + message = Integer.toString(response.getStatus()); + } + tmpMB.setString(message); + responseHeaderMessage.appendBytes(tmpMB); + + // Special headers + MimeHeaders headers = response.getMimeHeaders(); + String contentType = response.getContentType(); + if (contentType != null) { + headers.setValue("Content-Type").setString(contentType); + } + String contentLanguage = response.getContentLanguage(); + if (contentLanguage != null) { + headers.setValue("Content-Language").setString(contentLanguage); + } + long contentLength = response.getContentLengthLong(); + if (contentLength >= 0) { + headers.setValue("Content-Length").setLong(contentLength); + } + + // Other headers + int numHeaders = headers.size(); + responseHeaderMessage.appendInt(numHeaders); + for (int i = 0; i < numHeaders; i++) { + MessageBytes hN = headers.getName(i); + int hC = Constants.getResponseAjpIndex(hN.toString()); + if (hC > 0) { + responseHeaderMessage.appendInt(hC); + } + else { + responseHeaderMessage.appendBytes(hN); + } + MessageBytes hV=headers.getValue(i); + responseHeaderMessage.appendBytes(hV); + } + + // Write to buffer + responseHeaderMessage.end(); + output(responseHeaderMessage.getBuffer(), 0, + responseHeaderMessage.getLen()); + } + + // Methods called by prepareResponse() + protected abstract void output(byte[] src, int offset, int length) + throws IOException; + + + // ------------------------------------- InputStreamInputBuffer Inner Class + + + /** + * This class is an input buffer which will read its data from an input + * stream. + */ + protected class SocketInputBuffer + implements InputBuffer { + + + /** + * Read bytes into the specified chunk. + */ + @Override + public int doRead(ByteChunk chunk, Request req ) + throws IOException { + + if (endOfStream) { + return -1; + } + if (first && req.getContentLengthLong() > 0) { + // Handle special first-body-chunk + if (!receive()) { + return 0; + } + } else if (empty) { + if (!refillReadBuffer()) { + return -1; + } + } + ByteChunk bc = bodyBytes.getByteChunk(); + chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength()); + empty = true; + return chunk.getLength(); + + } + + } + + // Methods used by SocketInputBuffer + protected abstract boolean receive() throws IOException; + protected abstract boolean refillReadBuffer() throws IOException; +} diff --git a/java/org/apache/coyote/ajp/AjpAprProcessor.java b/java/org/apache/coyote/ajp/AjpAprProcessor.java index 6d45e7783..c92ebad48 100644 --- a/java/org/apache/coyote/ajp/AjpAprProcessor.java +++ b/java/org/apache/coyote/ajp/AjpAprProcessor.java @@ -17,19 +17,12 @@ package org.apache.coyote.ajp; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InterruptedIOException; -import java.net.InetAddress; import java.nio.ByteBuffer; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.coyote.ActionCode; -import org.apache.coyote.ActionHook; -import org.apache.coyote.Adapter; -import org.apache.coyote.InputBuffer; import org.apache.coyote.OutputBuffer; import org.apache.coyote.Request; import org.apache.coyote.RequestInfo; @@ -41,19 +34,15 @@ import org.apache.tomcat.jni.Status; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.HexUtils; -import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.HttpMessages; -import org.apache.tomcat.util.http.MimeHeaders; -import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.AprEndpoint; import org.apache.tomcat.util.net.SocketStatus; import org.apache.tomcat.util.net.SocketWrapper; -import org.apache.tomcat.util.res.StringManager; /** - * Processes HTTP requests. + * Processes AJP requests. * * @author Remy Maucherat * @author Henri Gomez @@ -63,19 +52,17 @@ import org.apache.tomcat.util.res.StringManager; * @author Costin Manolache * @author Bill Barker */ -public class AjpAprProcessor implements ActionHook { +public class AjpAprProcessor extends AbstractAjpProcessor { /** * Logger. */ private static final Log log = LogFactory.getLog(AjpAprProcessor.class); - - /** - * The string manager for this package. - */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); + @Override + protected Log getLog() { + return log; + } // ----------------------------------------------------------- Constructors @@ -128,123 +115,15 @@ public class AjpAprProcessor implements ActionHook { /** - * Associated adapter. - */ - protected Adapter adapter = null; - - - /** - * Request object. - */ - protected Request request = null; - - - /** - * Response object. - */ - protected Response response = null; - - - /** - * The socket timeout used when reading the first block of the request - * header. - */ - protected int packetSize; - - /** - * Header message. Note that this header is merely the one used during the - * processing of the first message of a "request", so it might not be a request - * header. It will stay unchanged during the processing of the whole request. - */ - protected AjpMessage requestHeaderMessage = null; - - - /** - * Message used for response header composition. - */ - protected AjpMessage responseHeaderMessage = null; - - - /** - * Body message. - */ - protected AjpMessage bodyMessage = null; - - - /** - * Body message. - */ - protected MessageBytes bodyBytes = MessageBytes.newInstance(); - - - /** - * Error flag. - */ - protected boolean error = false; - - /** - * Async used - */ - protected boolean async = false; - - /** * Socket associated with the current connection. */ protected SocketWrapper socket; /** - * Host name (used to avoid useless B2C conversion on the host name). - */ - protected char[] hostNameC = new char[0]; - - - /** - * Associated endpoint. - */ - protected AprEndpoint endpoint; - - - /** - * Temp message bytes used for processing. - */ - protected MessageBytes tmpMB = MessageBytes.newInstance(); - - - /** - * Byte chunk for certs. - */ - protected MessageBytes certificates = MessageBytes.newInstance(); - - - /** - * End of stream flag. - */ - protected boolean endOfStream = false; - - - /** - * Body empty flag. - */ - protected boolean empty = true; - - - /** - * First read. - */ - protected boolean first = true; - - - /** - * Replay read. - */ - protected boolean replay = false; - - - /** - * Finished response. + * Direct buffer used for input. */ - protected boolean finished = false; + protected ByteBuffer inputBuffer = null; /** @@ -254,12 +133,6 @@ public class AjpAprProcessor implements ActionHook { /** - * Direct buffer used for input. - */ - protected ByteBuffer inputBuffer = null; - - - /** * Direct buffer used for sending right away a get body message. */ protected final ByteBuffer getBodyMessageBuffer; @@ -276,6 +149,7 @@ public class AjpAprProcessor implements ActionHook { */ protected static final byte[] endMessageArray; + /** * Direct buffer used for sending explicit flush message. */ @@ -321,36 +195,9 @@ public class AjpAprProcessor implements ActionHook { } - // ------------------------------------------------------------- Properties - - - /** - * Use Tomcat authentication ? - */ - protected boolean tomcatAuthentication = true; - public boolean getTomcatAuthentication() { return tomcatAuthentication; } - public void setTomcatAuthentication(boolean tomcatAuthentication) { this.tomcatAuthentication = tomcatAuthentication; } - - - /** - * Required secret. - */ - protected String requiredSecret = null; - public void setRequiredSecret(String requiredSecret) { this.requiredSecret = requiredSecret; } - - // --------------------------------------------------------- Public Methods - /** Get the request associated with this processor. - * - * @return The request - */ - public Request getRequest() { - return request; - } - - /** * Process pipelined HTTP requests using the specified input and output * streams. @@ -476,7 +323,7 @@ public class AjpAprProcessor implements ActionHook { // Add the socket to the poller if (!error && !endpoint.isPaused()) { - endpoint.getPoller().add(socketRef); + ((AprEndpoint)endpoint).getPoller().add(socketRef); } else { openSocket = false; } @@ -489,9 +336,9 @@ public class AjpAprProcessor implements ActionHook { } - /* Copied from the AjpProcessor.java */ + public SocketState asyncDispatch(SocketWrapper socket, - SocketStatus status) throws IOException { + SocketStatus status) { // Setting up the socket this.socket = socket; @@ -542,131 +389,19 @@ public class AjpAprProcessor implements ActionHook { * @param actionCode Type of the action * @param param Action parameter */ - public void action(ActionCode actionCode, Object param) { + @Override + protected void actionInternal(ActionCode actionCode, Object param) { long socketRef = socket.getSocket().longValue(); - if (actionCode == ActionCode.COMMIT) { - - if (response.isCommitted()) - return; - - // Validate and write response headers - try { - prepareResponse(); - } catch (IOException e) { - // Set error flag - error = true; - } - - } else if (actionCode == ActionCode.CLIENT_FLUSH) { - - if (!response.isCommitted()) { - // Validate and write response headers - try { - prepareResponse(); - } catch (IOException e) { - // Set error flag - error = true; - return; - } - } - - try { - flush(); - // Send explicit flush message - if (Socket.sendb(socketRef, flushMessageBuffer, 0, - flushMessageBuffer.position()) < 0) { - error = true; - } - } catch (IOException e) { - // Set error flag - error = true; - } - - } else if (actionCode == ActionCode.CLOSE) { - // Close - async = false; - - // End the processing of the current request, and stop any further - // transactions with the client - - try { - finish(); - } catch (IOException e) { - // Set error flag - error = true; - } - - } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) { - - if (!certificates.isNull()) { - ByteChunk certData = certificates.getByteChunk(); - X509Certificate jsseCerts[] = null; - ByteArrayInputStream bais = - new ByteArrayInputStream(certData.getBytes(), - certData.getStart(), - certData.getLength()); - // Fill the elements. - try { - CertificateFactory cf = - CertificateFactory.getInstance("X.509"); - while(bais.available() > 0) { - X509Certificate cert = (X509Certificate) - cf.generateCertificate(bais); - if(jsseCerts == null) { - jsseCerts = new X509Certificate[1]; - jsseCerts[0] = cert; - } else { - X509Certificate [] temp = new X509Certificate[jsseCerts.length+1]; - System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length); - temp[jsseCerts.length] = cert; - jsseCerts = temp; - } - } - } catch (java.security.cert.CertificateException e) { - log.error(sm.getString("ajpprocessor.certs.fail"), e); - return; - } - request.setAttribute(AbstractEndpoint.CERTIFICATE_KEY, jsseCerts); - } - - } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) { - - // Get remote host name using a DNS resolution - if (request.remoteHost().isNull()) { - try { - request.remoteHost().setString(InetAddress.getByName - (request.remoteAddr().toString()).getHostName()); - } catch (IOException iex) { - // Ignore - } - } - - } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) { - - // Copy from local name for now, which should simply be an address - request.localAddr().setString(request.localName().toString()); - - } else if (actionCode == ActionCode.REQ_SET_BODY_REPLAY) { - - // Set the given bytes as the content - ByteChunk bc = (ByteChunk) param; - int length = bc.getLength(); - bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length); - request.setContentLength(length); - first = false; - empty = false; - replay = true; - - } else if (actionCode == ActionCode.ASYNC_START) { + if (actionCode == ActionCode.ASYNC_START) { async = true; } else if (actionCode == ActionCode.ASYNC_COMPLETE) { AtomicBoolean dispatch = (AtomicBoolean)param; RequestInfo rp = request.getRequestProcessor(); if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) { //async handling dispatch.set(true); - endpoint.getHandler().asyncDispatch(this.socket, SocketStatus.STOP); + ((AprEndpoint)endpoint).getHandler().asyncDispatch(this.socket, SocketStatus.STOP); } else { dispatch.set(false); } @@ -679,7 +414,7 @@ public class AjpAprProcessor implements ActionHook { RequestInfo rp = request.getRequestProcessor(); AtomicBoolean dispatch = (AtomicBoolean)param; if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) {//async handling - endpoint.getPoller().add(socketRef); + ((AprEndpoint)endpoint).getPoller().add(socketRef); dispatch.set(true); } else { dispatch.set(true); @@ -690,412 +425,21 @@ public class AjpAprProcessor implements ActionHook { } - // ------------------------------------------------------ Connector Methods - - - /** - * Set the associated adapter. - * - * @param adapter the new adapter - */ - public void setAdapter(Adapter adapter) { - this.adapter = adapter; - } - - - /** - * Get the associated adapter. - * - * @return the associated adapter - */ - public Adapter getAdapter() { - return adapter; - } - - // ------------------------------------------------------ Protected Methods - /** - * After reading the request headers, we have to setup the request filters. - */ - protected 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()); - - boolean isSSL = requestHeaderMessage.getByte() != 0; - if (isSSL) { - request.scheme().setString("https"); - } - - // Decode headers - MimeHeaders headers = request.getMimeHeaders(); - - int hCount = requestHeaderMessage.getInt(); - for(int i = 0 ; i < hCount ; i++) { - String hName = null; - - // 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 = null; - 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"))) { - // just read the content-length header, so set it - long cl = vMB.getLong(); - if(cl < Integer.MAX_VALUE) - request.setContentLength( (int)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.getOffset(), - bchunk.getLength()); - } - } - - // Decode extra attributes - boolean secret = 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 remotePort. - * Allow the AJP connector to add this info via - * a private request attribute. - * We will accept the forwarded data as the remote port, - * and remove it from the public list of request attributes. - */ - if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) { - try { - request.setRemotePort(Integer.parseInt(v)); - } catch (NumberFormatException nfe) { - } - } else { - request.setAttribute(n, v); - } - break; - - case Constants.SC_A_CONTEXT : - requestHeaderMessage.getBytes(tmpMB); - // nothing - break; - - case Constants.SC_A_SERVLET_PATH : - requestHeaderMessage.getBytes(tmpMB); - // nothing - break; - - case Constants.SC_A_REMOTE_USER : - if (tomcatAuthentication) { - // ignore server - requestHeaderMessage.getBytes(tmpMB); - } else { - requestHeaderMessage.getBytes(request.getRemoteUser()); - } - break; - - case Constants.SC_A_AUTH_TYPE : - if (tomcatAuthentication) { - // ignore server - requestHeaderMessage.getBytes(tmpMB); - } else { - requestHeaderMessage.getBytes(request.getAuthType()); - } - break; - - case Constants.SC_A_QUERY_STRING : - requestHeaderMessage.getBytes(request.queryString()); - break; - - case Constants.SC_A_JVM_ROUTE : - requestHeaderMessage.getBytes(request.instanceId()); - break; - - case Constants.SC_A_SSL_CERT : - request.scheme().setString("https"); - // SSL certificate extraction is lazy, moved to JkCoyoteHandler - requestHeaderMessage.getBytes(certificates); - break; - - case Constants.SC_A_SSL_CIPHER : - request.scheme().setString("https"); - requestHeaderMessage.getBytes(tmpMB); - request.setAttribute(AbstractEndpoint.CIPHER_SUITE_KEY, - tmpMB.toString()); - break; - - case Constants.SC_A_SSL_SESSION : - request.scheme().setString("https"); - requestHeaderMessage.getBytes(tmpMB); - request.setAttribute(AbstractEndpoint.SESSION_ID_KEY, - tmpMB.toString()); - break; - - case Constants.SC_A_SSL_KEY_SIZE : - request.setAttribute(AbstractEndpoint.KEY_SIZE_KEY, - new Integer(requestHeaderMessage.getInt())); - break; - - case Constants.SC_A_STORED_METHOD: - requestHeaderMessage.getBytes(request.method()); - break; - - case Constants.SC_A_SECRET: - requestHeaderMessage.getBytes(tmpMB); - if (requiredSecret != null) { - secret = true; - if (!tmpMB.equals(requiredSecret)) { - response.setStatus(403); - adapter.log(request, response, 0); - error = true; - } - } - break; - - default: - // Ignore unknown attribute for backward compatibility - break; - - } - - } - - // Check if secret was submitted if required - if ((requiredSecret != null) && !secret) { - response.setStatus(403); - adapter.log(request, response, 0); - error = true; - } - - // 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(); - int slashPos = -1; - if (pos != -1) { - byte[] uriB = uriBC.getBytes(); - 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); - - } - - - /** - * Parse host. - */ - public void parseHost(MessageBytes valueMB) { - - if (valueMB == null || (valueMB != null && valueMB.isNull()) ) { - // HTTP/1.0 - request.setServerPort(request.getLocalPort()); - try { - request.serverName().duplicate(request.localName()); - } catch (IOException e) { - response.setStatus(400); - adapter.log(request, response, 0); - error = true; - } - return; - } - - ByteChunk valueBC = valueMB.getByteChunk(); - byte[] valueB = valueBC.getBytes(); - int valueL = valueBC.getLength(); - int valueS = valueBC.getStart(); - int colonPos = -1; - if (hostNameC.length < valueL) { - hostNameC = new char[valueL]; - } - - boolean ipv6 = (valueB[valueS] == '['); - boolean bracketClosed = false; - for (int i = 0; i < valueL; i++) { - char b = (char) valueB[i + valueS]; - hostNameC[i] = b; - if (b == ']') { - bracketClosed = true; - } else if (b == ':') { - if (!ipv6 || bracketClosed) { - colonPos = i; - break; - } - } - } - - if (colonPos < 0) { - if (request.scheme().equalsIgnoreCase("https")) { - // 443 - Default HTTPS port - request.setServerPort(443); - } else { - // 80 - Default HTTTP port - request.setServerPort(80); - } - request.serverName().setChars(hostNameC, 0, valueL); - } else { - - request.serverName().setChars(hostNameC, 0, colonPos); - - int port = 0; - int mult = 1; - for (int i = valueL - 1; i > colonPos; i--) { - int charValue = HexUtils.getDec(valueB[i + valueS]); - if (charValue == -1) { - // Invalid character - error = true; - // 400 - Bad request - response.setStatus(400); - adapter.log(request, response, 0); - break; - } - port = port + (charValue * mult); - mult = 10 * mult; - } - request.setServerPort(port); - - } - - } - - - /** - * When committing the response, we have to validate the set of headers, as - * well as setup the response filters. - */ - protected void prepareResponse() - throws IOException { - - response.setCommitted(true); - - responseHeaderMessage.reset(); - responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - - // HTTP header contents - responseHeaderMessage.appendInt(response.getStatus()); - String message = null; - if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER && - HttpMessages.isSafeInHttpHeader(response.getMessage())) { - message = response.getMessage(); - } - if (message == null){ - message = HttpMessages.getMessage(response.getStatus()); - } - if (message == null) { - // mod_jk + httpd 2.x fails with a null status message - bug 45026 - message = Integer.toString(response.getStatus()); - } - tmpMB.setString(message); - responseHeaderMessage.appendBytes(tmpMB); - - // Special headers - MimeHeaders headers = response.getMimeHeaders(); - String contentType = response.getContentType(); - if (contentType != null) { - headers.setValue("Content-Type").setString(contentType); - } - String contentLanguage = response.getContentLanguage(); - if (contentLanguage != null) { - headers.setValue("Content-Language").setString(contentLanguage); - } - long contentLength = response.getContentLengthLong(); - if (contentLength >= 0) { - headers.setValue("Content-Length").setLong(contentLength); - } - - // Other headers - int numHeaders = headers.size(); - responseHeaderMessage.appendInt(numHeaders); - for (int i = 0; i < numHeaders; i++) { - MessageBytes hN = headers.getName(i); - int hC = Constants.getResponseAjpIndex(hN.toString()); - if (hC > 0) { - responseHeaderMessage.appendInt(hC); - } - else { - responseHeaderMessage.appendBytes(hN); - } - MessageBytes hV=headers.getValue(i); - responseHeaderMessage.appendBytes(hV); - } - - // Write to buffer - responseHeaderMessage.end(); - outputBuffer.put(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen()); - + @Override + protected void output(byte[] src, int offset, int length) + throws IOException { + outputBuffer.put(src, offset, length); } /** * Finish AJP response. */ - protected void finish() - throws IOException { + @Override + protected void finish() throws IOException { if (!response.isCommitted()) { // Validate and write response headers @@ -1193,6 +537,7 @@ public class AjpAprProcessor implements ActionHook { * 'special' packet in ajp13 and to receive the data * after we send a GET_BODY packet */ + @Override public boolean receive() throws IOException { first = false; @@ -1223,7 +568,8 @@ public class AjpAprProcessor implements ActionHook { * * @return true if there is more data, false if not. */ - private boolean refillReadBuffer() throws IOException { + @Override + protected boolean refillReadBuffer() throws IOException { // If the server returns an empty packet, assume that that end of // the stream has been reached (yuck -- fix protocol??). // FORM support @@ -1313,52 +659,21 @@ public class AjpAprProcessor implements ActionHook { /** * Callback to write data from the buffer. */ - protected void flush() - throws IOException { + @Override + protected void flush() throws IOException { + + long socketRef = socket.getSocket().longValue(); + if (outputBuffer.position() > 0) { - if (Socket.sendbb(socket.getSocket().longValue(), 0, outputBuffer.position()) < 0) { + if (Socket.sendbb(socketRef, 0, outputBuffer.position()) < 0) { throw new IOException(sm.getString("ajpprocessor.failedsend")); } outputBuffer.clear(); } - } - - - // ------------------------------------- InputStreamInputBuffer Inner Class - - - /** - * This class is an input buffer which will read its data from an input - * stream. - */ - protected class SocketInputBuffer - implements InputBuffer { - - - /** - * Read bytes into the specified chunk. - */ - public int doRead(ByteChunk chunk, Request req ) - throws IOException { - - if (endOfStream) { - return -1; - } - if (first && req.getContentLengthLong() > 0) { - // Handle special first-body-chunk - if (!receive()) { - return 0; - } - } else if (empty) { - if (!refillReadBuffer()) { - return -1; - } - } - ByteChunk bc = bodyBytes.getByteChunk(); - chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength()); - empty = true; - return chunk.getLength(); - + // Send explicit flush message + if (Socket.sendb(socketRef, flushMessageBuffer, 0, + flushMessageBuffer.position()) < 0) { + throw new IOException(sm.getString("ajpprocessor.failedflush")); } } @@ -1417,11 +732,6 @@ public class AjpAprProcessor implements ActionHook { } return chunk.getLength(); - } - - } - - } diff --git a/java/org/apache/coyote/ajp/AjpAprProtocol.java b/java/org/apache/coyote/ajp/AjpAprProtocol.java index 1e96ec8cb..e5c42d1df 100644 --- a/java/org/apache/coyote/ajp/AjpAprProtocol.java +++ b/java/org/apache/coyote/ajp/AjpAprProtocol.java @@ -442,20 +442,10 @@ public class AjpAprProtocol // Call the appropriate event try { state = result.asyncDispatch(socket, status); - } catch (java.net.SocketException e) { - // SocketExceptions are normal - AjpAprProtocol.log.debug - (sm.getString - ("ajpprotocol.proto.socketexception.debug"), e); - } catch (java.io.IOException e) { - // IOExceptions are normal - AjpAprProtocol.log.debug - (sm.getString - ("ajpprotocol.proto.ioexception.debug"), e); } // Future developers: if you discover any other // rare-but-nonfatal exceptions, catch them here, and log as - // above. + // debug. catch (Throwable e) { ExceptionUtils.handleThrowable(e); // any other exception or error is odd. Here we log it diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java b/java/org/apache/coyote/ajp/AjpProcessor.java index 17e9c45c2..42ced89d9 100644 --- a/java/org/apache/coyote/ajp/AjpProcessor.java +++ b/java/org/apache/coyote/ajp/AjpProcessor.java @@ -17,21 +17,14 @@ package org.apache.coyote.ajp; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; -import java.net.InetAddress; import java.net.Socket; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.coyote.ActionCode; -import org.apache.coyote.ActionHook; -import org.apache.coyote.Adapter; -import org.apache.coyote.InputBuffer; import org.apache.coyote.OutputBuffer; import org.apache.coyote.Request; import org.apache.coyote.RequestInfo; @@ -41,19 +34,15 @@ import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.HexUtils; -import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.HttpMessages; -import org.apache.tomcat.util.http.MimeHeaders; -import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.JIoEndpoint; import org.apache.tomcat.util.net.SocketStatus; import org.apache.tomcat.util.net.SocketWrapper; -import org.apache.tomcat.util.res.StringManager; /** - * Processes HTTP requests. + * Processes AJP requests. * * @author Remy Maucherat * @author Henri Gomez @@ -63,20 +52,17 @@ import org.apache.tomcat.util.res.StringManager; * @author Costin Manolache * @author Bill Barker */ -public class AjpProcessor implements ActionHook { +public class AjpProcessor extends AbstractAjpProcessor { /** * Logger. */ private static final Log log = LogFactory.getLog(AjpProcessor.class); - - /** - * The string manager for this package. - */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); - + @Override + protected Log getLog() { + return log; + } // ----------------------------------------------------------- Constructors @@ -122,62 +108,6 @@ public class AjpProcessor implements ActionHook { /** - * Associated adapter. - */ - protected Adapter adapter = null; - - - /** - * Request object. - */ - protected Request request = null; - - - /** - * Response object. - */ - protected Response response = null; - - - /** - * The socket timeout used when reading the first block of the request - * header. - */ - protected int packetSize; - - /** - * Header message. Note that this header is merely the one used during the - * processing of the first message of a "request", so it might not be a request - * header. It will stay unchanged during the processing of the whole request. - */ - protected AjpMessage requestHeaderMessage = null; - - - /** - * Message used for response header composition. - */ - protected AjpMessage responseHeaderMessage = null; - - - /** - * Body message. - */ - protected AjpMessage bodyMessage = null; - - - /** - * Body message. - */ - protected MessageBytes bodyBytes = MessageBytes.newInstance(); - - - /** - * Error flag. - */ - protected boolean error = false; - - - /** * Socket associated with the current connection. */ protected SocketWrapper socket; @@ -196,60 +126,6 @@ public class AjpProcessor implements ActionHook { /** - * Host name (used to avoid useless B2C conversion on the host name). - */ - protected char[] hostNameC = new char[0]; - - - /** - * Associated endpoint. - */ - protected JIoEndpoint endpoint; - - - /** - * Temp message bytes used for processing. - */ - protected MessageBytes tmpMB = MessageBytes.newInstance(); - - - /** - * Byte chunk for certs. - */ - protected MessageBytes certificates = MessageBytes.newInstance(); - - - /** - * End of stream flag. - */ - protected boolean endOfStream = false; - - - /** - * Body empty flag. - */ - protected boolean empty = true; - - - /** - * First read. - */ - protected boolean first = true; - - - /** - * Replay read. - */ - protected boolean replay = false; - - - /** - * Finished response. - */ - protected boolean finished = false; - - - /** * Direct buffer used for sending right away a get body message. */ protected final byte[] getBodyMessageArray; @@ -266,17 +142,12 @@ public class AjpProcessor implements ActionHook { */ protected static final byte[] endMessageArray; + /** * Flush message array. */ protected static final byte[] flushMessageArray; - /** - * Async used - */ - protected boolean async = false; - - // ----------------------------------------------------- Static Initializer @@ -319,21 +190,6 @@ public class AjpProcessor implements ActionHook { /** - * Use Tomcat authentication ? - */ - protected boolean tomcatAuthentication = true; - public boolean getTomcatAuthentication() { return tomcatAuthentication; } - public void setTomcatAuthentication(boolean tomcatAuthentication) { this.tomcatAuthentication = tomcatAuthentication; } - - - /** - * Required secret. - */ - protected String requiredSecret = null; - public void setRequiredSecret(String requiredSecret) { this.requiredSecret = requiredSecret; } - - - /** * The number of milliseconds Tomcat will wait for a subsequent request * before closing the connection. The default is the same as for * Apache HTTP Server (15 000 milliseconds). @@ -346,15 +202,6 @@ public class AjpProcessor implements ActionHook { // --------------------------------------------------------- Public Methods - /** Get the request associated with this processor. - * - * @return The request - */ - public Request getRequest() { - return request; - } - - /** * Process pipelined HTTP requests using the specified input and output * streams. @@ -495,7 +342,7 @@ public class AjpProcessor implements ActionHook { } - public SocketState asyncDispatch(SocketStatus status) throws IOException { + public SocketState asyncDispatch(SocketStatus status) { RequestInfo rp = request.getRequestProcessor(); try { @@ -548,116 +395,10 @@ public class AjpProcessor implements ActionHook { * @param actionCode Type of the action * @param param Action parameter */ - public void action(ActionCode actionCode, Object param) { - - if (actionCode == ActionCode.COMMIT) { - - if (response.isCommitted()) - return; + @Override + protected void actionInternal(ActionCode actionCode, Object param) { - // Validate and write response headers - try { - prepareResponse(); - } catch (IOException e) { - // Set error flag - error = true; - } - - } else if (actionCode == ActionCode.CLIENT_FLUSH) { - - if (!response.isCommitted()) { - // Validate and write response headers - try { - prepareResponse(); - } catch (IOException e) { - // Set error flag - error = true; - return; - } - } - - try { - flush(); - } catch (IOException e) { - // Set error flag - error = true; - } - - } else if (actionCode == ActionCode.CLOSE) { - // Close - async = false; - // End the processing of the current request, and stop any further - // transactions with the client - - try { - finish(); - } catch (IOException e) { - // Set error flag - error = true; - } - - } else if (actionCode == ActionCode.REQ_SSL_ATTRIBUTE ) { - - if (!certificates.isNull()) { - ByteChunk certData = certificates.getByteChunk(); - X509Certificate jsseCerts[] = null; - ByteArrayInputStream bais = - new ByteArrayInputStream(certData.getBytes(), - certData.getStart(), - certData.getLength()); - // Fill the elements. - try { - CertificateFactory cf = - CertificateFactory.getInstance("X.509"); - while(bais.available() > 0) { - X509Certificate cert = (X509Certificate) - cf.generateCertificate(bais); - if(jsseCerts == null) { - jsseCerts = new X509Certificate[1]; - jsseCerts[0] = cert; - } else { - X509Certificate [] temp = new X509Certificate[jsseCerts.length+1]; - System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length); - temp[jsseCerts.length] = cert; - jsseCerts = temp; - } - } - } catch (java.security.cert.CertificateException e) { - log.error(sm.getString("ajpprocessor.certs.fail"), e); - return; - } - request.setAttribute(AbstractEndpoint.CERTIFICATE_KEY, jsseCerts); - } - - } else if (actionCode == ActionCode.REQ_HOST_ATTRIBUTE) { - - // Get remote host name using a DNS resolution - if (request.remoteHost().isNull()) { - try { - request.remoteHost().setString(InetAddress.getByName - (request.remoteAddr().toString()).getHostName()); - } catch (IOException iex) { - // Ignore - } - } - - } else if (actionCode == ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE) { - - // Copy from local name for now, which should simply be an address - request.localAddr().setString(request.localName().toString()); - - } else if (actionCode == ActionCode.REQ_SET_BODY_REPLAY) { - - // Set the given bytes as the content - ByteChunk bc = (ByteChunk) param; - int length = bc.getLength(); - bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length); - request.setContentLength(length); - first = false; - empty = false; - replay = true; - - } else if (actionCode == ActionCode.ASYNC_START) { + if (actionCode == ActionCode.ASYNC_START) { //TODO SERVLET3 - async async = true; } else if (actionCode == ActionCode.ASYNC_COMPLETE) { @@ -666,7 +407,7 @@ public class AjpProcessor implements ActionHook { RequestInfo rp = request.getRequestProcessor(); if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) { //async handling dispatch.set(true); - endpoint.processSocketAsync(this.socket, SocketStatus.OPEN); + ((JIoEndpoint)endpoint).processSocketAsync(this.socket, SocketStatus.OPEN); } else { dispatch.set(false); } @@ -680,7 +421,7 @@ public class AjpProcessor implements ActionHook { RequestInfo rp = request.getRequestProcessor(); AtomicBoolean dispatch = (AtomicBoolean)param; if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) {//async handling - endpoint.processSocketAsync(this.socket, SocketStatus.OPEN); + ((JIoEndpoint)endpoint).processSocketAsync(this.socket, SocketStatus.OPEN); dispatch.set(true); } else { dispatch.set(true); @@ -691,412 +432,19 @@ public class AjpProcessor implements ActionHook { } - // ------------------------------------------------------ Connector Methods - - - /** - * Set the associated adapter. - * - * @param adapter the new adapter - */ - public void setAdapter(Adapter adapter) { - this.adapter = adapter; - } - - - /** - * Get the associated adapter. - * - * @return the associated adapter - */ - public Adapter getAdapter() { - return adapter; - } - - // ------------------------------------------------------ Protected Methods - - /** - * After reading the request headers, we have to setup the request filters. - */ - protected 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()); - - boolean isSSL = requestHeaderMessage.getByte() != 0; - if (isSSL) { - request.scheme().setString("https"); - } - - // Decode headers - MimeHeaders headers = request.getMimeHeaders(); - - int hCount = requestHeaderMessage.getInt(); - for(int i = 0 ; i < hCount ; i++) { - String hName = null; - - // 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 = null; - 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"))) { - // just read the content-length header, so set it - long cl = vMB.getLong(); - if(cl < Integer.MAX_VALUE) - request.setContentLength( (int)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.getOffset(), - bchunk.getLength()); - } - } - - // Decode extra attributes - boolean secret = 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 remotePort. - * Allow the AJP connector to add this info via - * a private request attribute. - * We will accept the forwarded data as the remote port, - * and remove it from the public list of request attributes. - */ - if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) { - try { - request.setRemotePort(Integer.parseInt(v)); - } catch (NumberFormatException nfe) { - } - } else { - request.setAttribute(n, v ); - } - break; - - case Constants.SC_A_CONTEXT : - requestHeaderMessage.getBytes(tmpMB); - // nothing - break; - - case Constants.SC_A_SERVLET_PATH : - requestHeaderMessage.getBytes(tmpMB); - // nothing - break; - - case Constants.SC_A_REMOTE_USER : - if (tomcatAuthentication) { - // ignore server - requestHeaderMessage.getBytes(tmpMB); - } else { - requestHeaderMessage.getBytes(request.getRemoteUser()); - } - break; - - case Constants.SC_A_AUTH_TYPE : - if (tomcatAuthentication) { - // ignore server - requestHeaderMessage.getBytes(tmpMB); - } else { - requestHeaderMessage.getBytes(request.getAuthType()); - } - break; - - case Constants.SC_A_QUERY_STRING : - requestHeaderMessage.getBytes(request.queryString()); - break; - - case Constants.SC_A_JVM_ROUTE : - requestHeaderMessage.getBytes(request.instanceId()); - break; - - case Constants.SC_A_SSL_CERT : - request.scheme().setString("https"); - // SSL certificate extraction is lazy, moved to JkCoyoteHandler - requestHeaderMessage.getBytes(certificates); - break; - - case Constants.SC_A_SSL_CIPHER : - request.scheme().setString("https"); - requestHeaderMessage.getBytes(tmpMB); - request.setAttribute(AbstractEndpoint.CIPHER_SUITE_KEY, - tmpMB.toString()); - break; - - case Constants.SC_A_SSL_SESSION : - request.scheme().setString("https"); - requestHeaderMessage.getBytes(tmpMB); - request.setAttribute(AbstractEndpoint.SESSION_ID_KEY, - tmpMB.toString()); - break; - - case Constants.SC_A_SSL_KEY_SIZE : - request.setAttribute(AbstractEndpoint.KEY_SIZE_KEY, - new Integer(requestHeaderMessage.getInt())); - break; - - case Constants.SC_A_STORED_METHOD: - requestHeaderMessage.getBytes(request.method()); - break; - - case Constants.SC_A_SECRET: - requestHeaderMessage.getBytes(tmpMB); - if (requiredSecret != null) { - secret = true; - if (!tmpMB.equals(requiredSecret)) { - response.setStatus(403); - adapter.log(request, response, 0); - error = true; - } - } - break; - - default: - // Ignore unknown attribute for backward compatibility - break; - - } - - } - - // Check if secret was submitted if required - if ((requiredSecret != null) && !secret) { - response.setStatus(403); - adapter.log(request, response, 0); - error = true; - } - - // 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(); - int slashPos = -1; - if (pos != -1) { - byte[] uriB = uriBC.getBytes(); - 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); - - } - - - /** - * Parse host. - */ - public void parseHost(MessageBytes valueMB) { - - if (valueMB == null || (valueMB != null && valueMB.isNull()) ) { - // HTTP/1.0 - request.setServerPort(request.getLocalPort()); - try { - request.serverName().duplicate(request.localName()); - } catch (IOException e) { - response.setStatus(400); - adapter.log(request, response, 0); - error = true; - } - return; - } - - ByteChunk valueBC = valueMB.getByteChunk(); - byte[] valueB = valueBC.getBytes(); - int valueL = valueBC.getLength(); - int valueS = valueBC.getStart(); - int colonPos = -1; - if (hostNameC.length < valueL) { - hostNameC = new char[valueL]; - } - - boolean ipv6 = (valueB[valueS] == '['); - boolean bracketClosed = false; - for (int i = 0; i < valueL; i++) { - char b = (char) valueB[i + valueS]; - hostNameC[i] = b; - if (b == ']') { - bracketClosed = true; - } else if (b == ':') { - if (!ipv6 || bracketClosed) { - colonPos = i; - break; - } - } - } - - if (colonPos < 0) { - if (request.scheme().equalsIgnoreCase("https")) { - // 443 - Default HTTPS port - request.setServerPort(443); - } else { - // 80 - Default HTTTP port - request.setServerPort(80); - } - request.serverName().setChars(hostNameC, 0, valueL); - } else { - - request.serverName().setChars(hostNameC, 0, colonPos); - - int port = 0; - int mult = 1; - for (int i = valueL - 1; i > colonPos; i--) { - int charValue = HexUtils.getDec(valueB[i + valueS]); - if (charValue == -1) { - // Invalid character - error = true; - // 400 - Bad request - response.setStatus(400); - adapter.log(request, response, 0); - break; - } - port = port + (charValue * mult); - mult = 10 * mult; - } - request.setServerPort(port); - - } - - } - - - /** - * When committing the response, we have to validate the set of headers, as - * well as setup the response filters. - */ - protected void prepareResponse() - throws IOException { - - response.setCommitted(true); - - responseHeaderMessage.reset(); - responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - - // HTTP header contents - responseHeaderMessage.appendInt(response.getStatus()); - String message = null; - if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER && - HttpMessages.isSafeInHttpHeader(response.getMessage())) { - message = response.getMessage(); - } - if (message == null){ - message = HttpMessages.getMessage(response.getStatus()); - } - if (message == null) { - // mod_jk + httpd 2.x fails with a null status message - bug 45026 - message = Integer.toString(response.getStatus()); - } - tmpMB.setString(message); - responseHeaderMessage.appendBytes(tmpMB); - - // Special headers - MimeHeaders headers = response.getMimeHeaders(); - String contentType = response.getContentType(); - if (contentType != null) { - headers.setValue("Content-Type").setString(contentType); - } - String contentLanguage = response.getContentLanguage(); - if (contentLanguage != null) { - headers.setValue("Content-Language").setString(contentLanguage); - } - long contentLength = response.getContentLengthLong(); - if (contentLength >= 0) { - headers.setValue("Content-Length").setLong(contentLength); - } - - // Other headers - int numHeaders = headers.size(); - responseHeaderMessage.appendInt(numHeaders); - for (int i = 0; i < numHeaders; i++) { - MessageBytes hN = headers.getName(i); - int hC = Constants.getResponseAjpIndex(hN.toString()); - if (hC > 0) { - responseHeaderMessage.appendInt(hC); - } - else { - responseHeaderMessage.appendBytes(hN); - } - MessageBytes hV=headers.getValue(i); - responseHeaderMessage.appendBytes(hV); - } - - // Write to buffer - responseHeaderMessage.end(); - output.write(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen()); - + @Override + protected void output(byte[] src, int offset, int length) + throws IOException { + output.write(src, offset, length); } - /** * Finish AJP response. */ - protected void finish() - throws IOException { + @Override + protected void finish() throws IOException { if (!response.isCommitted()) { // Validate and write response headers @@ -1146,6 +494,7 @@ public class AjpProcessor implements ActionHook { * 'special' packet in ajp13 and to receive the data * after we send a GET_BODY packet */ + @Override public boolean receive() throws IOException { first = false; @@ -1176,7 +525,8 @@ public class AjpProcessor implements ActionHook { * * @return true if there is more data, false if not. */ - private boolean refillReadBuffer() throws IOException { + @Override + protected boolean refillReadBuffer() throws IOException { // If the server returns an empty packet, assume that that end of // the stream has been reached (yuck -- fix protocol??). // FORM support @@ -1252,53 +602,13 @@ public class AjpProcessor implements ActionHook { /** * Callback to write data from the buffer. */ - protected void flush() - throws IOException { + @Override + protected void flush() throws IOException { // Send the flush message output.write(flushMessageArray); } - // ------------------------------------- InputStreamInputBuffer Inner Class - - - /** - * This class is an input buffer which will read its data from an input - * stream. - */ - protected class SocketInputBuffer - implements InputBuffer { - - - /** - * Read bytes into the specified chunk. - */ - public int doRead(ByteChunk chunk, Request req ) - throws IOException { - - if (endOfStream) { - return -1; - } - if (first && req.getContentLengthLong() > 0) { - // Handle special first-body-chunk - if (!receive()) { - return 0; - } - } else if (empty) { - if (!refillReadBuffer()) { - return -1; - } - } - ByteChunk bc = bodyBytes.getByteChunk(); - chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength()); - empty = true; - return chunk.getLength(); - - } - - } - - // ----------------------------------- OutputStreamOutputBuffer Inner Class @@ -1347,11 +657,6 @@ public class AjpProcessor implements ActionHook { } return chunk.getLength(); - } - - } - - } diff --git a/java/org/apache/coyote/ajp/LocalStrings.properties b/java/org/apache/coyote/ajp/LocalStrings.properties index 4965bfc26..4978eb41f 100644 --- a/java/org/apache/coyote/ajp/LocalStrings.properties +++ b/java/org/apache/coyote/ajp/LocalStrings.properties @@ -41,6 +41,8 @@ ajpprotocol.failedread=Socket read failed ajpprotocol.failedwrite=Socket write failed ajpprotocol.request.register=Error registering request processor in JMX +ajpprocessor.failedflush=Failed to flush AJP message +ajpprocessor.failedsend=Failed to send AJP message ajpprocessor.header.error=Header message parsing failed ajpprocessor.request.prepare=Error preparing request ajpprocessor.request.process=Error processing request -- 2.11.0