321735 HttpClient onException called for buffer overflow.

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2213 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2010-08-16 05:46:17 +00:00
parent aff6cfed53
commit a456b2b600
5 changed files with 441 additions and 387 deletions

View File

@ -11,6 +11,7 @@ jetty-7.2-SNAPSHOT
+ 321232 BasicAuthenticator ignores bad Authorization header.
+ 321307 HashSessionManager calls passivation listeners.
+ 321730 SelectChannelEndPoint prints to System.err
+ 321735 HttpClient onException called for buffer overflow.
+ 322683 RewriteHandler thread safety
+ JETTY-912 added per exchange timeout api
+ JETTY-1245 Do not use direct buffers with NIO SSL

View File

@ -190,14 +190,14 @@ public class HttpConnection implements Connection
_exchange.associate(this);
}
if (_exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
{
no_progress = 0;
commitRequest();
}
try
{
if (_exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
{
no_progress = 0;
commitRequest();
}
long io = 0;
_endp.flush();
@ -284,6 +284,8 @@ public class HttpConnection implements Connection
if (e instanceof ThreadDeath)
throw (ThreadDeath)e;
failed = true;
synchronized (this)
{
if (_exchange != null)
@ -296,19 +298,20 @@ public class HttpConnection implements Connection
_exchange.getEventListener().onException(e);
}
}
else
{
if (e instanceof IOException)
throw (IOException)e;
if (e instanceof Error)
throw (Error)e;
if (e instanceof RuntimeException)
throw (RuntimeException)e;
throw new RuntimeException(e);
}
}
failed = true;
if (e instanceof IOException)
throw (IOException)e;
if (e instanceof Error)
throw (Error)e;
if (e instanceof RuntimeException)
throw (RuntimeException)e;
throw new RuntimeException(e);
}
finally
{

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.log.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -114,6 +115,47 @@ public class HttpHeadersTest
httpClient.stop();
}
}
@Test
public void testHttpHeadersSize() throws Exception
{
HttpClient httpClient = new HttpClient();
httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
httpClient.start();
try
{
String requestUrl = "http://localhost:" + _port + "/header";
ContentExchange exchange = new ContentExchange()
{
@Override
protected void onException(Throwable x)
{
// suppress exception
Log.ignore(x);
}
};
exchange.setURL(requestUrl);
exchange.setMethod(HttpMethods.GET);
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 1024; j++)
{
exchange.addRequestHeader("header" + i + "-" + j,"v");
}
}
httpClient.send(exchange);
int state = exchange.waitForDone();
assertEquals(HttpExchange.STATUS_EXCEPTED, state);
}
finally
{
httpClient.stop();
}
}
private static class TestHeaderHandler extends AbstractHandler
{

View File

@ -387,395 +387,402 @@ public class HttpGenerator extends AbstractGenerator
boolean has_server = false;
if (isRequest())
try
{
_persistent=true;
if (_version == HttpVersions.HTTP_0_9_ORDINAL)
if (isRequest())
{
_contentLength = HttpTokens.NO_CONTENT;
_header.put(_method);
_header.put((byte)' ');
_header.put(_uri.getBytes("utf-8")); // TODO WRONG!
_header.put(HttpTokens.CRLF);
_state = STATE_FLUSHING;
_noContent=true;
return;
}
else
{
_header.put(_method);
_header.put((byte)' ');
_header.put(_uri.getBytes("utf-8")); // TODO WRONG!
_header.put((byte)' ');
_header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
_header.put(HttpTokens.CRLF);
}
}
else
{
// Responses
_persistent=true;
if (_version == HttpVersions.HTTP_0_9_ORDINAL)
{
_persistent = false;
_contentLength = HttpTokens.EOF_CONTENT;
_state = STATE_CONTENT;
return;
}
else
{
if (_persistent==null)
_persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL);
// add response line
Status status = _status<__status.length?__status[_status]:null;
if (status==null)
if (_version == HttpVersions.HTTP_0_9_ORDINAL)
{
_header.put(HttpVersions.HTTP_1_1_BUFFER);
_header.put((byte) ' ');
_header.put((byte) ('0' + _status / 100));
_header.put((byte) ('0' + (_status % 100) / 10));
_header.put((byte) ('0' + (_status % 10)));
_header.put((byte) ' ');
if (_reason==null)
_contentLength = HttpTokens.NO_CONTENT;
_header.put(_method);
_header.put((byte)' ');
_header.put(_uri.getBytes("utf-8")); // TODO WRONG!
_header.put(HttpTokens.CRLF);
_state = STATE_FLUSHING;
_noContent=true;
return;
}
else
{
_header.put(_method);
_header.put((byte)' ');
_header.put(_uri.getBytes("utf-8")); // TODO WRONG!
_header.put((byte)' ');
_header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
_header.put(HttpTokens.CRLF);
}
}
else
{
// Responses
if (_version == HttpVersions.HTTP_0_9_ORDINAL)
{
_persistent = false;
_contentLength = HttpTokens.EOF_CONTENT;
_state = STATE_CONTENT;
return;
}
else
{
if (_persistent==null)
_persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL);
// add response line
Status status = _status<__status.length?__status[_status]:null;
if (status==null)
{
_header.put(HttpVersions.HTTP_1_1_BUFFER);
_header.put((byte) ' ');
_header.put((byte) ('0' + _status / 100));
_header.put((byte) ('0' + (_status % 100) / 10));
_header.put((byte) ('0' + (_status % 10)));
}
else
_header.put(_reason);
_header.put(HttpTokens.CRLF);
}
else
{
if (_reason==null)
_header.put(status._responseLine);
else
{
_header.put(status._schemeCode);
_header.put(_reason);
_header.put(HttpTokens.CRLF);
}
}
if (_status<200 && _status>=100 )
{
_noContent=true;
_content=null;
if (_buffer!=null)
_buffer.clear();
// end the header.
if (_status!=101 )
{
_header.put(HttpTokens.CRLF);
_state = STATE_CONTENT;
return;
}
}
else if (_status==204 || _status==304)
{
_noContent=true;
_content=null;
if (_buffer!=null)
_buffer.clear();
}
}
}
// Add headers
if (_status>=200 && _date!=null)
{
_header.put(HttpHeaders.DATE_BUFFER);
_header.put((byte)':');
_header.put((byte)' ');
_header.put(_date);
_header.put(CRLF);
}
// key field values
HttpFields.Field content_length = null;
HttpFields.Field transfer_encoding = null;
boolean keep_alive = false;
boolean close=false;
boolean content_type=false;
StringBuilder connection = null;
if (fields != null)
{
int s=fields.size();
for (int f=0;f<s;f++)
{
HttpFields.Field field = fields.getField(f);
if (field==null)
continue;
switch (field.getNameOrdinal())
{
case HttpHeaders.CONTENT_LENGTH_ORDINAL:
content_length = field;
_contentLength = field.getLongValue();
if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
content_length = null;
// write the field to the header buffer
field.put(_header);
break;
case HttpHeaders.CONTENT_TYPE_ORDINAL:
if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
// write the field to the header buffer
content_type=true;
field.put(_header);
break;
case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
if (_version == HttpVersions.HTTP_1_1_ORDINAL)
transfer_encoding = field;
// Do NOT add yet!
break;
case HttpHeaders.CONNECTION_ORDINAL:
if (isRequest())
field.put(_header);
int connection_value = field.getValueOrdinal();
switch (connection_value)
{
case -1:
{
String[] values = field.getValue().split(",");
for (int i=0;values!=null && i<values.length;i++)
{
CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
if (cb!=null)
{
switch(cb.getOrdinal())
{
case HttpHeaderValues.CLOSE_ORDINAL:
close=true;
if (isResponse())
_persistent=false;
keep_alive=false;
if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
_contentLength = HttpTokens.EOF_CONTENT;
break;
case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
if (_version == HttpVersions.HTTP_1_0_ORDINAL)
{
keep_alive = true;
if (isResponse())
_persistent = true;
}
break;
default:
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(values[i]);
}
}
else
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(values[i]);
}
}
break;
}
case HttpHeaderValues.UPGRADE_ORDINAL:
{
// special case for websocket connection ordering
if (isResponse())
{
field.put(_header);
continue;
}
}
case HttpHeaderValues.CLOSE_ORDINAL:
{
close=true;
if (isResponse())
_persistent=false;
if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
_contentLength = HttpTokens.EOF_CONTENT;
break;
}
case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
{
if (_version == HttpVersions.HTTP_1_0_ORDINAL)
{
keep_alive = true;
if (isResponse())
_persistent=true;
}
break;
}
default:
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(field.getValue());
}
}
// Do NOT add yet!
break;
case HttpHeaders.SERVER_ORDINAL:
if (getSendServerVersion())
{
has_server=true;
field.put(_header);
}
break;
default:
// write the field to the header buffer
field.put(_header);
}
}
}
// Calculate how to end _content and connection, _content length and transfer encoding
// settings.
// From RFC 2616 4.4:
// 1. No body for 1xx, 204, 304 & HEAD response
// 2. Force _content-length?
// 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
// 4. Content-Length
// 5. multipart/byteranges
// 6. close
switch ((int) _contentLength)
{
case HttpTokens.UNKNOWN_CONTENT:
// It may be that we have no _content, or perhaps _content just has not been
// written yet?
// Response known not to have a body
if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304))
_contentLength = HttpTokens.NO_CONTENT;
else if (_last)
{
// we have seen all the _content there is
_contentLength = _contentWritten;
if (content_length == null && (isResponse() || _contentLength>0 || content_type ))
{
// known length but not actually set.
_header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
_header.put(HttpTokens.COLON);
_header.put((byte) ' ');
BufferUtil.putDecLong(_header, _contentLength);
if (_reason==null)
{
_header.put((byte) ('0' + _status / 100));
_header.put((byte) ('0' + (_status % 100) / 10));
_header.put((byte) ('0' + (_status % 10)));
}
else
_header.put(_reason);
_header.put(HttpTokens.CRLF);
}
}
else
{
// No idea, so we must assume that a body is coming
_contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT)
else
{
if (_reason==null)
_header.put(status._responseLine);
else
{
_header.put(status._schemeCode);
_header.put(_reason);
_header.put(HttpTokens.CRLF);
}
}
if (_status<200 && _status>=100 )
{
_contentLength=HttpTokens.NO_CONTENT;
_noContent=true;
_content=null;
if (_buffer!=null)
_buffer.clear();
// end the header.
if (_status!=101 )
{
_header.put(HttpTokens.CRLF);
_state = STATE_CONTENT;
return;
}
}
else if (_status==204 || _status==304)
{
_noContent=true;
_content=null;
if (_buffer!=null)
_buffer.clear();
}
}
break;
case HttpTokens.NO_CONTENT:
if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304)
_header.put(CONTENT_LENGTH_0);
break;
case HttpTokens.EOF_CONTENT:
_persistent = isRequest();
break;
case HttpTokens.CHUNKED_CONTENT:
break;
default:
// TODO - maybe allow forced chunking by setting te ???
break;
}
// Add transfer_encoding if needed
if (_contentLength == HttpTokens.CHUNKED_CONTENT)
{
// try to use user supplied encoding as it may have other values.
if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
{
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValues.CHUNKED))
transfer_encoding.put(_header);
else
throw new IllegalArgumentException("BAD TE");
}
else
_header.put(TRANSFER_ENCODING_CHUNKED);
}
// Handle connection if need be
if (_contentLength==HttpTokens.EOF_CONTENT)
{
keep_alive=false;
_persistent=false;
}
if (isResponse())
{
if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
// Add headers
if (_status>=200 && _date!=null)
{
_header.put(CONNECTION_CLOSE);
if (connection!=null)
{
_header.setPutIndex(_header.putIndex()-2);
_header.put((byte)',');
_header.put(connection.toString().getBytes());
_header.put(CRLF);
}
}
else if (keep_alive)
{
_header.put(CONNECTION_KEEP_ALIVE);
if (connection!=null)
{
_header.setPutIndex(_header.putIndex()-2);
_header.put((byte)',');
_header.put(connection.toString().getBytes());
_header.put(CRLF);
}
}
else if (connection!=null)
{
_header.put(CONNECTION_);
_header.put(connection.toString().getBytes());
_header.put(HttpHeaders.DATE_BUFFER);
_header.put((byte)':');
_header.put((byte)' ');
_header.put(_date);
_header.put(CRLF);
}
// key field values
HttpFields.Field content_length = null;
HttpFields.Field transfer_encoding = null;
boolean keep_alive = false;
boolean close=false;
boolean content_type=false;
StringBuilder connection = null;
if (fields != null)
{
int s=fields.size();
for (int f=0;f<s;f++)
{
HttpFields.Field field = fields.getField(f);
if (field==null)
continue;
switch (field.getNameOrdinal())
{
case HttpHeaders.CONTENT_LENGTH_ORDINAL:
content_length = field;
_contentLength = field.getLongValue();
if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
content_length = null;
// write the field to the header buffer
field.put(_header);
break;
case HttpHeaders.CONTENT_TYPE_ORDINAL:
if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
// write the field to the header buffer
content_type=true;
field.put(_header);
break;
case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
if (_version == HttpVersions.HTTP_1_1_ORDINAL)
transfer_encoding = field;
// Do NOT add yet!
break;
case HttpHeaders.CONNECTION_ORDINAL:
if (isRequest())
field.put(_header);
int connection_value = field.getValueOrdinal();
switch (connection_value)
{
case -1:
{
String[] values = field.getValue().split(",");
for (int i=0;values!=null && i<values.length;i++)
{
CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
if (cb!=null)
{
switch(cb.getOrdinal())
{
case HttpHeaderValues.CLOSE_ORDINAL:
close=true;
if (isResponse())
_persistent=false;
keep_alive=false;
if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
_contentLength = HttpTokens.EOF_CONTENT;
break;
case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
if (_version == HttpVersions.HTTP_1_0_ORDINAL)
{
keep_alive = true;
if (isResponse())
_persistent = true;
}
break;
default:
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(values[i]);
}
}
else
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(values[i]);
}
}
break;
}
case HttpHeaderValues.UPGRADE_ORDINAL:
{
// special case for websocket connection ordering
if (isResponse())
{
field.put(_header);
continue;
}
}
case HttpHeaderValues.CLOSE_ORDINAL:
{
close=true;
if (isResponse())
_persistent=false;
if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
_contentLength = HttpTokens.EOF_CONTENT;
break;
}
case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
{
if (_version == HttpVersions.HTTP_1_0_ORDINAL)
{
keep_alive = true;
if (isResponse())
_persistent=true;
}
break;
}
default:
{
if (connection==null)
connection=new StringBuilder();
else
connection.append(',');
connection.append(field.getValue());
}
}
// Do NOT add yet!
break;
case HttpHeaders.SERVER_ORDINAL:
if (getSendServerVersion())
{
has_server=true;
field.put(_header);
}
break;
default:
// write the field to the header buffer
field.put(_header);
}
}
}
// Calculate how to end _content and connection, _content length and transfer encoding
// settings.
// From RFC 2616 4.4:
// 1. No body for 1xx, 204, 304 & HEAD response
// 2. Force _content-length?
// 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
// 4. Content-Length
// 5. multipart/byteranges
// 6. close
switch ((int) _contentLength)
{
case HttpTokens.UNKNOWN_CONTENT:
// It may be that we have no _content, or perhaps _content just has not been
// written yet?
// Response known not to have a body
if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304))
_contentLength = HttpTokens.NO_CONTENT;
else if (_last)
{
// we have seen all the _content there is
_contentLength = _contentWritten;
if (content_length == null && (isResponse() || _contentLength>0 || content_type ))
{
// known length but not actually set.
_header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
_header.put(HttpTokens.COLON);
_header.put((byte) ' ');
BufferUtil.putDecLong(_header, _contentLength);
_header.put(HttpTokens.CRLF);
}
}
else
{
// No idea, so we must assume that a body is coming
_contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT)
{
_contentLength=HttpTokens.NO_CONTENT;
_noContent=true;
}
}
break;
case HttpTokens.NO_CONTENT:
if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304)
_header.put(CONTENT_LENGTH_0);
break;
case HttpTokens.EOF_CONTENT:
_persistent = isRequest();
break;
case HttpTokens.CHUNKED_CONTENT:
break;
default:
// TODO - maybe allow forced chunking by setting te ???
break;
}
// Add transfer_encoding if needed
if (_contentLength == HttpTokens.CHUNKED_CONTENT)
{
// try to use user supplied encoding as it may have other values.
if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
{
String c = transfer_encoding.getValue();
if (c.endsWith(HttpHeaderValues.CHUNKED))
transfer_encoding.put(_header);
else
throw new IllegalArgumentException("BAD TE");
}
else
_header.put(TRANSFER_ENCODING_CHUNKED);
}
// Handle connection if need be
if (_contentLength==HttpTokens.EOF_CONTENT)
{
keep_alive=false;
_persistent=false;
}
if (isResponse())
{
if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
{
_header.put(CONNECTION_CLOSE);
if (connection!=null)
{
_header.setPutIndex(_header.putIndex()-2);
_header.put((byte)',');
_header.put(connection.toString().getBytes());
_header.put(CRLF);
}
}
else if (keep_alive)
{
_header.put(CONNECTION_KEEP_ALIVE);
if (connection!=null)
{
_header.setPutIndex(_header.putIndex()-2);
_header.put((byte)',');
_header.put(connection.toString().getBytes());
_header.put(CRLF);
}
}
else if (connection!=null)
{
_header.put(CONNECTION_);
_header.put(connection.toString().getBytes());
_header.put(CRLF);
}
}
if (!has_server && _status>199 && getSendServerVersion())
_header.put(SERVER);
// end the header.
_header.put(HttpTokens.CRLF);
_state = STATE_CONTENT;
}
catch(ArrayIndexOutOfBoundsException e)
{
throw new RuntimeException("Header>"+_header.capacity(),e);
}
if (!has_server && _status>199 && getSendServerVersion())
_header.put(SERVER);
// end the header.
_header.put(HttpTokens.CRLF);
_state = STATE_CONTENT;
}

View File

@ -418,7 +418,7 @@ public class HttpConnectionTest
writer.close();
throw new RuntimeException("SHOULD NOT GET HERE");
}
catch(ArrayIndexOutOfBoundsException e)
catch(Exception e)
{
Log.debug(e);
Log.info("correctly ignored "+e);
@ -436,6 +436,7 @@ public class HttpConnectionTest
"\015\012"
);
System.err.println(response);
offset = checkContains(response, offset, "HTTP/1.1 500");
}
catch(Exception e)