Merge branch 'release/3.1' => 'master' (#16834)

This commit is contained in:
Doug Bunting 2019-11-05 15:39:47 -08:00 committed by GitHub
commit 9abf4bfe3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 368 additions and 116 deletions

View File

@ -2,9 +2,6 @@
<configuration> <configuration>
<packageSources> <packageSources>
<clear /> <clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<add key="darc-pub-dotnet-corefx-4ac4c03" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-4ac4c036/nuget/v3/index.json" />
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" /> <add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" /> <add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="aspnet-blazor" value="https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json" /> <add key="aspnet-blazor" value="https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json" />

View File

@ -15,7 +15,7 @@
<div id="blazor-error-ui"> <div id="blazor-error-ui">
An unhandled error has occurred. An unhandled error has occurred.
<a href class="reload">Reload</a> <a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
<script src="_framework/blazor.webassembly.js"></script> <script src="_framework/blazor.webassembly.js"></script>

View File

@ -122,8 +122,9 @@ namespace Microsoft.AspNetCore.HttpsPolicy
// 1. Set in the HttpsRedirectionOptions // 1. Set in the HttpsRedirectionOptions
// 2. HTTPS_PORT environment variable // 2. HTTPS_PORT environment variable
// 3. IServerAddressesFeature // 3. IServerAddressesFeature
// 4. Fail if not set // 4. Fail if not sets
var nullablePort = _config.GetValue<int?>("HTTPS_PORT");
var nullablePort = _config.GetValue<int?>("HTTPS_PORT") ?? _config.GetValue<int?>("ANCM_HTTPS_PORT");
if (nullablePort.HasValue) if (nullablePort.HasValue)
{ {
var port = nullablePort.Value; var port = nullablePort.Value;

View File

@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
{ {
response.ContentLength = resolvedContentTypeEncoding.GetByteCount(result.Content); response.ContentLength = resolvedContentTypeEncoding.GetByteCount(result.Content);
using (var textWriter = _httpResponseStreamWriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) await using (var textWriter = _httpResponseStreamWriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
{ {
await textWriter.WriteAsync(result.Content); await textWriter.WriteAsync(result.Content);

View File

@ -286,7 +286,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
{ {
// This means we're writing to a 'real' writer, probably to the actual output stream. // This means we're writing to a 'real' writer, probably to the actual output stream.
// We're using PagedBufferedTextWriter here to 'smooth' synchronous writes of IHtmlContent values. // We're using PagedBufferedTextWriter here to 'smooth' synchronous writes of IHtmlContent values.
using (var writer = _bufferScope.CreateWriter(context.Writer)) await using (var writer = _bufferScope.CreateWriter(context.Writer))
{ {
await bodyWriter.Buffer.WriteToAsync(writer, _htmlEncoder); await bodyWriter.Buffer.WriteToAsync(writer, _htmlEncoder);
await writer.FlushAsync(); await writer.FlushAsync();

View File

@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
_writerFactory ??= context.HttpContext.RequestServices.GetRequiredService<IHttpResponseStreamWriterFactory>(); _writerFactory ??= context.HttpContext.RequestServices.GetRequiredService<IHttpResponseStreamWriterFactory>();
using (var writer = _writerFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) await using (var writer = _writerFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
{ {
var viewContext = new ViewContext( var viewContext = new ViewContext(
context, context,
@ -149,8 +149,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
} }
else else
{ {
using var bufferingStream = new FileBufferingWriteStream(); await using var bufferingStream = new FileBufferingWriteStream();
using (var intermediateWriter = _writerFactory.CreateWriter(bufferingStream, resolvedContentTypeEncoding)) await using (var intermediateWriter = _writerFactory.CreateWriter(bufferingStream, resolvedContentTypeEncoding))
{ {
viewComponentResult.WriteTo(intermediateWriter, _htmlEncoder); viewComponentResult.WriteTo(intermediateWriter, _htmlEncoder);
} }

View File

@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
OnExecuting(viewContext); OnExecuting(viewContext);
using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) await using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding))
{ {
var view = viewContext.View; var view = viewContext.View;

View File

@ -83,6 +83,44 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
} }
} }
[Fact]
public async Task ExecuteAsync_ExceptionInSyncContext()
{
// Arrange
var view = CreateView((v) =>
{
v.Writer.Write("xyz");
throw new NotImplementedException("This should be raw!");
});
var context = new DefaultHttpContext();
var stream = new Mock<Stream>();
stream.Setup(s => s.CanWrite).Returns(true);
context.Response.Body = stream.Object;
var actionContext = new ActionContext(
context,
new RouteData(),
new ActionDescriptor());
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewExecutor = CreateViewExecutor();
// Act
var exception = await Assert.ThrowsAsync<NotImplementedException>(async () => await viewExecutor.ExecuteAsync(
actionContext,
view,
viewData,
Mock.Of<ITempDataDictionary>(),
contentType: null,
statusCode: null)
);
// Assert
Assert.Equal("This should be raw!", exception.Message);
stream.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never);
}
[Theory] [Theory]
[MemberData(nameof(ViewExecutorSetsContentTypeAndEncodingData))] [MemberData(nameof(ViewExecutorSetsContentTypeAndEncodingData))]
public async Task ExecuteAsync_SetsContentTypeAndEncoding( public async Task ExecuteAsync_SetsContentTypeAndEncoding(

View File

@ -64,6 +64,17 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
#endif #endif
} }
[Fact]
public async Task GivesCorrectCallstackForSyncronousCalls()
{
// Regression test for https://github.com/aspnet/AspNetCore/issues/15367
// Arrange
var exception = await Assert.ThrowsAsync<HttpRequestException>(async () => await Client.GetAsync("http://localhost/Home/MyHtml"));
// Assert
Assert.Equal("Should be visible", exception.InnerException.InnerException.Message);
}
[Fact] [Fact]
public async Task CanRenderViewsWithTagHelpersAndUnboundDynamicAttributes_Encoded() public async Task CanRenderViewsWithTagHelpersAndUnboundDynamicAttributes_Encoded()
{ {
@ -318,4 +329,4 @@ page:<root>root-content</root>"
#endif #endif
} }
} }
} }

View File

@ -35,6 +35,11 @@ namespace TagHelpersWebSite.Controllers
return View(); return View();
} }
public IActionResult MyHtml()
{
return View();
}
public IActionResult ViewComponentTagHelpers() public IActionResult ViewComponentTagHelpers()
{ {
return View(); return View();

View File

@ -0,0 +1,27 @@
// 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.IO;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace TagHelpersWebSite
{
public class MyHtmlContent : IHtmlContent
{
private IHtmlHelper Html { get; }
public MyHtmlContent(IHtmlHelper html)
{
Html = html;
}
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
#pragma warning disable MVC1000 // Use of IHtmlHelper.{0} should be avoided.
Html.Partial("_Test").WriteTo(writer, encoder);
#pragma warning restore MVC1000 // Use of IHtmlHelper.{0} should be avoided.
}
}
}

View File

@ -3,6 +3,7 @@
using System.IO; using System.IO;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;

View File

@ -9,7 +9,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj" /> <ProjectReference Include="..\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" /> <Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" /> <Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />

View File

@ -0,0 +1 @@
@(new TagHelpersWebSite.MyHtmlContent(Html))

View File

@ -0,0 +1,3 @@
@{
throw new Exception("Should be visible");
}

View File

@ -1,7 +1,7 @@
@page "/" @page "/"
@namespace BlazorServerWeb_CSharp.Pages @namespace BlazorServerWeb_CSharp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{ @{
Layout = null; Layout = null;
} }
@ -27,7 +27,7 @@
<environment include="Development"> <environment include="Development">
An unhandled exception has occurred. See browser dev tools for details. An unhandled exception has occurred. See browser dev tools for details.
</environment> </environment>
<a href class="reload">Reload</a> <a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>

View File

@ -3,6 +3,7 @@ tmp/
CustomHive/ CustomHive/
angular/ angular/
blazorserver/ blazorserver/
blazorwasm/
mvc/ mvc/
razor/ razor/
react/ react/

View File

@ -0,0 +1,13 @@
#!/usr/bin/env pwsh
#requires -version 4
# This script packages, installs and creates a template to help with rapid iteration in the templating area.
[CmdletBinding(PositionalBinding = $false)]
param()
Set-StrictMode -Version 2
$ErrorActionPreference = 'Stop'
. $PSScriptRoot\Test-Template.ps1
Test-Template "blazorwasm" "blazorwasm" "Microsoft.AspnetCore.Blazor.Templates.3.1.0-dev.nupkg" $false

View File

@ -13,7 +13,6 @@
#define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000 #define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000
HRESULT HRESULT
SERVER_PROCESS::Initialize( SERVER_PROCESS::Initialize(
PROCESS_MANAGER *pProcessManager, PROCESS_MANAGER *pProcessManager,

View File

@ -8,7 +8,7 @@
#define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH" #define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH"
#define ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR L"ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED" #define ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED_ENV_STR L"ASPNETCORE_IIS_WEBSOCKETS_SUPPORTED"
#define ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR L"ASPNETCORE_IIS_PHYSICAL_PATH" #define ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR L"ASPNETCORE_IIS_PHYSICAL_PATH"
#define ASPNETCORE_HTTPS_PORT_ENV_STR L"ASPNETCORE_HTTPS_PORT" #define ASPNETCORE_ANCM_HTTPS_PORT_ENV_STR L"ASPNETCORE_ANCM_HTTPS_PORT"
#define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;" #define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;"
#define ASPNETCORE_IIS_AUTH_BASIC L"basic;" #define ASPNETCORE_IIS_AUTH_BASIC L"basic;"
#define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" #define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;"

View File

@ -43,7 +43,7 @@ public:
environmentVariables.insert_or_assign(ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, pApplicationPhysicalPath); environmentVariables.insert_or_assign(ASPNETCORE_IIS_PHYSICAL_PATH_ENV_STR, pApplicationPhysicalPath);
if (pHttpsPort) if (pHttpsPort)
{ {
environmentVariables.try_emplace(ASPNETCORE_HTTPS_PORT_ENV_STR, pHttpsPort); environmentVariables.try_emplace(ASPNETCORE_ANCM_HTTPS_PORT_ENV_STR, pHttpsPort);
} }
std::wstring strIisAuthEnvValue; std::wstring strIisAuthEnvValue;

View File

@ -57,14 +57,14 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
if (DeployerSelector.HasNewHandler && if (DeployerSelector.HasNewHandler &&
DeployerSelector.HasNewShim) DeployerSelector.HasNewShim)
{ {
// We expect ServerAddress to be set for InProcess and HTTPS_PORT for OutOfProcess // We expect ServerAddress to be set for InProcess and ANCM_HTTPS_PORT for OutOfProcess
if (variant.HostingModel == HostingModel.InProcess) if (variant.HostingModel == HostingModel.InProcess)
{ {
Assert.Equal(deploymentParameters.ApplicationBaseUriHint, await client.GetStringAsync("/ServerAddresses")); Assert.Equal(deploymentParameters.ApplicationBaseUriHint, await client.GetStringAsync("/ServerAddresses"));
} }
else else
{ {
Assert.Equal(port.ToString(), await client.GetStringAsync("/HTTPS_PORT")); Assert.Equal(port.ToString(), await client.GetStringAsync("/ANCM_HTTPS_PORT"));
} }
} }
} }
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
[ConditionalFact] [ConditionalFact]
[RequiresNewHandler] [RequiresNewHandler]
[RequiresNewShim] [RequiresNewShim]
public async Task HttpsPortCanBeOverriden() public async Task AncmHttpsPortCanBeOverriden()
{ {
var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess); var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess);
@ -125,12 +125,57 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
.SetAttributeValue("bindingInformation", $":{TestPortHelper.GetNextSSLPort()}:localhost"); .SetAttributeValue("bindingInformation", $":{TestPortHelper.GetNextSSLPort()}:localhost");
}); });
deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_HTTPS_PORT"] = "123"; deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_ANCM_HTTPS_PORT"] = "123";
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
var client = CreateNonValidatingClient(deploymentResult); var client = CreateNonValidatingClient(deploymentResult);
Assert.Equal("123", await client.GetStringAsync("/HTTPS_PORT")); Assert.Equal("123", await client.GetStringAsync("/ANCM_HTTPS_PORT"));
Assert.Equal("NOVALUE", await client.GetStringAsync("/HTTPS_PORT"));
}
[ConditionalFact]
[RequiresNewShim]
public async Task HttpsRedirectionWorksIn30AndNot22()
{
var port = TestPortHelper.GetNextSSLPort();
var deploymentParameters = Fixture.GetBaseDeploymentParameters(HostingModel.OutOfProcess);
deploymentParameters.WebConfigBasedEnvironmentVariables["ENABLE_HTTPS_REDIRECTION"] = "true";
deploymentParameters.ApplicationBaseUriHint = $"http://localhost:{TestPortHelper.GetNextPort()}/";
deploymentParameters.AddServerConfigAction(
element => {
element.Descendants("bindings")
.Single()
.AddAndGetInnerElement("binding", "protocol", "https")
.SetAttributeValue("bindingInformation", $":{port}:localhost");
element.Descendants("access")
.Single()
.SetAttributeValue("sslFlags", "None");
});
var deploymentResult = await DeployAsync(deploymentParameters);
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
AllowAutoRedirect = false
};
var client = new HttpClient(handler)
{
BaseAddress = new Uri(deploymentParameters.ApplicationBaseUriHint)
};
if (DeployerSelector.HasNewHandler)
{
var response = await client.GetAsync("/ANCM_HTTPS_PORT");
Assert.Equal(307, (int)response.StatusCode);
}
else
{
var response = await client.GetAsync("/ANCM_HTTPS_PORT");
Assert.Equal(200, (int)response.StatusCode);
}
} }
[ConditionalFact] [ConditionalFact]
@ -159,7 +204,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
var client = CreateNonValidatingClient(deploymentResult); var client = CreateNonValidatingClient(deploymentResult);
Assert.Equal("NOVALUE", await client.GetStringAsync("/HTTPS_PORT")); Assert.Equal("NOVALUE", await client.GetStringAsync("/ANCM_HTTPS_PORT"));
} }
[ConditionalFact] [ConditionalFact]

