--- /dev/null
+/*
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed 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.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 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;
+import org.apache.coyote.Response;
+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.JIoEndpoint;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.threads.ThreadWithAttributes;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
+ * @author Bill Barker
+ */
+public class AjpProcessor implements ActionHook {
+
+
+ /**
+ * Logger.
+ */
+ protected static org.apache.commons.logging.Log log
+ = org.apache.commons.logging.LogFactory.getLog(AjpProcessor.class);
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ public AjpProcessor(JIoEndpoint endpoint) {
+
+ this.endpoint = endpoint;
+
+ request = new Request();
+ request.setInputBuffer(new SocketInputBuffer());
+
+ response = new Response();
+ response.setHook(this);
+ response.setOutputBuffer(new SocketOutputBuffer());
+ request.setResponse(response);
+
+ // Cause loading of HexUtils
+ int foo = HexUtils.DEC[0];
+
+ // Cause loading of HttpMessages
+ HttpMessages.getMessage(200);
+
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Associated adapter.
+ */
+ protected Adapter adapter = null;
+
+
+ /**
+ * Request object.
+ */
+ protected Request request = null;
+
+
+ /**
+ * Response object.
+ */
+ protected Response response = null;
+
+
+ /**
+ * 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 = new AjpMessage();
+
+
+ /**
+ * Message used for response header composition.
+ */
+ protected AjpMessage responseHeaderMessage = new AjpMessage();
+
+
+ /**
+ * Body message.
+ */
+ protected AjpMessage bodyMessage = new AjpMessage();
+
+
+ /**
+ * Body message.
+ */
+ protected MessageBytes bodyBytes = MessageBytes.newInstance();
+
+
+ /**
+ * State flag.
+ */
+ protected boolean started = false;
+
+
+ /**
+ * Error flag.
+ */
+ protected boolean error = false;
+
+
+ /**
+ * Socket associated with the current connection.
+ */
+ protected Socket socket;
+
+
+ /**
+ * Input stream.
+ */
+ protected InputStream input;
+
+
+ /**
+ * Output stream.
+ */
+ protected OutputStream output;
+
+
+ /**
+ * Host name (used to avoid useless B2C conversion on the host name).
+ */
+ protected char[] hostNameC = new char[0];
+
+
+ /**
+ * Associated endpoint.
+ */
+ protected JIoEndpoint endpoint;
+
+
+ /**
+ * The socket timeout used when reading the first block of the request
+ * header.
+ */
+ protected long readTimeout;
+
+
+ /**
+ * 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 static final byte[] getBodyMessageArray;
+
+
+ /**
+ * Direct buffer used for sending right away a pong message.
+ */
+ protected static final byte[] pongMessageArray;
+
+
+ /**
+ * End message array.
+ */
+ protected static final byte[] endMessageArray;
+
+
+ // ----------------------------------------------------- Static Initializer
+
+
+ static {
+
+ // Set the get body message buffer
+ AjpMessage getBodyMessage = new AjpMessage();
+ getBodyMessage.reset();
+ getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
+ getBodyMessage.appendInt(Constants.MAX_READ_SIZE);
+ getBodyMessage.end();
+ getBodyMessageArray = new byte[getBodyMessage.getLen()];
+ System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray,
+ 0, getBodyMessage.getLen());
+
+ // Set the read body message buffer
+ AjpMessage pongMessage = new AjpMessage();
+ pongMessage.reset();
+ pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
+ pongMessage.end();
+ pongMessageArray = new byte[pongMessage.getLen()];
+ System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
+ 0, pongMessage.getLen());
+
+ // Allocate the end message array
+ AjpMessage endMessage = new AjpMessage();
+ endMessage.reset();
+ endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
+ endMessage.appendByte(1);
+ endMessage.end();
+ endMessageArray = new byte[endMessage.getLen()];
+ System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
+ endMessage.getLen());
+
+ }
+
+
+ // ------------------------------------------------------------- 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.
+ *
+ * @throws IOException error during an I/O operation
+ */
+ public boolean process(Socket socket)
+ throws IOException {
+ ThreadWithAttributes thrA=
+ (ThreadWithAttributes)Thread.currentThread();
+ RequestInfo rp = request.getRequestProcessor();
+ thrA.setCurrentStage(endpoint, "parsing http request");
+ rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+ // Setting up the socket
+ this.socket = socket;
+ input = socket.getInputStream();
+ output = socket.getOutputStream();
+
+ // Error flag
+ error = false;
+
+ long soTimeout = endpoint.getSoTimeout();
+
+ while (started && !error) {
+
+ // Parsing the request header
+ try {
+ // Get first message of the request
+ if (!readMessage(requestHeaderMessage)) {
+ // This means a connection timeout
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+ break;
+ }
+ // Check message type, process right away and break if
+ // not regular request processing
+ int type = requestHeaderMessage.getByte();
+ if (type == Constants.JK_AJP13_CPING_REQUEST) {
+ try {
+ output.write(pongMessageArray);
+ } catch (IOException e) {
+ error = true;
+ }
+ continue;
+ } else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
+ // Usually the servlet didn't read the previous request body
+ if(log.isDebugEnabled()) {
+ log.debug("Unexpected message: "+type);
+ }
+ continue;
+ }
+
+ request.setStartTime(System.currentTimeMillis());
+ } catch (IOException e) {
+ error = true;
+ break;
+ } catch (Throwable t) {
+ log.debug(sm.getString("ajpprocessor.header.error"), t);
+ // 400 - Bad Request
+ response.setStatus(400);
+ error = true;
+ }
+
+ // Setting up filters, and parse some request headers
+ thrA.setCurrentStage(endpoint, "prepareRequest");
+ rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+ try {
+ prepareRequest();
+ thrA.setParam(endpoint, request.requestURI());
+ } catch (Throwable t) {
+ log.debug(sm.getString("ajpprocessor.request.prepare"), t);
+ // 400 - Internal Server Error
+ response.setStatus(400);
+ error = true;
+ }
+
+ // Process the request in the adapter
+ if (!error) {
+ try {
+ thrA.setCurrentStage(endpoint, "service");
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+ adapter.service(request, response);
+ } catch (InterruptedIOException e) {
+ error = true;
+ } catch (Throwable t) {
+ log.error(sm.getString("ajpprocessor.request.process"), t);
+ // 500 - Internal Server Error
+ response.setStatus(500);
+ error = true;
+ }
+ }
+
+ // Finish the response if not done yet
+ if (!finished) {
+ try {
+ finish();
+ } catch (Throwable t) {
+ error = true;
+ }
+ }
+
+ // If there was an error, make sure the request is counted as
+ // and error, and update the statistics counter
+ if (error) {
+ response.setStatus(500);
+ }
+ request.updateCounters();
+
+ thrA.setCurrentStage(endpoint, "ended");
+ rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+ recycle();
+
+ }
+
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+ recycle();
+
+ return true;
+
+ }
+
+
+ // ----------------------------------------------------- ActionHook Methods
+
+
+ /**
+ * Send an action to the connector.
+ *
+ * @param actionCode Type of the action
+ * @param param Action parameter
+ */
+ public void action(ActionCode actionCode, Object param) {
+
+ if (actionCode == ActionCode.ACTION_COMMIT) {
+
+ if (response.isCommitted())
+ return;
+
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+
+ } else if (actionCode == ActionCode.ACTION_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.ACTION_CLOSE) {
+ // Close
+
+ // 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.ACTION_START) {
+
+ started = true;
+
+ } else if (actionCode == ActionCode.ACTION_STOP) {
+
+ started = false;
+
+ } else if (actionCode == ActionCode.ACTION_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 first element.
+ try {
+ CertificateFactory cf =
+ CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate)
+ cf.generateCertificate(bais);
+ jsseCerts = new X509Certificate[1];
+ jsseCerts[0] = cert;
+ request.setAttribute(JIoEndpoint.CERTIFICATE_KEY, jsseCerts);
+ } catch (java.security.cert.CertificateException e) {
+ log.error(sm.getString("ajpprocessor.certs.fail"), e);
+ return;
+ }
+ }
+
+ } else if (actionCode == ActionCode.ACTION_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.ACTION_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.ACTION_REQ_SET_BODY_REPLAY) {
+
+ // Set the given bytes as the content
+ ByteChunk bc = (ByteChunk) param;
+ bodyBytes.setBytes(bc.getBytes(), bc.getStart(), bc.getLength());
+ first = false;
+ empty = false;
+ replay = true;
+
+ }
+
+
+ }
+
+
+ // ------------------------------------------------------ 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.methodTransArray[(int)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.headerTransArray[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
+ request.setContentLength( vMB.getInt() );
+ } 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();
+ 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(JIoEndpoint.CIPHER_SUITE_KEY,
+ tmpMB.toString());
+ break;
+
+ case Constants.SC_A_SSL_SESSION :
+ request.scheme().setString("https");
+ requestHeaderMessage.getBytes(tmpMB);
+ request.setAttribute(JIoEndpoint.SESSION_ID_KEY,
+ tmpMB.toString());
+ break;
+
+ case Constants.SC_A_SSL_KEY_SIZE :
+ request.setAttribute(JIoEndpoint.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);
+ 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);
+ 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
+ // Default is what the socket tells us. Overriden if a host is
+ // found/parsed
+ request.setServerPort(endpoint.getPort());
+ 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.DEC[(int) valueB[i + valueS]];
+ if (charValue == -1) {
+ // Invalid character
+ error = true;
+ // 400 - Bad request
+ response.setStatus(400);
+ 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 = response.getMessage();
+ if (message == null){
+ message = HttpMessages.getMessage(response.getStatus());
+ } else {
+ message = message.replace('\n', ' ').replace('\r', ' ');
+ }
+ 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);
+ }
+ int contentLength = response.getContentLength();
+ if (contentLength >= 0) {
+ headers.setValue("Content-Length").setInt(contentLength);
+ }
+
+ // Other headers
+ int numHeaders = headers.size();
+ responseHeaderMessage.appendInt(numHeaders);
+ for (int i = 0; i < numHeaders; i++) {
+ MessageBytes hN = headers.getName(i);
+ responseHeaderMessage.appendBytes(hN);
+ MessageBytes hV=headers.getValue(i);
+ responseHeaderMessage.appendBytes(hV);
+ }
+
+ // Write to buffer
+ responseHeaderMessage.end();
+ output.write(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen());
+
+ }
+
+
+ /**
+ * Finish AJP response.
+ */
+ protected void finish()
+ throws IOException {
+
+ if (!response.isCommitted()) {
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+ }
+
+ if (finished)
+ return;
+
+ finished = true;
+
+ // Add the end message
+ output.write(endMessageArray);
+
+ }
+
+
+ /**
+ * Read at least the specified amount of bytes, and place them
+ * in the input buffer.
+ */
+ protected boolean read(byte[] buf, int pos, int n)
+ throws IOException {
+
+ int read = 0;
+ int res = 0;
+ while (read < n) {
+ res = input.read(buf, read + pos, n - read);
+ if (res > 0) {
+ read += res;
+ } else {
+ throw new IOException(sm.getString("ajpprotocol.failedread"));
+ }
+ }
+
+ return true;
+
+ }
+
+
+ /** Receive a chunk of data. Called to implement the
+ * 'special' packet in ajp13 and to receive the data
+ * after we send a GET_BODY packet
+ */
+ public boolean receive() throws IOException {
+
+ first = false;
+ bodyMessage.reset();
+ readMessage(bodyMessage);
+
+ // No data received.
+ if (bodyMessage.getLen() == 0) {
+ // just the header
+ // Don't mark 'end of stream' for the first chunk.
+ return false;
+ }
+ int blen = bodyMessage.peekInt();
+ if (blen == 0) {
+ return false;
+ }
+
+ bodyMessage.getBytes(bodyBytes);
+ empty = false;
+ return true;
+ }
+
+ /**
+ * Get more request body data from the web server and store it in the
+ * internal buffer.
+ *
+ * @return true if there is more data, false if not.
+ */
+ private 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
+ if (replay) {
+ endOfStream = true; // we've read everything there is
+ }
+ if (endOfStream) {
+ return false;
+ }
+
+ // Request more data immediately
+ output.write(getBodyMessageArray);
+
+ boolean moreData = receive();
+ if( !moreData ) {
+ endOfStream = true;
+ }
+ return moreData;
+ }
+
+
+ /**
+ * Read an AJP message.
+ *
+ * @return true if the message has been read, false if the short read
+ * didn't return anything
+ * @throws IOException any other failure, including incomplete reads
+ */
+ protected boolean readMessage(AjpMessage message)
+ throws IOException {
+
+ byte[] buf = message.getBuffer();
+
+ read(buf, 0, message.getHeaderLength());
+
+ message.processHeader();
+ read(buf, message.getHeaderLength(), message.getLen());
+
+ return true;
+
+ }
+
+
+ /**
+ * Recycle the processor.
+ */
+ public void recycle() {
+
+ // Recycle Request object
+ first = true;
+ endOfStream = false;
+ empty = true;
+ replay = false;
+ finished = false;
+ request.recycle();
+ response.recycle();
+ certificates.recycle();
+
+ input = null;
+ output = null;
+
+ }
+
+
+ /**
+ * Callback to write data from the buffer.
+ */
+ protected void flush()
+ throws IOException {
+ // Note: at the moment, there's no buffering at the socket level
+ }
+
+
+ // ------------------------------------- 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.getContentLength() > 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
+
+
+ /**
+ * This class is an output buffer which will write data to an output
+ * stream.
+ */
+ protected class SocketOutputBuffer
+ implements OutputBuffer {
+
+
+ /**
+ * Write chunk.
+ */
+ public int doWrite(ByteChunk chunk, Response res)
+ throws IOException {
+
+ if (!response.isCommitted()) {
+ // Validate and write response headers
+ try {
+ prepareResponse();
+ } catch (IOException e) {
+ // Set error flag
+ error = true;
+ }
+ }
+
+ int len = chunk.getLength();
+ // 4 - hardcoded, byte[] marshalling overhead
+ int chunkSize = Constants.MAX_SEND_SIZE;
+ int off = 0;
+ while (len > 0) {
+ int thisTime = len;
+ if (thisTime > chunkSize) {
+ thisTime = chunkSize;
+ }
+ len -= thisTime;
+ responseHeaderMessage.reset();
+ responseHeaderMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+ responseHeaderMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
+ output.write(responseHeaderMessage.getBuffer(), 0, responseHeaderMessage.getLen());
+ off += thisTime;
+ }
+
+ return chunk.getLength();
+
+ }
+
+
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed 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.net.InetAddress;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.ActionHook;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.ProtocolHandler;
+import org.apache.coyote.RequestGroupInfo;
+import org.apache.coyote.RequestInfo;
+import org.apache.tomcat.util.modeler.Registry;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.apache.tomcat.util.res.StringManager;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class AjpProtocol
+ implements ProtocolHandler, MBeanRegistration {
+
+
+ protected static org.apache.commons.logging.Log log =
+ org.apache.commons.logging.LogFactory.getLog(AjpProtocol.class);
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+
+ // ------------------------------------------------------------ Constructor
+
+
+ public AjpProtocol() {
+ cHandler = new AjpConnectionHandler(this);
+ setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+ setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+ //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);
+ setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ protected ObjectName tpOname;
+
+
+ protected ObjectName rgOname;
+
+
+ /**
+ * Associated java.io endpoint.
+ */
+ protected JIoEndpoint ep = new JIoEndpoint();
+
+
+ /**
+ * Configuration attributes.
+ */
+ protected Hashtable attributes = new Hashtable();
+
+
+ /**
+ * Should authentication be done in the native webserver layer,
+ * or in the Servlet container ?
+ */
+ protected boolean tomcatAuthentication = true;
+
+
+ /**
+ * Required secret.
+ */
+ protected String requiredSecret = null;
+
+
+ /**
+ * Adapter which will process the requests recieved by this endpoint.
+ */
+ private Adapter adapter;
+
+
+ /**
+ * Connection handler for AJP.
+ */
+ private AjpConnectionHandler cHandler;
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Pass config info
+ */
+ public void setAttribute(String name, Object value) {
+ if (log.isTraceEnabled()) {
+ log.trace(sm.getString("ajpprotocol.setattribute", name, value));
+ }
+ attributes.put(name, value);
+ }
+
+ public Object getAttribute(String key) {
+ if (log.isTraceEnabled()) {
+ log.trace(sm.getString("ajpprotocol.getattribute", key));
+ }
+ return attributes.get(key);
+ }
+
+
+ public Iterator getAttributeNames() {
+ return attributes.keySet().iterator();
+ }
+
+
+ /**
+ * Set a property.
+ */
+ public void setProperty(String name, String value) {
+ setAttribute(name, value);
+ }
+
+
+ /**
+ * Get a property
+ */
+ public String getProperty(String name) {
+ return (String) getAttribute(name);
+ }
+
+
+ /**
+ * The adapter, used to call the connector
+ */
+ public void setAdapter(Adapter adapter) {
+ this.adapter = adapter;
+ }
+
+
+ public Adapter getAdapter() {
+ return adapter;
+ }
+
+
+ /** Start the protocol
+ */
+ public void init() throws Exception {
+ ep.setName(getName());
+ ep.setHandler(cHandler);
+
+ try {
+ ep.init();
+ } catch (Exception ex) {
+ log.error(sm.getString("ajpprotocol.endpoint.initerror"), ex);
+ throw ex;
+ }
+ if (log.isInfoEnabled()) {
+ log.info(sm.getString("ajpprotocol.init", getName()));
+ }
+ }
+
+
+ public void start() throws Exception {
+ if (this.domain != null ) {
+ try {
+ tpOname = new ObjectName
+ (domain + ":" + "type=ThreadPool,name=" + getName());
+ Registry.getRegistry(null, null)
+ .registerComponent(ep, tpOname, null );
+ } catch (Exception e) {
+ log.error("Can't register threadpool" );
+ }
+ rgOname = new ObjectName
+ (domain + ":type=GlobalRequestProcessor,name=" + getName());
+ Registry.getRegistry(null, null).registerComponent
+ (cHandler.global, rgOname, null);
+ }
+
+ try {
+ ep.start();
+ } catch (Exception ex) {
+ log.error(sm.getString("ajpprotocol.endpoint.starterror"), ex);
+ throw ex;
+ }
+ if (log.isInfoEnabled())
+ log.info(sm.getString("ajpprotocol.start", getName()));
+ }
+
+ public void pause() throws Exception {
+ try {
+ ep.pause();
+ } catch (Exception ex) {
+ log.error(sm.getString("ajpprotocol.endpoint.pauseerror"), ex);
+ throw ex;
+ }
+ if (log.isInfoEnabled())
+ log.info(sm.getString("ajpprotocol.pause", getName()));
+ }
+
+ public void resume() throws Exception {
+ try {
+ ep.resume();
+ } catch (Exception ex) {
+ log.error(sm.getString("ajpprotocol.endpoint.resumeerror"), ex);
+ throw ex;
+ }
+ if (log.isInfoEnabled())
+ log.info(sm.getString("ajpprotocol.resume", getName()));
+ }
+
+ public void destroy() throws Exception {
+ if (log.isInfoEnabled())
+ log.info(sm.getString("ajpprotocol.stop", getName()));
+ ep.destroy();
+ if (tpOname!=null)
+ Registry.getRegistry(null, null).unregisterComponent(tpOname);
+ if (rgOname != null)
+ Registry.getRegistry(null, null).unregisterComponent(rgOname);
+ }
+
+ // *
+ public Executor getExecutor() {
+ return ep.getExecutor();
+ }
+
+ // *
+ public void setExecutor(Executor executor) {
+ ep.setExecutor(executor);
+ }
+
+ public int getMaxThreads() {
+ return ep.getMaxThreads();
+ }
+
+ public void setMaxThreads(int maxThreads) {
+ ep.setMaxThreads(maxThreads);
+ setAttribute("maxThreads", "" + maxThreads);
+ }
+
+ public void setThreadPriority(int threadPriority) {
+ ep.setThreadPriority(threadPriority);
+ setAttribute("threadPriority", "" + threadPriority);
+ }
+
+ public int getThreadPriority() {
+ return ep.getThreadPriority();
+ }
+
+
+ public int getBacklog() {
+ return ep.getBacklog();
+ }
+
+
+ public void setBacklog( int i ) {
+ ep.setBacklog(i);
+ setAttribute("backlog", "" + i);
+ }
+
+
+ public int getPort() {
+ return ep.getPort();
+ }
+
+
+ public void setPort( int port ) {
+ ep.setPort(port);
+ setAttribute("port", "" + port);
+ }
+
+
+ public InetAddress getAddress() {
+ return ep.getAddress();
+ }
+
+
+ public void setAddress(InetAddress ia) {
+ ep.setAddress(ia);
+ setAttribute("address", "" + ia);
+ }
+
+
+ public String getName() {
+ String encodedAddr = "";
+ if (getAddress() != null) {
+ encodedAddr = "" + getAddress();
+ if (encodedAddr.startsWith("/"))
+ encodedAddr = encodedAddr.substring(1);
+ encodedAddr = URLEncoder.encode(encodedAddr) + "-";
+ }
+ return ("ajp-" + encodedAddr + ep.getPort());
+ }
+
+
+ public boolean getTcpNoDelay() {
+ return ep.getTcpNoDelay();
+ }
+
+
+ public void setTcpNoDelay(boolean b) {
+ ep.setTcpNoDelay(b);
+ setAttribute("tcpNoDelay", "" + b);
+ }
+
+
+ public boolean getTomcatAuthentication() {
+ return tomcatAuthentication;
+ }
+
+
+ public void setTomcatAuthentication(boolean tomcatAuthentication) {
+ this.tomcatAuthentication = tomcatAuthentication;
+ }
+
+
+ public int getSoLinger() {
+ return ep.getSoLinger();
+ }
+
+
+ public void setSoLinger(int i) {
+ ep.setSoLinger(i);
+ setAttribute("soLinger", "" + i);
+ }
+
+
+ public int getSoTimeout() {
+ return ep.getSoTimeout();
+ }
+
+
+ public void setSoTimeout( int i ) {
+ ep.setSoTimeout(i);
+ setAttribute("soTimeout", "" + i);
+ }
+
+
+ public void setRequiredSecret(String requiredSecret) {
+ this.requiredSecret = requiredSecret;
+ }
+
+
+ // -------------------------------------- AjpConnectionHandler Inner Class
+
+
+ protected static class AjpConnectionHandler implements Handler {
+ protected AjpProtocol proto;
+ protected static int count = 0;
+ protected RequestGroupInfo global=new RequestGroupInfo();
+ protected ThreadLocal localProcessor = new ThreadLocal();
+
+ public AjpConnectionHandler(AjpProtocol proto) {
+ this.proto = proto;
+ }
+
+ public boolean process(Socket socket) {
+ AjpProcessor processor = null;
+ try {
+ processor = (AjpProcessor) localProcessor.get();
+ if (processor == null) {
+ processor = new AjpProcessor(proto.ep);
+ processor.setAdapter(proto.adapter);
+ processor.setTomcatAuthentication(proto.tomcatAuthentication);
+ processor.setRequiredSecret(proto.requiredSecret);
+ localProcessor.set(processor);
+ if (proto.getDomain() != null) {
+ synchronized (this) {
+ try {
+ RequestInfo rp = processor.getRequest().getRequestProcessor();
+ rp.setGlobalProcessor(global);
+ ObjectName rpName = new ObjectName
+ (proto.getDomain() + ":type=RequestProcessor,worker="
+ + proto.getName() + ",name=AjpRequest" + count++ );
+ Registry.getRegistry(null, null)
+ .registerComponent(rp, rpName, null);
+ } catch (Exception ex) {
+ log.warn(sm.getString("ajpprotocol.request.register"));
+ }
+ }
+ }
+ }
+
+ if (processor instanceof ActionHook) {
+ ((ActionHook) processor).action(ActionCode.ACTION_START, null);
+ }
+
+ return processor.process(socket);
+
+ } catch(java.net.SocketException e) {
+ // SocketExceptions are normal
+ AjpProtocol.log.debug
+ (sm.getString
+ ("ajpprotocol.proto.socketexception.debug"), e);
+ } catch (java.io.IOException e) {
+ // IOExceptions are normal
+ AjpProtocol.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.
+ catch (Throwable e) {
+ // any other exception or error is odd. Here we log it
+ // with "ERROR" level, so it will show up even on
+ // less-than-verbose logs.
+ AjpProtocol.log.error
+ (sm.getString("ajpprotocol.proto.error"), e);
+ } finally {
+ if (processor instanceof ActionHook) {
+ ((ActionHook) processor).action(ActionCode.ACTION_STOP, null);
+ }
+ }
+ return false;
+ }
+ }
+
+
+ // -------------------- Various implementation classes --------------------
+
+
+ protected String domain;
+ protected ObjectName oname;
+ protected MBeanServer mserver;
+
+ public ObjectName getObjectName() {
+ return oname;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public ObjectName preRegister(MBeanServer server,
+ ObjectName name) throws Exception {
+ oname=name;
+ mserver=server;
+ domain=name.getDomain();
+ return name;
+ }
+
+ public void postRegister(Boolean registrationDone) {
+ }
+
+ public void preDeregister() throws Exception {
+ }
+
+ public void postDeregister() {
+ }
+
+
+}