26
26
import io.netty.util.AsciiString;
27
27
import io.netty.util.ByteProcessor;
28
28
import io.netty.util.internal.StringUtil;
29
+ import io.netty.util.internal.SystemPropertyUtil;
29
30
30
31
import java.util.List;
31
32
import java.util.concurrent.atomic.AtomicBoolean;
@@ -151,6 +152,23 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
151
152
public static final boolean DEFAULT_VALIDATE_HEADERS = true;
152
153
public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
153
154
public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
155
+ public static final boolean DEFAULT_STRICT_LINE_PARSING =
156
+ SystemPropertyUtil.getBoolean("io.netty.handler.codec.http.defaultStrictLineParsing", true);
157
+
158
+ private static final Runnable THROW_INVALID_CHUNK_EXTENSION = new Runnable() {
159
+ @Override
160
+ public void run() {
161
+ throw new InvalidChunkExtensionException();
162
+ }
163
+ };
164
+
165
+ private static final Runnable THROW_INVALID_LINE_SEPARATOR = new Runnable() {
166
+ @Override
167
+ public void run() {
168
+ throw new InvalidLineSeparatorException();
169
+ }
170
+ };
171
+
154
172
private final int maxChunkSize;
155
173
private final boolean chunkedSupported;
156
174
private final boolean allowPartialChunks;
@@ -163,6 +181,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
163
181
protected final HttpHeadersFactory trailersFactory;
164
182
private final boolean allowDuplicateContentLengths;
165
183
private final ByteBuf parserScratchBuffer;
184
+ private final Runnable defaultStrictCRLFCheck;
166
185
private final HeaderParser headerParser;
167
186
private final LineParser lineParser;
168
187
@@ -171,7 +190,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
171
190
private long contentLength = Long.MIN_VALUE;
172
191
private boolean chunked;
173
192
private boolean isSwitchingToNonHttp1Protocol;
174
-
193
+
175
194
// Liberty specific configurations
176
195
private int limitFieldSize;
177
196
private int limitNumHeaders;
@@ -320,6 +339,7 @@ protected HttpObjectDecoder(HttpDecoderConfig config) {
320
339
checkNotNull(config, "config");
321
340
322
341
parserScratchBuffer = Unpooled.buffer(config.getInitialBufferSize());
342
+ defaultStrictCRLFCheck = config.isStrictLineParsing() ? THROW_INVALID_LINE_SEPARATOR : null;
323
343
shouldDoLibertyCheck = config.isLibertyHttpHeaderOptionsSet();
324
344
lineParser = new LineParser(parserScratchBuffer, config.getMaxInitialLineLength());
325
345
headerParser = new HeaderParser(parserScratchBuffer, shouldDoLibertyCheck ? -1 : config.getMaxHeaderSize());
@@ -347,12 +367,12 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> ou
347
367
if (resetRequested.get()) {
348
368
resetNow();
349
369
}
350
-
370
+
351
371
switch (currentState) {
352
372
case SKIP_CONTROL_CHARS:
353
373
// Fall-through
354
374
case READ_INITIAL: try {
355
- ByteBuf line = lineParser.parse(buffer);
375
+ ByteBuf line = lineParser.parse(buffer, defaultStrictCRLFCheck );
356
376
if (line == null) {
357
377
return;
358
378
}
@@ -461,7 +481,7 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> ou
461
481
* read chunk, read and ignore the CRLF and repeat until 0
462
482
*/
463
483
case READ_CHUNK_SIZE: try {
464
- ByteBuf line = lineParser.parse(buffer);
484
+ ByteBuf line = lineParser.parse(buffer, THROW_INVALID_CHUNK_EXTENSION );
465
485
if (line == null) {
466
486
return;
467
487
}
@@ -499,16 +519,16 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> ou
499
519
// fall-through
500
520
}
501
521
case READ_CHUNK_DELIMITER: {
502
- final int wIdx = buffer.writerIndex();
503
- int rIdx = buffer.readerIndex();
504
- while (wIdx > rIdx) {
505
- byte next = buffer.getByte(rIdx++);
506
- if (next == HttpConstants.LF) {
522
+ if ( buffer.readableBytes() >= 2) {
523
+ int rIdx = buffer.readerIndex();
524
+ if (buffer.getByte( rIdx) == HttpConstants.CR &&
525
+ buffer.getByte(rIdx + 1) == HttpConstants.LF) {
526
+ buffer.skipBytes(2);
507
527
currentState = State.READ_CHUNK_SIZE;
508
- break;
528
+ } else {
529
+ out.add(invalidChunk(buffer, new InvalidChunkTerminationException()));
509
530
}
510
531
}
511
- buffer.readerIndex(rIdx);
512
532
return;
513
533
}
514
534
case READ_CHUNK_FOOTER: try {
@@ -731,7 +751,7 @@ private State readHeaders(ByteBuf buffer) {
731
751
732
752
final HeaderParser headerParser = this.headerParser;
733
753
734
- ByteBuf line = headerParser.parse(buffer);
754
+ ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck );
735
755
if (line == null) {
736
756
return null;
737
757
}
@@ -759,7 +779,7 @@ private State readHeaders(ByteBuf buffer) {
759
779
splitHeader(lineContent, startLine, lineLength);
760
780
}
761
781
762
- line = headerParser.parse(buffer);
782
+ line = headerParser.parse(buffer, defaultStrictCRLFCheck );
763
783
if (line == null) {
764
784
return null;
765
785
}
@@ -855,7 +875,7 @@ protected void handleTransferEncodingChunkedWithContentLength(HttpMessage messag
855
875
856
876
private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
857
877
final HeaderParser headerParser = this.headerParser;
858
- ByteBuf line = headerParser.parse(buffer);
878
+ ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck );
859
879
if (line == null) {
860
880
return null;
861
881
}
@@ -898,7 +918,7 @@ private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
898
918
name = null;
899
919
value = null;
900
920
}
901
- line = headerParser.parse(buffer);
921
+ line = headerParser.parse(buffer, defaultStrictCRLFCheck );
902
922
if (line == null) {
903
923
return null;
904
924
}
@@ -1167,7 +1187,7 @@ private static class HeaderParser {
1167
1187
this.maxLength = maxLength;
1168
1188
}
1169
1189
1170
- public ByteBuf parse(ByteBuf buffer) {
1190
+ public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck ) {
1171
1191
final int readableBytes = buffer.readableBytes();
1172
1192
final int readerIndex = buffer.readerIndex();
1173
1193
long maxAllowedBody = 0;
@@ -1199,6 +1219,9 @@ public ByteBuf parse(ByteBuf buffer) {
1199
1219
// Drop CR if we had a CRLF pair
1200
1220
endOfSeqIncluded = indexOfLf - 1;
1201
1221
} else {
1222
+ if (strictCRLFCheck != null) {
1223
+ strictCRLFCheck.run();
1224
+ }
1202
1225
endOfSeqIncluded = indexOfLf;
1203
1226
}
1204
1227
final int newSize = endOfSeqIncluded - readerIndex;
@@ -1234,18 +1257,18 @@ private final class LineParser extends HeaderParser {
1234
1257
}
1235
1258
1236
1259
@Override
1237
- public ByteBuf parse(ByteBuf buffer) {
1260
+ public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck ) {
1238
1261
// Suppress a warning because HeaderParser.reset() is supposed to be called
1239
1262
reset();
1240
1263
final int readableBytes = buffer.readableBytes();
1241
1264
if (readableBytes == 0) {
1242
1265
return null;
1243
1266
}
1244
- final int readerIndex = buffer.readerIndex();
1245
- if (currentState == State.SKIP_CONTROL_CHARS && skipControlChars(buffer, readableBytes, readerIndex)) {
1267
+ if (currentState == State.SKIP_CONTROL_CHARS &&
1268
+ skipControlChars(buffer, readableBytes, buffer. readerIndex() )) {
1246
1269
return null;
1247
1270
}
1248
- return super.parse(buffer);
1271
+ return super.parse(buffer, strictCRLFCheck );
1249
1272
}
1250
1273
1251
1274
private boolean skipControlChars(ByteBuf buffer, int readableBytes, int readerIndex) {
0 commit comments