327109 Fixed AJP handling of empty packets

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2327 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2010-10-07 00:21:41 +00:00
parent 0e6f62664d
commit 143229f544
3 changed files with 897 additions and 7 deletions

View File

@ -3,6 +3,7 @@ jetty-7.2.0.RC1-SNAPSHOT
+ 297154 add source distribution artifact
+ 323985 Xmlconfiguration pulls start.jar config properties
+ 326734 Configure Digest maxNonceAge with Security handler init param
+ 327109 Fixed AJP handling of empty packets
jetty-7.2.0.RC0 1 Oct 2010
+ 314087 Simplified SelectorManager

View File

@ -36,7 +36,8 @@ 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 final static int STATE_AJP13CHUNK_START_AFTER_LENGTH = 2;
private final static int STATE_AJP13CHUNK = 3;
private int _state = STATE_START;
private long _contentLength;
@ -519,12 +520,12 @@ public class Ajp13Parser implements Parser
switch (_state)
{
case STATE_AJP13CHUNK_START:
if (_buffer.length()<6)
if (_buffer.length()<4)
{
if (total_filled<0)
total_filled=0;
total_filled+=fill();
if (_buffer.length()<6)
if (_buffer.length()<4)
return total_filled;
}
int _magic=Ajp13RequestPacket.getInt(_buffer);
@ -534,15 +535,27 @@ public class Ajp13Parser implements Parser
+Integer.toHexString(Ajp13RequestHeaders.MAGIC)+" "+this);
}
_chunkPosition=0;
_chunkLength=Ajp13RequestPacket.getInt(_buffer)-2;
Ajp13RequestPacket.getInt(_buffer);
if (_chunkLength==0)
int rawChunkLength=Ajp13RequestPacket.getInt(_buffer);
_chunkLength = rawChunkLength - 2;
if (rawChunkLength==0 || _chunkLength==0)
{
_state=STATE_END;
_generator.gotBody();
_handler.messageComplete(_contentPosition);
return total_filled;
}
_state=STATE_AJP13CHUNK_START_AFTER_LENGTH;
case STATE_AJP13CHUNK_START_AFTER_LENGTH:
if (_buffer.length() < 2)
{
if (total_filled < 0)
total_filled = 0;
total_filled += fill();
if (_buffer.length() < 2)
return total_filled;
}
Ajp13RequestPacket.getInt(_buffer);
_state = STATE_AJP13CHUNK;
case STATE_AJP13CHUNK:

View File

