//======================================================================== //Copyright 2006 Mort Bay Consulting Pty. Ltd. //------------------------------------------------------------------------ //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.mortbay.jetty.ajp; import java.io.IOException; import java.io.InterruptedIOException; import javax.servlet.ServletInputStream; import org.mortbay.io.Buffer; import org.mortbay.io.BufferUtil; import org.mortbay.io.Buffers; import org.mortbay.io.EndPoint; import org.mortbay.io.View; import org.mortbay.jetty.EofException; import org.mortbay.jetty.HttpTokens; import org.mortbay.jetty.Parser; import org.mortbay.log.Log; /** * @author Markus Kobler */ public class Ajp13Parser implements Parser { private final static int STATE_START = -1; private final static int STATE_END = 0; private final static int STATE_AJP13CHUNK_START = 1; private final static int STATE_AJP13CHUNK = 2; private int _state = STATE_START; private long _contentLength; private long _contentPosition; private int _chunkLength; private int _chunkPosition; private int _headers; private Buffers _buffers; private EndPoint _endp; private Buffer _buffer; private Buffer _header; // Buffer for header data (and small _content) private Buffer _body; // Buffer for large content private View _contentView = new View(); private EventHandler _handler; private Ajp13Generator _generator; private View _tok0; // Saved token: header name, request method or response version private View _tok1; // Saved token: header value, request URI orresponse code protected int _length; protected int _packetLength; /* ------------------------------------------------------------------------------- */ public Ajp13Parser(Buffers buffers, EndPoint endPoint) { _buffers = buffers; _endp = endPoint; } /* ------------------------------------------------------------------------------- */ public void setEventHandler(EventHandler handler) { _handler=handler; } /* ------------------------------------------------------------------------------- */ public void setGenerator(Ajp13Generator generator) { _generator=generator; } /* ------------------------------------------------------------------------------- */ public long getContentLength() { return _contentLength; } /* ------------------------------------------------------------------------------- */ public int getState() { return _state; } /* ------------------------------------------------------------------------------- */ public boolean inContentState() { return _state > 0; } /* ------------------------------------------------------------------------------- */ public boolean inHeaderState() { return _state < 0; } /* ------------------------------------------------------------------------------- */ public boolean isIdle() { return _state == STATE_START; } /* ------------------------------------------------------------------------------- */ public boolean isComplete() { return _state == STATE_END; } /* ------------------------------------------------------------------------------- */ public boolean isMoreInBuffer() { if (_header != null && _header.hasContent() || _body != null && _body.hasContent()) return true; return false; } /* ------------------------------------------------------------------------------- */ public boolean isState(int state) { return _state == state; } /* ------------------------------------------------------------------------------- */ public void parse() throws IOException { if (_state == STATE_END) reset(false); if (_state != STATE_START) throw new IllegalStateException("!START"); // continue parsing while (!isComplete()) { parseNext(); } } /* ------------------------------------------------------------------------------- */ public long parseAvailable() throws IOException { long len = parseNext(); long total = len > 0 ? len : 0; // continue parsing while (!isComplete() && _buffer != null && _buffer.length() > 0) { len = parseNext(); if (len > 0) total += len; else break; } return total; } /* ------------------------------------------------------------------------------- */ private int fill() throws IOException { int filled = -1; if (_body != null && _buffer != _body) { // mod_jk implementations may have some partial data from header // check if there are partial contents in the header // copy it to the body if there are any if(_header.length() > 0) { // copy the patial data from the header to the body _body.put(_header); } _buffer = _body; if (_buffer.length()>0) { filled = _buffer.length(); return filled; } } if (_buffer.markIndex() == 0 && _buffer.putIndex() == _buffer.capacity()) throw new IOException("FULL"); if (_endp != null && filled <= 0) { // Compress buffer if handling _content buffer // TODO check this is not moving data too much if (_buffer == _body) _buffer.compact(); if (_buffer.space() == 0) throw new IOException("FULL"); try { filled = _endp.fill(_buffer); } catch (IOException e) { // This is normal in AJP since the socket closes on timeout only Log.debug(e); reset(true); throw (e instanceof EofException) ? e : new EofException(e); } } if (filled < 0) { if (_state > STATE_END) { _state = STATE_END; _handler.messageComplete(_contentPosition); return filled; } reset(true); throw new EofException(); } return filled; } /* ------------------------------------------------------------------------------- */ public long parseNext() throws IOException { long total_filled = -1; if (_buffer == null) { if (_header == null) { _header = _buffers.getBuffer(Ajp13Packet.MAX_PACKET_SIZE); _header.clear(); } _buffer = _header; _tok0 = new View(_header); _tok1 = new View(_header); _tok0.setPutIndex(_tok0.getIndex()); _tok1.setPutIndex(_tok1.getIndex()); } if (_state == STATE_END) throw new IllegalStateException("STATE_END"); if (_state > STATE_END && _contentPosition == _contentLength) { _state = STATE_END; _handler.messageComplete(_contentPosition); return total_filled; } if (_state < 0) { // have we seen a packet? if (_packetLength<=0) { if (_buffer.length()<4) { if (total_filled<0) total_filled=0; total_filled+=fill(); if (_buffer.length()<4) return total_filled; } _contentLength = HttpTokens.UNKNOWN_CONTENT; int _magic = Ajp13RequestPacket.getInt(_buffer); if (_magic != Ajp13RequestHeaders.MAGIC) throw new IOException("Bad AJP13 rcv packet: " + "0x" + Integer.toHexString(_magic) + " expected " + "0x" + Integer.toHexString(Ajp13RequestHeaders.MAGIC) + " " + this); _packetLength = Ajp13RequestPacket.getInt(_buffer); if (_packetLength > Ajp13Packet.MAX_PACKET_SIZE) throw new IOException("AJP13 packet (" + _packetLength + "bytes) too large for buffer"); } if (_buffer.length() < _packetLength) { if (total_filled<0) total_filled=0; total_filled+=fill(); if (_buffer.length() < _packetLength) return total_filled; } // Parse Header Buffer bufHeaderName = null; Buffer bufHeaderValue = null; int attr_type = 0; byte packetType = Ajp13RequestPacket.getByte(_buffer); switch (packetType) { case Ajp13Packet.FORWARD_REQUEST_ORDINAL: _handler.startForwardRequest(); break; case Ajp13Packet.CPING_REQUEST_ORDINAL: ((Ajp13Generator) _generator).sendCPong(); if(_header != null) { _buffers.returnBuffer(_header); _header = null; } if(_body != null) { _buffers.returnBuffer(_body); _body = null; } _buffer= null; reset(true); return -1; case Ajp13Packet.SHUTDOWN_ORDINAL: shutdownRequest(); return -1; default: // XXX Throw an Exception here?? Close // connection! Log.warn("AJP13 message type ({PING}: "+packetType+" ) not supported/recognized as an AJP request"); throw new IllegalStateException("PING is not implemented"); } _handler.parsedMethod(Ajp13RequestPacket.getMethod(_buffer)); _handler.parsedProtocol(Ajp13RequestPacket.getString(_buffer, _tok0)); _handler.parsedUri(Ajp13RequestPacket.getString(_buffer, _tok1)); _handler.parsedRemoteAddr(Ajp13RequestPacket.getString(_buffer, _tok1)); _handler.parsedRemoteHost(Ajp13RequestPacket.getString(_buffer, _tok1)); _handler.parsedServerName(Ajp13RequestPacket.getString(_buffer, _tok1)); _handler.parsedServerPort(Ajp13RequestPacket.getInt(_buffer)); _handler.parsedSslSecure(Ajp13RequestPacket.getBool(_buffer)); _headers = Ajp13RequestPacket.getInt(_buffer); for (int h=0;h<_headers;h++) { bufHeaderName = Ajp13RequestPacket.getHeaderName(_buffer, _tok0); bufHeaderValue = Ajp13RequestPacket.getString(_buffer, _tok1); if (bufHeaderName != null && bufHeaderName.toString().equals(Ajp13RequestHeaders.CONTENT_LENGTH)) { _contentLength = BufferUtil.toLong(bufHeaderValue); if (_contentLength == 0) _contentLength = HttpTokens.NO_CONTENT; } _handler.parsedHeader(bufHeaderName, bufHeaderValue); } attr_type = Ajp13RequestPacket.getByte(_buffer) & 0xff; while (attr_type != 0xFF) { switch (attr_type) { // XXX How does this plug into the web // containers // authentication? case Ajp13RequestHeaders.REMOTE_USER_ATTR: _handler.parsedRemoteUser(Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.AUTH_TYPE_ATTR: _handler.parsedAuthorizationType(Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.QUERY_STRING_ATTR: _handler.parsedQueryString(Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.JVM_ROUTE_ATTR: // XXX Using old Jetty 5 key, // should change! // Note used in // org.mortbay.jetty.servlet.HashSessionIdManager _handler.parsedRequestAttribute("org.mortbay.http.ajp.JVMRoute", Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.SSL_CERT_ATTR: _handler.parsedSslCert(Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.SSL_CIPHER_ATTR: _handler.parsedSslCipher(Ajp13RequestPacket.getString(_buffer, _tok1)); // SslSocketConnector.customize() break; case Ajp13RequestHeaders.SSL_SESSION_ATTR: _handler.parsedSslSession(Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.REQUEST_ATTR: _handler.parsedRequestAttribute(Ajp13RequestPacket.getString(_buffer, _tok0).toString(), Ajp13RequestPacket.getString(_buffer, _tok1)); break; // New Jk API? // Check if experimental or can they // assumed to be // supported case Ajp13RequestHeaders.SSL_KEYSIZE_ATTR: // This has been implemented in AJP13 as either a string or a integer. // Servlet specs say javax.servlet.request.key_size must be an Integer // Does it look like a string containing digits? int length = Ajp13RequestPacket.getInt(_buffer); if (length>0 && length<16) { // this must be a string length rather than a key length _buffer.skip(-2); _handler.parsedSslKeySize(Integer.parseInt(Ajp13RequestPacket.getString(_buffer, _tok1).toString())); } else _handler.parsedSslKeySize(length); break; // Used to lock down jk requests with a // secreate // key. case Ajp13RequestHeaders.SECRET_ATTR: // XXX Investigate safest way to // deal with // this... // should this tie into shutdown // packet? break; case Ajp13RequestHeaders.STORED_METHOD_ATTR: // XXX Confirm this should // really overide // previously parsed method? // _handler.parsedMethod(Ajp13PacketMethods.CACHE.get(Ajp13RequestPacket.getString())); break; case Ajp13RequestHeaders.CONTEXT_ATTR: _handler.parsedContextPath(Ajp13RequestPacket.getString(_buffer, _tok1)); break; case Ajp13RequestHeaders.SERVLET_PATH_ATTR: _handler.parsedServletPath(Ajp13RequestPacket.getString(_buffer, _tok1)); break; default: Log.warn("Unsupported Ajp13 Request Attribute {}", new Integer(attr_type)); break; } attr_type = Ajp13RequestPacket.getByte(_buffer) & 0xff; } _contentPosition = 0; switch ((int) _contentLength) { case HttpTokens.NO_CONTENT: _state = STATE_END; _handler.headerComplete(); _handler.messageComplete(_contentPosition); break; case HttpTokens.UNKNOWN_CONTENT: _generator.getBodyChunk(); if (_buffers != null && _body == null && _buffer == _header && _header.length() <= 0) { _body = _buffers.getBuffer(Ajp13Packet.MAX_PACKET_SIZE); _body.clear(); } _state = STATE_AJP13CHUNK_START; _handler.headerComplete(); // May recurse here! return total_filled; default: if (_buffers != null && _body == null && _buffer == _header && _contentLength > (_header.capacity() - _header.getIndex())) { _body = _buffers.getBuffer(Ajp13Packet.MAX_PACKET_SIZE); _body.clear(); } _state = STATE_AJP13CHUNK_START; _handler.headerComplete(); // May recurse here! return total_filled; } } Buffer chunk; while (_state>STATE_END) { switch (_state) { case STATE_AJP13CHUNK_START: if (_buffer.length()<6) { if (total_filled<0) total_filled=0; total_filled+=fill(); if (_buffer.length()<6) return total_filled; } int _magic=Ajp13RequestPacket.getInt(_buffer); if (_magic!=Ajp13RequestHeaders.MAGIC) { throw new IOException("Bad AJP13 rcv packet: "+"0x"+Integer.toHexString(_magic)+" expected "+"0x" +Integer.toHexString(Ajp13RequestHeaders.MAGIC)+" "+this); } _chunkPosition=0; _chunkLength=Ajp13RequestPacket.getInt(_buffer)-2; Ajp13RequestPacket.getInt(_buffer); if (_chunkLength==0) { _state=STATE_END; _generator.gotBody(); _handler.messageComplete(_contentPosition); return total_filled; } _state=STATE_AJP13CHUNK; case STATE_AJP13CHUNK: if (_buffer.length()<_chunkLength) { if (total_filled<0) total_filled=0; total_filled+=fill(); if (_buffer.length()<_chunkLength) return total_filled; } int remaining=_chunkLength-_chunkPosition; if (remaining==0) { _state=STATE_AJP13CHUNK_START; if (_contentPosition<_contentLength) { _generator.getBodyChunk(); } else { _generator.gotBody(); } return total_filled; } if (_buffer.length() 0) return true; if (_parser.isState(Ajp13Parser.STATE_END)) return false; // Handle simple end points. if (_endp == null) _parser.parseNext(); // Handle blocking end points else if (_endp.isBlocking()) { _parser.parseNext(); // parse until some progress is made (or IOException thrown for timeout) while (_content.length() == 0 && !_parser.isState(Ajp13Parser.STATE_END)) { // Try to get more _parser._content _parser.parseNext(); } } else // Handle non-blocking end point { long filled = _parser.parseNext(); boolean blocked = false; // parse until some progress is made (or // IOException thrown for timeout) while (_content.length() == 0 && !_parser.isState(Ajp13Parser.STATE_END)) { // if fill called, but no bytes read, // then block if (filled > 0) blocked = false; else if (filled == 0) { if (blocked) throw new InterruptedIOException("timeout"); blocked = true; _endp.blockReadable(_maxIdleTime); } // Try to get more _parser._content filled = _parser.parseNext(); } } return _content.length() > 0; } } }