View File

@ -42,6 +42,9 @@
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" > <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" >
<AllowExplicitReference>true</AllowExplicitReference> <AllowExplicitReference>true</AllowExplicitReference>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.HttpsPolicy" Version="2.2.0" >
<AllowExplicitReference>true</AllowExplicitReference>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" > <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.2.0" >
<AllowExplicitReference>true</AllowExplicitReference> <AllowExplicitReference>true</AllowExplicitReference>
</PackageReference> </PackageReference>

View File

@ -23,6 +23,7 @@
<Reference Include="Microsoft.AspNetCore.Server.IIS" /> <Reference Include="Microsoft.AspNetCore.Server.IIS" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" /> <Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" /> <Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
<Reference Include="Microsoft.AspNetCore.WebUtilities" /> <Reference Include="Microsoft.AspNetCore.WebUtilities" />
<Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" /> <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<Reference Include="Microsoft.Extensions.Configuration.Json" /> <Reference Include="Microsoft.Extensions.Configuration.Json" />

View File

@ -34,6 +34,10 @@ namespace TestSite
{ {
public void Configure(IApplicationBuilder app) public void Configure(IApplicationBuilder app)
{ {
if (Environment.GetEnvironmentVariable("ENABLE_HTTPS_REDIRECTION") != null)
{
app.UseHttpsRedirection();
}
TestStartup.Register(app, this); TestStartup.Register(app, this);
} }
@ -992,6 +996,13 @@ namespace TestSite
await context.Response.WriteAsync(Process.GetCurrentProcess().Id.ToString()); await context.Response.WriteAsync(Process.GetCurrentProcess().Id.ToString());
} }
public async Task ANCM_HTTPS_PORT(HttpContext context)
{
var httpsPort = context.RequestServices.GetService<IConfiguration>().GetValue<int?>("ANCM_HTTPS_PORT");
await context.Response.WriteAsync(httpsPort.HasValue ? httpsPort.Value.ToString() : "NOVALUE");
}
public async Task HTTPS_PORT(HttpContext context) public async Task HTTPS_PORT(HttpContext context)
{ {
var httpsPort = context.RequestServices.GetService<IConfiguration>().GetValue<int?>("HTTPS_PORT"); var httpsPort = context.RequestServices.GetService<IConfiguration>().GetValue<int?>("HTTPS_PORT");

View File

@ -43,5 +43,13 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
return existing; return existing;
} }
public static XElement AddAndGetInnerElement(this XElement element, string name, string attribute, string attributeValue)
{
var innerElement = new XElement(name, new XAttribute(attribute, attributeValue));
element.Add(innerElement);
return innerElement;
}
} }
} }

