Resolved issue: NETTY-133 Limit the length of HTTP header lines.
* Added maxHeaderSize option * Added maxInitialLineLength option * Overall HTTP code cleanup
This commit is contained in:
parent
c9b3122b6c
commit
4a72aafd56
@ -44,7 +44,7 @@ public class HttpServerPipelineFactory implements ChannelPipelineFactory {
|
|||||||
// Create a default pipeline implementation.
|
// Create a default pipeline implementation.
|
||||||
ChannelPipeline pipeline = pipeline();
|
ChannelPipeline pipeline = pipeline();
|
||||||
|
|
||||||
pipeline.addLast("decoder", new HttpRequestDecoder(8192));
|
pipeline.addLast("decoder", new HttpRequestDecoder(8192, 8192, 8192));
|
||||||
pipeline.addLast("encoder", new HttpResponseEncoder());
|
pipeline.addLast("encoder", new HttpResponseEncoder());
|
||||||
pipeline.addLast("handler", handler);
|
pipeline.addLast("handler", handler);
|
||||||
return pipeline;
|
return pipeline;
|
||||||
|
@ -29,6 +29,7 @@ import org.jboss.netty.buffer.ChannelBuffer;
|
|||||||
import org.jboss.netty.buffer.ChannelBuffers;
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
import org.jboss.netty.channel.Channel;
|
import org.jboss.netty.channel.Channel;
|
||||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
|
||||||
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
|
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,9 +47,12 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
private static final Pattern HEADER_PATTERN = Pattern.compile(
|
private static final Pattern HEADER_PATTERN = Pattern.compile(
|
||||||
"^\\s*(\\S+)\\s*:\\s*(.*)\\s*$");
|
"^\\s*(\\S+)\\s*:\\s*(.*)\\s*$");
|
||||||
|
|
||||||
|
private final int maxInitialLineLength;
|
||||||
|
private final int maxHeaderSize;
|
||||||
private final int maxChunkSize;
|
private final int maxChunkSize;
|
||||||
protected volatile HttpMessage message;
|
protected volatile HttpMessage message;
|
||||||
private volatile ChannelBuffer content;
|
private volatile ChannelBuffer content;
|
||||||
|
private volatile int headerSize;
|
||||||
private volatile int chunkSize;
|
private volatile int chunkSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,16 +78,28 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected HttpMessageDecoder() {
|
protected HttpMessageDecoder() {
|
||||||
this(0);
|
this(8192, 8192, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpMessageDecoder(int maxChunkSize) {
|
protected HttpMessageDecoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
|
||||||
super(State.SKIP_CONTROL_CHARS, true);
|
super(State.SKIP_CONTROL_CHARS, true);
|
||||||
|
if (maxInitialLineLength <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"maxInitialLineLength must be a positive integer: " +
|
||||||
|
maxInitialLineLength);
|
||||||
|
}
|
||||||
|
if (maxHeaderSize <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"maxHeaderSize must be a positive integer: " +
|
||||||
|
maxChunkSize);
|
||||||
|
}
|
||||||
if (maxChunkSize < 0) {
|
if (maxChunkSize < 0) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"maxChunkSize must not be a negative integer: " +
|
"maxChunkSize must not be a negative integer: " +
|
||||||
maxChunkSize);
|
maxChunkSize);
|
||||||
}
|
}
|
||||||
|
this.maxInitialLineLength = maxInitialLineLength;
|
||||||
|
this.maxHeaderSize = maxHeaderSize;
|
||||||
this.maxChunkSize = maxChunkSize;
|
this.maxChunkSize = maxChunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +119,16 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case READ_INITIAL: {
|
case READ_INITIAL: {
|
||||||
readInitial(buffer);
|
String[] initialLine = splitInitialLine(readLine(buffer, maxInitialLineLength));
|
||||||
|
if (initialLine.length < 3) {
|
||||||
|
// Invalid initial line - ignore.
|
||||||
|
checkpoint(State.SKIP_CONTROL_CHARS);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = createMessage(initialLine);
|
||||||
|
checkpoint(State.READ_HEADER);
|
||||||
|
headerSize = 0;
|
||||||
}
|
}
|
||||||
case READ_HEADER: {
|
case READ_HEADER: {
|
||||||
State nextState = readHeaders(buffer);
|
State nextState = readHeaders(buffer);
|
||||||
@ -200,7 +225,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
* read chunk, read and ignore the CRLF and repeat until 0
|
* read chunk, read and ignore the CRLF and repeat until 0
|
||||||
*/
|
*/
|
||||||
case READ_CHUNK_SIZE: {
|
case READ_CHUNK_SIZE: {
|
||||||
String line = readIntoCurrentLine(buffer);
|
String line = readLine(buffer, maxInitialLineLength);
|
||||||
int chunkSize = getChunkSize(line);
|
int chunkSize = getChunkSize(line);
|
||||||
this.chunkSize = chunkSize;
|
this.chunkSize = chunkSize;
|
||||||
if (chunkSize == 0) {
|
if (chunkSize == 0) {
|
||||||
@ -268,18 +293,20 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case READ_CHUNK_FOOTER: {
|
case READ_CHUNK_FOOTER: {
|
||||||
String line = readIntoCurrentLine(buffer);
|
// Skip the footer; does anyone use it?
|
||||||
if (line.trim().length() == 0) {
|
try {
|
||||||
if (maxChunkSize == 0) {
|
if (!skipLine(buffer)) {
|
||||||
// Chunked encoding disabled.
|
if (maxChunkSize == 0) {
|
||||||
return reset();
|
// Chunked encoding disabled.
|
||||||
} else {
|
return reset();
|
||||||
reset();
|
} else {
|
||||||
// The last chunk, which is empty
|
reset();
|
||||||
return HttpChunk.LAST_CHUNK;
|
// The last chunk, which is empty
|
||||||
|
return HttpChunk.LAST_CHUNK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} finally {
|
||||||
checkpoint(State.READ_CHUNK_FOOTER);
|
checkpoint();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -324,9 +351,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private State readHeaders(ChannelBuffer buffer) {
|
private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException {
|
||||||
message.clearHeaders();
|
message.clearHeaders();
|
||||||
String line = readIntoCurrentLine(buffer);
|
String line = readHeader(buffer);
|
||||||
String lastHeader = null;
|
String lastHeader = null;
|
||||||
while (line.length() != 0) {
|
while (line.length() != 0) {
|
||||||
if (line.startsWith(" ") || line.startsWith("\t")) {
|
if (line.startsWith(" ") || line.startsWith("\t")) {
|
||||||
@ -341,7 +368,7 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
message.addHeader(header[0], header[1]);
|
message.addHeader(header[0], header[1]);
|
||||||
lastHeader = header[0];
|
lastHeader = header[0];
|
||||||
}
|
}
|
||||||
line = readIntoCurrentLine(buffer);
|
line = readHeader(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
State nextState;
|
State nextState;
|
||||||
@ -355,8 +382,38 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
return nextState;
|
return nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String readHeader(ChannelBuffer buffer) throws TooLongFrameException {
|
||||||
|
StringBuilder sb = new StringBuilder(64);
|
||||||
|
int headerSize = this.headerSize;
|
||||||
|
while (true) {
|
||||||
|
byte nextByte = buffer.readByte();
|
||||||
|
if (nextByte == HttpCodecUtil.CR) {
|
||||||
|
nextByte = buffer.readByte();
|
||||||
|
if (nextByte == HttpCodecUtil.LF) {
|
||||||
|
this.headerSize = headerSize + 2;
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nextByte == HttpCodecUtil.LF) {
|
||||||
|
this.headerSize = headerSize + 1;
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Abort decoding if the header part is too large.
|
||||||
|
if (headerSize >= maxHeaderSize) {
|
||||||
|
throw new TooLongFrameException(
|
||||||
|
"HTTP header is larger than " +
|
||||||
|
maxHeaderSize + " bytes.");
|
||||||
|
|
||||||
|
}
|
||||||
|
headerSize ++;
|
||||||
|
sb.append((char) nextByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract boolean isDecodingRequest();
|
protected abstract boolean isDecodingRequest();
|
||||||
protected abstract void readInitial(ChannelBuffer buffer) throws Exception;
|
protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
|
||||||
|
|
||||||
private int getChunkSize(String hex) {
|
private int getChunkSize(String hex) {
|
||||||
hex = hex.trim();
|
hex = hex.trim();
|
||||||
@ -371,8 +428,9 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
return Integer.parseInt(hex, 16);
|
return Integer.parseInt(hex, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String readIntoCurrentLine(ChannelBuffer buffer) {
|
private String readLine(ChannelBuffer buffer, int maxLineLength) throws TooLongFrameException {
|
||||||
StringBuilder sb = new StringBuilder(64);
|
StringBuilder sb = new StringBuilder(64);
|
||||||
|
int lineLength = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
byte nextByte = buffer.readByte();
|
byte nextByte = buffer.readByte();
|
||||||
if (nextByte == HttpCodecUtil.CR) {
|
if (nextByte == HttpCodecUtil.CR) {
|
||||||
@ -385,12 +443,42 @@ public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDec
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (lineLength >= maxLineLength) {
|
||||||
|
throw new TooLongFrameException(
|
||||||
|
"An HTTP line is larger than " + maxLineLength +
|
||||||
|
" bytes.");
|
||||||
|
}
|
||||||
|
lineLength ++;
|
||||||
sb.append((char) nextByte);
|
sb.append((char) nextByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String[] splitInitial(String sb) {
|
/**
|
||||||
|
* Returns {@code true} if only if the skipped line was not empty.
|
||||||
|
* Please note that an empty line is also skipped, while {@code} false is
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
private boolean skipLine(ChannelBuffer buffer) {
|
||||||
|
int lineLength = 0;
|
||||||
|
while (true) {
|
||||||
|
byte nextByte = buffer.readByte();
|
||||||
|
if (nextByte == HttpCodecUtil.CR) {
|
||||||
|
nextByte = buffer.readByte();
|
||||||
|
if (nextByte == HttpCodecUtil.LF) {
|
||||||
|
return lineLength != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nextByte == HttpCodecUtil.LF) {
|
||||||
|
return lineLength != 0;
|
||||||
|
}
|
||||||
|
else if (!Character.isWhitespace((char) nextByte)) {
|
||||||
|
lineLength ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] splitInitialLine(String sb) {
|
||||||
Matcher m = INITIAL_PATTERN.matcher(sb);
|
Matcher m = INITIAL_PATTERN.matcher(sb);
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
return new String[] { m.group(1), m.group(2), m.group(3) };
|
return new String[] { m.group(1), m.group(2), m.group(3) };
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jboss.netty.handler.codec.http;
|
package org.jboss.netty.handler.codec.http;
|
||||||
|
|
||||||
import org.jboss.netty.buffer.ChannelBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* decodes an http request.
|
* decodes an http request.
|
||||||
@ -37,17 +36,15 @@ public class HttpRequestDecoder extends HttpMessageDecoder {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpRequestDecoder(int maxChunkSize) {
|
public HttpRequestDecoder(
|
||||||
super(maxChunkSize);
|
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
|
||||||
|
super(maxInitialLineLength, maxHeaderSize, maxChunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void readInitial(ChannelBuffer buffer) throws Exception{
|
protected HttpMessage createMessage(String[] initialLine) throws Exception{
|
||||||
String line = readIntoCurrentLine(buffer);
|
return new DefaultHttpRequest(
|
||||||
String[] split = splitInitial(line);
|
HttpVersion.valueOf(initialLine[2]), HttpMethod.valueOf(initialLine[0]), initialLine[1]);
|
||||||
message = new DefaultHttpRequest(
|
|
||||||
HttpVersion.valueOf(split[2]), HttpMethod.valueOf(split[0]), split[1]);
|
|
||||||
checkpoint(State.READ_HEADER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jboss.netty.handler.codec.http;
|
package org.jboss.netty.handler.codec.http;
|
||||||
|
|
||||||
import org.jboss.netty.buffer.ChannelBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* an http response decoder
|
* an http response decoder
|
||||||
@ -37,16 +36,14 @@ public class HttpResponseDecoder extends HttpMessageDecoder {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpResponseDecoder(int maxChunkSize) {
|
public HttpResponseDecoder(
|
||||||
super(maxChunkSize);
|
int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
|
||||||
|
super(maxInitialLineLength, maxHeaderSize, maxChunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void readInitial(ChannelBuffer buffer) {
|
protected HttpMessage createMessage(String[] initialLine) {
|
||||||
String line = readIntoCurrentLine(buffer);
|
return new DefaultHttpResponse(HttpVersion.valueOf(initialLine[0]), new HttpResponseStatus(Integer.valueOf(initialLine[1]), initialLine[2]));
|
||||||
String[] split = splitInitial(line);
|
|
||||||
message = new DefaultHttpResponse(HttpVersion.valueOf(split[0]), new HttpResponseStatus(Integer.valueOf(split[1]), split[2]));
|
|
||||||
checkpoint(State.READ_HEADER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
Reference in New Issue
Block a user