diff --git a/src/Kestrel.Core/Adapter/Internal/RawStream.cs b/src/Kestrel.Core/Adapter/Internal/RawStream.cs index 4f35b95ebe..137b87ea04 100644 --- a/src/Kestrel.Core/Adapter/Internal/RawStream.cs +++ b/src/Kestrel.Core/Adapter/Internal/RawStream.cs @@ -131,7 +131,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal readableBuffer.CopyTo(destination.Span); return count; } - else if (result.IsCompleted) + + if (result.IsCompleted) { return 0; } diff --git a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs index d65805cc83..f11619d7f3 100644 --- a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs @@ -87,7 +87,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http break; } } - else if (result.IsCompleted) + + // Read() will have already have greedily consumed the entire request body if able. + if (result.IsCompleted) { // Treat any FIN from an upgraded request as expected. // It's up to higher-level consumer (i.e. WebSocket middleware) to determine diff --git a/src/Kestrel.Core/Internal/Http/MessageBody.cs b/src/Kestrel.Core/Internal/Http/MessageBody.cs index 339c9d182a..3ed2b86c58 100644 --- a/src/Kestrel.Core/Internal/Http/MessageBody.cs +++ b/src/Kestrel.Core/Internal/Http/MessageBody.cs @@ -57,7 +57,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http slice.CopyTo(buffer.Span); return actual; } - else if (result.IsCompleted) + + if (result.IsCompleted) { return 0; } @@ -98,7 +99,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http #endif } } - else if (result.IsCompleted) + + if (result.IsCompleted) { return; } diff --git a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs index 15c47b381c..a2110bee0a 100644 --- a/src/Kestrel.Core/Internal/Http2/Http2Connection.cs +++ b/src/Kestrel.Core/Internal/Http2/Http2Connection.cs @@ -147,7 +147,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 await ProcessFrameAsync(application); } } - else if (result.IsCompleted) + + if (result.IsCompleted) { return; } @@ -254,6 +255,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 return true; } } + if (result.IsCompleted) { return false; diff --git a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs index e6871565ab..ce15b17a46 100644 --- a/test/Kestrel.Core.Tests/Http2ConnectionTests.cs +++ b/test/Kestrel.Core.Tests/Http2ConnectionTests.cs @@ -3841,6 +3841,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { return frame; } + + if (result.IsCompleted) + { + throw new IOException("The reader completed without returning a frame."); + } } finally { diff --git a/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs b/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs index 3bc0c9d286..02122cff42 100644 --- a/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs +++ b/test/Kestrel.FunctionalTests/ChunkedRequestTests.cs @@ -1,14 +1,17 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Testing; using Xunit; @@ -639,6 +642,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } } + + [Theory] + [MemberData(nameof(ConnectionAdapterData))] + public async Task ClosingConnectionMidChunkPrefixThrows(ListenOptions listenOptions) + { + var testContext = new TestServiceContext(LoggerFactory); + var readStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var exTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + using (var server = new TestServer(async httpContext => + { + var readTask = httpContext.Request.Body.CopyToAsync(Stream.Null); + readStartedTcs.SetResult(null); + + try + { + await readTask; + } + catch (BadHttpRequestException badRequestEx) + { + exTcs.TrySetResult(badRequestEx); + } + catch (Exception ex) + { + exTcs.SetException(ex); + } + }, testContext, listenOptions)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendAll( + "POST / HTTP/1.1", + "Host:", + "Transfer-Encoding: chunked", + "", + "1"); + + await readStartedTcs.Task.TimeoutAfter(TestConstants.DefaultTimeout); + + connection.Socket.Shutdown(SocketShutdown.Send); + + await connection.ReceiveEnd(); + + var badReqEx = await exTcs.Task.TimeoutAfter(TestConstants.DefaultTimeout); + Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, badReqEx.Reason); + } + } + } } } diff --git a/test/Kestrel.FunctionalTests/Http2/TlsTests.cs b/test/Kestrel.FunctionalTests/Http2/TlsTests.cs index d65ec34f14..28eef0675d 100644 --- a/test/Kestrel.FunctionalTests/Http2/TlsTests.cs +++ b/test/Kestrel.FunctionalTests/Http2/TlsTests.cs @@ -100,19 +100,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2 var consumed = buffer.Start; var examined = buffer.End; - if (buffer.IsEmpty && result.IsCompleted) - { - throw new IOException("The reader completed without returning a frame."); - } - try { - // Assert.True(buffer.Length > 0); - if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined)) { return frame; } + + if (result.IsCompleted) + { + throw new IOException("The reader completed without returning a frame."); + } } finally { diff --git a/test/Kestrel.FunctionalTests/RequestTests.cs b/test/Kestrel.FunctionalTests/RequestTests.cs index 732ed8074a..e4ff7237bc 100644 --- a/test/Kestrel.FunctionalTests/RequestTests.cs +++ b/test/Kestrel.FunctionalTests/RequestTests.cs @@ -1696,8 +1696,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests [Fact] public async Task DoesNotEnforceRequestBodyMinimumDataRateOnUpgradedRequest() { - var appEvent = new TaskCompletionSource(); - var delayEvent = new TaskCompletionSource(); + var appEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var delayEvent = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var serviceContext = new TestServiceContext(LoggerFactory) { SystemClock = new SystemClock() diff --git a/test/shared/TestConnection.cs b/test/shared/TestConnection.cs index 04e547214d..2d760a5bc8 100644 --- a/test/shared/TestConnection.cs +++ b/test/shared/TestConnection.cs @@ -47,6 +47,8 @@ namespace Microsoft.AspNetCore.Testing _reader = new StreamReader(_stream, Encoding.ASCII); } + public Socket Socket => _socket; + public Stream Stream => _stream; public StreamReader Reader => _reader;