View File

@ -163,7 +163,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
// Read() will have already have greedily consumed the entire request body if able. // Read() will have already have greedily consumed the entire request body if able.
CheckCompletedReadResult(result); if (result.IsCompleted)
{
ThrowUnexpectedEndOfRequestContent();
}
} }
finally finally
{ {

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_readCompleted) if (_readCompleted)
{ {
_isReading = true; _isReading = true;
return _readResult; return new ReadResult(_readResult.Buffer, Interlocked.Exchange(ref _userCanceled, 0) == 1, _readResult.IsCompleted);
} }
TryStart(); TryStart();
@ -70,44 +70,47 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
catch (ConnectionAbortedException ex) catch (ConnectionAbortedException ex)
{ {
_isReading = false;
throw new TaskCanceledException("The request was aborted", ex); throw new TaskCanceledException("The request was aborted", ex);
} }
void ResetReadingState()
{
_isReading = false;
// Reset the timing read here for the next call to read.
StopTimingRead(0);
_context.Input.AdvanceTo(_readResult.Buffer.Start);
}
if (_context.RequestTimedOut) if (_context.RequestTimedOut)
{ {
ResetReadingState();
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout); BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
} }
// Make sure to handle when this is canceled here. if (_readResult.IsCompleted)
if (_readResult.IsCanceled)
{ {
if (Interlocked.Exchange(ref _userCanceled, 0) == 1) ResetReadingState();
{ ThrowUnexpectedEndOfRequestContent();
// Ignore the readResult if it wasn't by the user.
CreateReadResultFromConnectionReadResult();
break;
}
else
{
// Reset the timing read here for the next call to read.
StopTimingRead(0);
continue;
}
} }
var readableBuffer = _readResult.Buffer; // Ignore the canceled readResult if it wasn't canceled by the user.
var readableBufferLength = readableBuffer.Length; if (!_readResult.IsCanceled || Interlocked.Exchange(ref _userCanceled, 0) == 1)
StopTimingRead(readableBufferLength);
CheckCompletedReadResult(_readResult);
if (readableBufferLength > 0)
{ {
CreateReadResultFromConnectionReadResult(); var returnedReadResultLength = CreateReadResultFromConnectionReadResult();
// Don't count bytes belonging to the next request, since read rate timeouts are done on a per-request basis.
StopTimingRead(returnedReadResultLength);
if (_readResult.IsCompleted)
{
TryStop();
}
break; break;
} }
ResetReadingState();
} }
return _readResult; return _readResult;
@ -129,66 +132,69 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_readCompleted) if (_readCompleted)
{ {
_isReading = true; _isReading = true;
readResult = _readResult; readResult = new ReadResult(_readResult.Buffer, Interlocked.Exchange(ref _userCanceled, 0) == 1, _readResult.IsCompleted);
return true; return true;
} }
TryStart(); TryStart();
if (!_context.Input.TryRead(out _readResult)) // The while(true) because we don't want to return a canceled ReadResult if the user themselves didn't cancel it.
while (true)
{ {
readResult = default; if (!_context.Input.TryRead(out _readResult))
return false;
}
if (_readResult.IsCanceled)
{
if (Interlocked.Exchange(ref _userCanceled, 0) == 0)
{ {
// Cancellation wasn't by the user, return default ReadResult
readResult = default; readResult = default;
return false; return false;
} }
}
// Only set _isReading if we are returing true. if (!_readResult.IsCanceled || Interlocked.Exchange(ref _userCanceled, 0) == 1)
_isReading = true; {
break;
}
CreateReadResultFromConnectionReadResult(); _context.Input.AdvanceTo(_readResult.Buffer.Start);
readResult = _readResult;
CountBytesRead(readResult.Buffer.Length);
return true;
}
public override Task ConsumeAsync()
{
TryStart();
if (!_readResult.Buffer.IsEmpty && _inputLength == 0)
{
_context.Input.AdvanceTo(_readResult.Buffer.End);
}
return OnConsumeAsync();
}
private void CreateReadResultFromConnectionReadResult()
{
if (_readResult.Buffer.Length >= _inputLength + _examinedUnconsumedBytes)
{
_readCompleted = true;
_readResult = new ReadResult(
_readResult.Buffer.Slice(0, _inputLength + _examinedUnconsumedBytes),
_readResult.IsCanceled && Interlocked.Exchange(ref _userCanceled, 0) == 1,
_readCompleted);
} }
if (_readResult.IsCompleted) if (_readResult.IsCompleted)
{
_context.Input.AdvanceTo(_readResult.Buffer.Start);
ThrowUnexpectedEndOfRequestContent();
}
var returnedReadResultLength = CreateReadResultFromConnectionReadResult();
// Don't count bytes belonging to the next request, since read rate timeouts are done on a per-request basis.
CountBytesRead(returnedReadResultLength);
// Only set _isReading if we are returning true.
_isReading = true;
readResult = _readResult;
if (readResult.IsCompleted)
{ {
TryStop(); TryStop();
} }
return true;
}
private long CreateReadResultFromConnectionReadResult()
{
var initialLength = _readResult.Buffer.Length;
var maxLength = _inputLength + _examinedUnconsumedBytes;
if (initialLength < maxLength)
{
return initialLength;
}
_readCompleted = true;
_readResult = new ReadResult(
_readResult.Buffer.Slice(0, maxLength),
_readResult.IsCanceled,
isCompleted: true);
return maxLength;
} }
public override void AdvanceTo(SequencePosition consumed) public override void AdvanceTo(SequencePosition consumed)
@ -207,9 +213,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
if (_readCompleted) if (_readCompleted)
{ {
_readResult = new ReadResult(_readResult.Buffer.Slice(consumed, _readResult.Buffer.End), Interlocked.Exchange(ref _userCanceled, 0) == 1, _readCompleted); // If the old stored _readResult was canceled, it's already been observed. Do not store a canceled read result permanently.
_readResult = new ReadResult(_readResult.Buffer.Slice(consumed, _readResult.Buffer.End), isCanceled: false, _readCompleted);
if (_readResult.Buffer.Length == 0 && !_finalAdvanceCalled) if (!_finalAdvanceCalled && _readResult.Buffer.Length == 0)
{ {
_context.Input.AdvanceTo(consumed); _context.Input.AdvanceTo(consumed);
_finalAdvanceCalled = true; _finalAdvanceCalled = true;

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -21,19 +22,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_context = context; _context = context;
} }
protected void CheckCompletedReadResult(ReadResult result) [StackTraceHidden]
protected void ThrowUnexpectedEndOfRequestContent()
{ {
if (result.IsCompleted) // OnInputOrOutputCompleted() is an idempotent method that closes the connection. Sometimes
{ // input completion is observed here before the Input.OnWriterCompleted() callback is fired,
// OnInputOrOutputCompleted() is an idempotent method that closes the connection. Sometimes // so we call OnInputOrOutputCompleted() now to prevent a race in our tests where a 400
// input completion is observed here before the Input.OnWriterCompleted() callback is fired, // response is written after observing the unexpected end of request content instead of just
// so we call OnInputOrOutputCompleted() now to prevent a race in our tests where a 400 // closing the connection without a response as expected.
// response is written after observing the unexpected end of request content instead of just _context.OnInputOrOutputCompleted();
// closing the connection without a response as expected.
_context.OnInputOrOutputCompleted();
BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent); BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
}
} }
public abstract bool TryReadInternal(out ReadResult readResult); public abstract bool TryReadInternal(out ReadResult readResult);

View File

@ -25,19 +25,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public override ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default) public override ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default)
{ {
if (_completed) ThrowIfCompleted();
{
throw new InvalidOperationException("Reading is not allowed after the reader was completed.");
}
return _context.Input.ReadAsync(cancellationToken); return _context.Input.ReadAsync(cancellationToken);
} }
public override bool TryRead(out ReadResult result) public override bool TryRead(out ReadResult result)
{ {
if (_completed) ThrowIfCompleted();
{
throw new InvalidOperationException("Reading is not allowed after the reader was completed.");
}
return _context.Input.TryRead(out result); return _context.Input.TryRead(out result);
} }