@ -0,0 +1,876 @@
// ========================================================================
// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.ajp;
import java.io.IOException;
import java.io.InterruptedIOException;
import javax.servlet.ServletInputStream;
import org.eclipse.jetty.http.HttpTokens;
import org.eclipse.jetty.http.Parser;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferUtil;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.View;
import org.eclipse.jetty.util.log.Log;
/**
*
*/
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.getHeader();
_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:
(_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:
//XXX JASPI how does this make sense?
_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.eclipse.jetty.servlet.HashSessionIdManager
_handler.parsedRequestAttribute("org.eclipse.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();
_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();
_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()<remaining)
{
remaining=_buffer.length();
}
chunk=Ajp13RequestPacket.get(_buffer,remaining);
_contentPosition+=chunk.length();
_chunkPosition+=chunk.length();
_contentView.update(chunk);
remaining=_chunkLength-_chunkPosition;
if (remaining==0)
{
_state=STATE_AJP13CHUNK_START;
if (_contentPosition<_contentLength || _contentLength == HttpTokens.UNKNOWN_CONTENT)
{
_generator.getBodyChunk();
}
else
{
_generator.gotBody();
}
}
_handler.content(chunk);
return total_filled;
default:
throw new IllegalStateException("Invalid Content State");
}
}
return total_filled;
}
/* ------------------------------------------------------------------------------- */
public void reset(boolean returnBuffers)
{
_state = STATE_START;
_contentLength = HttpTokens.UNKNOWN_CONTENT;
_contentPosition = 0;
_length = 0;
_packetLength = 0;
if (_body != null)
{
if (_body.hasContent())
{
_header.setMarkIndex(-1);
_header.compact();
// TODO if pipelined requests received after big
// input - maybe this is not good?.
_body.skip(_header.put(_body));
}
if (_body.length() == 0)
{
if (_buffers != null && returnBuffers)
_buffers.returnBuffer(_body);
_body = null;
}
else
{
_body.setMarkIndex(-1);
_body.compact();
}
}
if (_header != null)
{
_header.setMarkIndex(-1);
if (!_header.hasContent() && _buffers != null && returnBuffers)
{
_buffers.returnBuffer(_header);
_header = null;
_buffer = null;
}
else
{
_header.compact();
_tok0.update(_header);
_tok0.update(0, 0);
_tok1.update(_header);
_tok1.update(0, 0);
}
}
_buffer = _header;
}
/* ------------------------------------------------------------------------------- */
Buffer getHeaderBuffer()
{
return _buffer;
}
private void shutdownRequest()
{
_state = STATE_END;
if(!Ajp13SocketConnector.__allowShutdown)
{
Log.warn("AJP13: Shutdown Request is Denied, allowShutdown is set to false!!!");
return;
}
if(Ajp13SocketConnector.__secretWord != null)
{
Log.warn("AJP13: Validating Secret Word");
try
{
String secretWord = Ajp13RequestPacket.getString(_buffer, _tok1).toString();
if(!Ajp13SocketConnector.__secretWord.equals(secretWord))
{
Log.warn("AJP13: Shutdown Request Denied, Invalid Sercret word!!!");
throw new IllegalStateException("AJP13: Secret Word is Invalid: Peer has requested shutdown but, Secret Word did not match");
}
}
catch (Exception e)
{
Log.warn("AJP13: Secret Word is Required!!!");
Log.debug(e);
throw new IllegalStateException("AJP13: Secret Word is Required: Peer has requested shutdown but, has not provided a Secret Word");
}
Log.warn("AJP13: Shutdown Request is Denied, allowShutdown is set to false!!!");
return;
}
Log.warn("AJP13: Peer Has Requested for Shutdown!!!");
Log.warn("AJP13: Jetty 6 is shutting down !!!");
System.exit(0);
}
/* ------------------------------------------------------------------------------- */
public interface EventHandler
{
// public void shutdownRequest() throws IOException;
// public void cpingRequest() throws IOException;
public void content(Buffer ref) throws IOException;
public void headerComplete() throws IOException;
public void messageComplete(long contextLength) throws IOException;
public void parsedHeader(Buffer name, Buffer value) throws IOException;
public void parsedMethod(Buffer method) throws IOException;
public void parsedProtocol(Buffer protocol) throws IOException;
public void parsedQueryString(Buffer value) throws IOException;
public void parsedRemoteAddr(Buffer addr) throws IOException;
public void parsedRemoteHost(Buffer host) throws IOException;
public void parsedRequestAttribute(String key, Buffer value) throws IOException;
public void parsedRequestAttribute(String key, int value) throws IOException;
public void parsedServerName(Buffer name) throws IOException;
public void parsedServerPort(int port) throws IOException;
public void parsedSslSecure(boolean secure) throws IOException;
public void parsedUri(Buffer uri) throws IOException;
public void startForwardRequest() throws IOException;
public void parsedAuthorizationType(Buffer authType) throws IOException;
public void parsedRemoteUser(Buffer remoteUser) throws IOException;
public void parsedServletPath(Buffer servletPath) throws IOException;
public void parsedContextPath(Buffer context) throws IOException;
public void parsedSslCert(Buffer sslCert) throws IOException;
public void parsedSslCipher(Buffer sslCipher) throws IOException;
public void parsedSslSession(Buffer sslSession) throws IOException;
public void parsedSslKeySize(int keySize) throws IOException;
}
/* ------------------------------------------------------------ */
/**
* TODO Make this common with HttpParser
*
*/
public static class Input extends ServletInputStream
{
private Ajp13Parser _parser;
private EndPoint _endp;
private long _maxIdleTime;
private View _content;
/* ------------------------------------------------------------ */
public Input(Ajp13Parser parser, long maxIdleTime)
{
_parser = parser;
_endp = parser._endp;
_maxIdleTime = maxIdleTime;
_content = _parser._contentView;
}
/* ------------------------------------------------------------ */
@Override
public int read() throws IOException
{
int c = -1;
if (blockForContent())
c = 0xff & _content.get();
return c;
}
/* ------------------------------------------------------------ */
/*
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(byte[] b, int off, int len) throws IOException
{
int l = -1;
if (blockForContent())
l = _content.get(b, off, len);
return l;
}
/* ------------------------------------------------------------ */
private boolean blockForContent() throws IOException
{
if (_content.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;
}
}
}