1616
1717import io .netty .buffer .ByteBuf ;
1818import io .netty .channel .ChannelHandlerContext ;
19+ import io .netty .handler .codec .http .HttpHeaderNames ;
1920import io .netty .handler .codec .http .HttpStatusClass ;
21+ import io .netty .handler .codec .http .HttpUtil ;
2022import io .netty .handler .codec .http2 .Http2Connection .Endpoint ;
23+ import io .netty .util .internal .SystemPropertyUtil ;
2124import io .netty .util .internal .UnstableApi ;
2225import io .netty .util .internal .logging .InternalLogger ;
2326import io .netty .util .internal .logging .InternalLoggerFactory ;
4952 */
5053@ UnstableApi
5154public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
55+ private static final boolean VALIDATE_CONTENT_LENGTH =
56+ SystemPropertyUtil .getBoolean ("io.netty.http2.validateContentLength" , true );
5257 private static final InternalLogger logger = InternalLoggerFactory .getInstance (DefaultHttp2ConnectionDecoder .class );
5358 private Http2FrameListener internalFrameListener = new PrefaceFrameListener ();
5459 private final Http2Connection connection ;
@@ -59,6 +64,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
5964 private final Http2PromisedRequestVerifier requestVerifier ;
6065 private final Http2SettingsReceivedConsumer settingsReceivedConsumer ;
6166 private final boolean autoAckPing ;
67+ private final Http2Connection .PropertyKey contentLengthKey ;
6268
6369 public DefaultHttp2ConnectionDecoder (Http2Connection connection ,
6470 Http2ConnectionEncoder encoder ,
@@ -125,6 +131,7 @@ public DefaultHttp2ConnectionDecoder(Http2Connection connection,
125131 settingsReceivedConsumer = (Http2SettingsReceivedConsumer ) encoder ;
126132 }
127133 this .connection = checkNotNull (connection , "connection" );
134+ contentLengthKey = this .connection .newKey ();
128135 this .frameReader = checkNotNull (frameReader , "frameReader" );
129136 this .encoder = checkNotNull (encoder , "encoder" );
130137 this .requestVerifier = checkNotNull (requestVerifier , "requestVerifier" );
@@ -223,6 +230,23 @@ void onUnknownFrame0(ChannelHandlerContext ctx, byte frameType, int streamId, Ht
223230 listener .onUnknownFrame (ctx , frameType , streamId , flags , payload );
224231 }
225232
233+ // See https://tools.ietf.org/html/rfc7540#section-8.1.2.6
234+ private void verifyContentLength (Http2Stream stream , int data , boolean isEnd ) throws Http2Exception {
235+ if (!VALIDATE_CONTENT_LENGTH ) {
236+ return ;
237+ }
238+ ContentLength contentLength = stream .getProperty (contentLengthKey );
239+ if (contentLength != null ) {
240+ try {
241+ contentLength .increaseReceivedBytes (connection .isServer (), stream .id (), data , isEnd );
242+ } finally {
243+ if (isEnd ) {
244+ stream .removeProperty (contentLengthKey );
245+ }
246+ }
247+ }
248+ }
249+
226250 /**
227251 * Handles all inbound frames from the network.
228252 */
@@ -232,7 +256,8 @@ public int onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf dat
232256 boolean endOfStream ) throws Http2Exception {
233257 Http2Stream stream = connection .stream (streamId );
234258 Http2LocalFlowController flowController = flowController ();
235- int bytesToReturn = data .readableBytes () + padding ;
259+ int readable = data .readableBytes ();
260+ int bytesToReturn = readable + padding ;
236261
237262 final boolean shouldIgnore ;
238263 try {
@@ -259,7 +284,6 @@ public int onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf dat
259284 // All bytes have been consumed.
260285 return bytesToReturn ;
261286 }
262-
263287 Http2Exception error = null ;
264288 switch (stream .state ()) {
265289 case OPEN :
@@ -287,6 +311,8 @@ public int onDataRead(final ChannelHandlerContext ctx, int streamId, ByteBuf dat
287311 throw error ;
288312 }
289313
314+ verifyContentLength (stream , readable , endOfStream );
315+
290316 // Call back the application and retrieve the number of bytes that have been
291317 // immediately processed.
292318 bytesToReturn = listener .onDataRead (ctx , streamId , data , padding , endOfStream );
@@ -367,14 +393,34 @@ public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers
367393 stream .state ());
368394 }
369395
370- stream .headersReceived (isInformational );
371- encoder .flowController ().updateDependencyTree (streamId , streamDependency , weight , exclusive );
372-
373- listener .onHeadersRead (ctx , streamId , headers , streamDependency , weight , exclusive , padding , endOfStream );
396+ if (!stream .isHeadersReceived ()) {
397+ // extract the content-length header
398+ List <? extends CharSequence > contentLength = headers .getAll (HttpHeaderNames .CONTENT_LENGTH );
399+ if (contentLength != null && !contentLength .isEmpty ()) {
400+ try {
401+ long cLength = HttpUtil .normalizeAndGetContentLength (contentLength , false , true );
402+ if (cLength != -1 ) {
403+ headers .setLong (HttpHeaderNames .CONTENT_LENGTH , cLength );
404+ stream .setProperty (contentLengthKey , new ContentLength (cLength ));
405+ }
406+ } catch (IllegalArgumentException e ) {
407+ throw streamError (stream .id (), PROTOCOL_ERROR ,
408+ "Multiple content-length headers received" , e );
409+ }
410+ }
411+ }
374412
375- // If the headers completes this stream, close it.
376- if (endOfStream ) {
377- lifecycleManager .closeStreamRemote (stream , ctx .newSucceededFuture ());
413+ stream .headersReceived (isInformational );
414+ try {
415+ verifyContentLength (stream , 0 , endOfStream );
416+ encoder .flowController ().updateDependencyTree (streamId , streamDependency , weight , exclusive );
417+ listener .onHeadersRead (ctx , streamId , headers , streamDependency ,
418+ weight , exclusive , padding , endOfStream );
419+ } finally {
420+ // If the headers completes this stream, close it.
421+ if (endOfStream ) {
422+ lifecycleManager .closeStreamRemote (stream , ctx .newSucceededFuture ());
423+ }
378424 }
379425 }
380426
@@ -736,4 +782,40 @@ public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int stream
736782 onUnknownFrame0 (ctx , frameType , streamId , flags , payload );
737783 }
738784 }
785+
786+ private static final class ContentLength {
787+ private final long expected ;
788+ private long seen ;
789+
790+ ContentLength (long expected ) {
791+ this .expected = expected ;
792+ }
793+
794+ void increaseReceivedBytes (boolean server , int streamId , int bytes , boolean isEnd ) throws Http2Exception {
795+ seen += bytes ;
796+ // Check for overflow
797+ if (seen < 0 ) {
798+ throw streamError (streamId , PROTOCOL_ERROR ,
799+ "Received amount of data did overflow and so not match content-length header %d" , expected );
800+ }
801+ // Check if we received more data then what was advertised via the content-length header.
802+ if (seen > expected ) {
803+ throw streamError (streamId , PROTOCOL_ERROR ,
804+ "Received amount of data %d does not match content-length header %d" , seen , expected );
805+ }
806+
807+ if (isEnd ) {
808+ if (seen == 0 && !server ) {
809+ // This may be a response to a HEAD request, let's just allow it.
810+ return ;
811+ }
812+
813+ // Check that we really saw what was told via the content-length header.
814+ if (expected > seen ) {
815+ throw streamError (streamId , PROTOCOL_ERROR ,
816+ "Received amount of data %d does not match content-length header %d" , seen , expected );
817+ }
818+ }
819+ }
820+ }
739821}
0 commit comments