View File

@ -221,7 +221,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
} }
else else
{ {
_logger?.LogError(2, ex, CoreStrings.BadDeveloperCertificateState); _logger?.LogError(3, ex, CoreStrings.BadDeveloperCertificateState);
} }
await sslStream.DisposeAsync(); await sslStream.DisposeAsync();

View File

@ -1189,6 +1189,56 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
} }
} }
[Fact]
public async Task UnexpectedEndOfRequestContentIsRepeatedlyThrownForContentLengthBody()
{
using (var input = new TestInput())
{
var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderContentLength = "5" }, input.Http1Connection);
var reader = new HttpRequestPipeReader();
reader.StartAcceptingReads(body);
input.Application.Output.Complete();
var ex0 = Assert.Throws<BadHttpRequestException>(() => reader.TryRead(out var readResult));
var ex1 = Assert.Throws<BadHttpRequestException>(() => reader.TryRead(out var readResult));
var ex2 = await Assert.ThrowsAsync<BadHttpRequestException>(() => reader.ReadAsync().AsTask());
var ex3 = await Assert.ThrowsAsync<BadHttpRequestException>(() => reader.ReadAsync().AsTask());
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex0.Reason);
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex1.Reason);
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex2.Reason);
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex3.Reason);
await body.StopAsync();
}
}
[Fact]
public async Task UnexpectedEndOfRequestContentIsRepeatedlyThrownForChunkedBody()
{
using (var input = new TestInput())
{
var body = Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders { HeaderTransferEncoding = "chunked" }, input.Http1Connection);
var reader = new HttpRequestPipeReader();
reader.StartAcceptingReads(body);
input.Application.Output.Complete();
var ex0 = Assert.Throws<BadHttpRequestException>(() => reader.TryRead(out var readResult));
var ex1 = Assert.Throws<BadHttpRequestException>(() => reader.TryRead(out var readResult));
var ex2 = await Assert.ThrowsAsync<BadHttpRequestException>(() => reader.ReadAsync().AsTask());
var ex3 = await Assert.ThrowsAsync<BadHttpRequestException>(() => reader.ReadAsync().AsTask());
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex0.Reason);
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex1.Reason);
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex2.Reason);
Assert.Equal(RequestRejectionReason.UnexpectedEndOfRequestContent, ex3.Reason);
await body.StopAsync();
}
}
[Fact] [Fact]
public async Task CompleteForChunkedAllowsConsumeToWork() public async Task CompleteForChunkedAllowsConsumeToWork()
{ {

View File

@ -408,7 +408,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
} }
await loggerProvider.FilterLogger.LogTcs.Task.DefaultTimeout(); await loggerProvider.FilterLogger.LogTcs.Task.DefaultTimeout();
Assert.Equal(2, loggerProvider.FilterLogger.LastEventId); Assert.Equal(3, loggerProvider.FilterLogger.LastEventId);
Assert.Equal(LogLevel.Error, loggerProvider.FilterLogger.LastLogLevel); Assert.Equal(LogLevel.Error, loggerProvider.FilterLogger.LastLogLevel);
} }

View File

@ -19,11 +19,12 @@ using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Serilog;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
{ {
public class RequestTests : LoggedTest public class RequestTests : TestApplicationErrorLoggerLoggedTest
{ {
[Fact] [Fact]
public async Task StreamsAreNotPersistedAcrossRequests() public async Task StreamsAreNotPersistedAcrossRequests()
@ -1440,6 +1441,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
} }
} }
[Fact]
public async Task ContentLengthSwallowedUnexpectedEndOfRequestContentDoesNotResultInWarnings()
{
var testContext = new TestServiceContext(LoggerFactory);
await using (var server = new TestServer(async httpContext =>
{
try
{
await httpContext.Request.Body.ReadAsync(new byte[1], 0, 1);
}
catch
{
}
}, testContext))
{
using (var connection = server.CreateConnection())
{
await connection.Send(
"POST / HTTP/1.1",
"Host:",
"Content-Length: 5",
"",
"");
connection.ShutdownSend();
await connection.ReceiveEnd();
}
}
Assert.Empty(TestApplicationErrorLogger.Messages.Where(m => m.LogLevel >= LogLevel.Warning));
}
[Fact] [Fact]
public async Task ContentLengthRequestCallCancelPendingReadWorks() public async Task ContentLengthRequestCallCancelPendingReadWorks()
{ {