diff --git a/.azure/pipelines/quarantined-pr.yml b/.azure/pipelines/quarantined-pr.yml
index d6e4b61ed2fe..adcff0e3054a 100644
--- a/.azure/pipelines/quarantined-pr.yml
+++ b/.azure/pipelines/quarantined-pr.yml
@@ -28,7 +28,7 @@ jobs:
jobName: Helix_quarantined_x64
jobDisplayName: 'Tests: Helix'
agentOs: Windows
- timeoutInMinutes: 240
+ timeoutInMinutes: 120
steps:
# Build the shared framework
- script: ./build.cmd -ci -nobl -all -pack -arch x64 /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log
@@ -53,7 +53,7 @@ jobs:
jobName: Windows_Quarantined_x64
jobDisplayName: 'Tests: Windows x64'
agentOs: Windows
- timeoutInMinutes: 240
+ timeoutInMinutes: 90
isTestingJob: true
steps:
- powershell: "& ./build.ps1 -CI -nobl -all -pack -NoBuildJava"
@@ -86,7 +86,7 @@ jobs:
jobName: MacOS_Quarantined_Test
jobDisplayName: "Tests: macOS 10.14"
agentOs: macOS
- timeoutInMinutes: 240
+ timeoutInMinutes: 60
isTestingJob: true
steps:
- bash: ./build.sh --all --pack --ci --nobl --no-build-java
@@ -119,7 +119,7 @@ jobs:
jobName: Linux_Quarantined_Test
jobDisplayName: "Tests: Ubuntu 16.04 x64"
agentOs: Linux
- timeoutInMinutes: 240
+ timeoutInMinutes: 60
isTestingJob: true
useHostedUbuntu: false
steps:
diff --git a/AspNetCore.sln b/AspNetCore.sln
index 978e53e91529..968ff850f8df 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1507,6 +1507,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagno
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagnostics.HealthChecks.Tests", "src\HealthChecks\HealthChecks\test\Microsoft.Extensions.Diagnostics.HealthChecks.Tests.csproj", "{7509AA1E-3093-4BEE-984F-E11579E98A11}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Tests", "src\JSInterop\Microsoft.JSInterop\test\Microsoft.JSInterop.Tests.csproj", "{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -7179,6 +7181,18 @@ Global
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x64.Build.0 = Release|Any CPU
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x86.ActiveCfg = Release|Any CPU
{7509AA1E-3093-4BEE-984F-E11579E98A11}.Release|x86.Build.0 = Release|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x64.Build.0 = Debug|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Debug|x86.Build.0 = Debug|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x64.ActiveCfg = Release|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x64.Build.0 = Release|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.ActiveCfg = Release|Any CPU
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -7934,6 +7948,7 @@ Global
{B06040BC-DA28-4923-8CAC-20EB517D471B} = {22D7D74B-565D-4047-97B4-F149B1A13350}
{55CACC1F-FE96-47C8-8073-91F4CAA55C75} = {2A91479A-4ABE-4BB7-9A5E-CA3B9CCFC69E}
{7509AA1E-3093-4BEE-984F-E11579E98A11} = {7CB09412-C9B0-47E8-A8C3-311AA4CFDE04}
+ {DAAB6B35-CBD2-4573-B633-CDD42F583A0E} = {16898702-3E33-41C1-B8D8-4CE3F1D46BD9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index 8da95c63148c..68e1f6149cd7 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -64,6 +64,7 @@ and are generated based on the last package release.
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index feef8a227779..f2dce7e4a609 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -205,6 +205,10 @@
https://github.com/dotnet/runtime
907f7da59b40c80941b02ac2a46650adf3f606bc
+
+ https://github.com/dotnet/runtime
+ 907f7da59b40c80941b02ac2a46650adf3f606bc
+
https://github.com/dotnet/runtime
907f7da59b40c80941b02ac2a46650adf3f606bc
diff --git a/eng/Versions.props b/eng/Versions.props
index 7dff1b4b2778..9df1d8b152df 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -108,6 +108,7 @@
5.0.0-rc.1.20417.14
5.0.0-rc.1.20417.14
5.0.0-rc.1.20417.14
+ 5.0.0-rc.1.20417.14
5.0.0-rc.1.20417.14
5.0.0-rc.1.20417.14
5.0.0-rc.1.20417.14
diff --git a/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs b/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs
index b35358fea233..513f92b1c6f6 100644
--- a/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs
+++ b/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenStore.cs
@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
+using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
@@ -57,7 +58,24 @@ public async Task GetRequestTokensAsync(HttpContext httpCon
{
// Check the content-type before accessing the form collection to make sure
// we report errors gracefully.
- var form = await httpContext.Request.ReadFormAsync();
+ IFormCollection form;
+ try
+ {
+ form = await httpContext.Request.ReadFormAsync();
+ }
+ catch (InvalidDataException ex)
+ {
+ // ReadFormAsync can throw InvalidDataException if the form content is malformed.
+ // Wrap it in an AntiforgeryValidationException and allow the caller to handle it as just another antiforgery failure.
+ throw new AntiforgeryValidationException(Resources.AntiforgeryToken_UnableToReadRequest, ex);
+ }
+ catch (IOException ex)
+ {
+ // Reading the request body (which happens as part of ReadFromAsync) may throw an exception if a client disconnects.
+ // Wrap it in an AntiforgeryValidationException and allow the caller to handle it as just another antiforgery failure.
+ throw new AntiforgeryValidationException(Resources.AntiforgeryToken_UnableToReadRequest, ex);
+ }
+
requestToken = form[_options.FormFieldName];
}
diff --git a/src/Antiforgery/src/Resources.resx b/src/Antiforgery/src/Resources.resx
index eeda70bc6388..1bf0528d9e35 100644
--- a/src/Antiforgery/src/Resources.resx
+++ b/src/Antiforgery/src/Resources.resx
@@ -136,6 +136,9 @@
Validation of the provided antiforgery token failed. The cookie token and the request token were swapped.
+
+ Unable to read the antiforgery request token from the posted form.
+
The provided antiforgery token was meant for user "{0}", but the current user is "{1}".
diff --git a/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs b/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs
index e4af2032f52f..8456ee318ad1 100644
--- a/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs
+++ b/src/Antiforgery/test/DefaultAntiforgeryTokenStoreTest.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
@@ -235,6 +237,56 @@ public async Task GetRequestTokens_BothHeaderValueAndFormFieldsEmpty_ReturnsNull
Assert.Null(tokenSet.RequestToken);
}
+ [Fact]
+ public async Task GetRequestTokens_ReadFormAsyncThrowsIOException_ThrowsAntiforgeryValidationException()
+ {
+ // Arrange
+ var ioException = new IOException();
+ var httpContext = new Mock();
+
+ httpContext.Setup(r => r.Request.Cookies).Returns(Mock.Of());
+ httpContext.SetupGet(r => r.Request.HasFormContentType).Returns(true);
+ httpContext.Setup(r => r.Request.ReadFormAsync(It.IsAny())).Throws(ioException);
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = null,
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act & Assert
+ var ex = await Assert.ThrowsAsync(() => tokenStore.GetRequestTokensAsync(httpContext.Object));
+ Assert.Same(ioException, ex.InnerException);
+ }
+
+ [Fact]
+ public async Task GetRequestTokens_ReadFormAsyncThrowsInvalidDataException_ThrowsAntiforgeryValidationException()
+ {
+ // Arrange
+ var exception = new InvalidDataException();
+ var httpContext = new Mock();
+
+ httpContext.Setup(r => r.Request.Cookies).Returns(Mock.Of());
+ httpContext.SetupGet(r => r.Request.HasFormContentType).Returns(true);
+ httpContext.Setup(r => r.Request.ReadFormAsync(It.IsAny())).Throws(exception);
+
+ var options = new AntiforgeryOptions
+ {
+ Cookie = { Name = "cookie-name" },
+ FormFieldName = "form-field-name",
+ HeaderName = null,
+ };
+
+ var tokenStore = new DefaultAntiforgeryTokenStore(new TestOptionsManager(options));
+
+ // Act & Assert
+ var ex = await Assert.ThrowsAsync(() => tokenStore.GetRequestTokensAsync(httpContext.Object));
+ Assert.Same(exception, ex.InnerException);
+ }
+
[Theory]
[InlineData(false, CookieSecurePolicy.SameAsRequest, null)]
[InlineData(true, CookieSecurePolicy.SameAsRequest, true)]
diff --git a/src/Components/Components/src/ComponentFactory.cs b/src/Components/Components/src/ComponentFactory.cs
index eddb39a937bf..10c9b1aa06b6 100644
--- a/src/Components/Components/src/ComponentFactory.cs
+++ b/src/Components/Components/src/ComponentFactory.cs
@@ -63,7 +63,7 @@ private Action CreateInitializer(Type type)
(
propertyName: property.Name,
propertyType: property.PropertyType,
- setter: MemberAssignment.CreatePropertySetter(type, property, cascading: false)
+ setter: new PropertySetter(type, property)
)).ToArray();
return Initialize;
diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs
index 1b6e43d7ba4e..dd6aafab26e2 100644
--- a/src/Components/Components/src/Reflection/ComponentProperties.cs
+++ b/src/Components/Components/src/Reflection/ComponentProperties.cs
@@ -144,7 +144,7 @@ public static void SetProperties(in ParameterView parameters, object target)
}
}
- static void SetProperty(object target, IPropertySetter writer, string parameterName, object value)
+ static void SetProperty(object target, PropertySetter writer, string parameterName, object value)
{
try
{
@@ -246,13 +246,13 @@ private static void ThrowForInvalidCaptureUnmatchedValuesParameterType(Type targ
private class WritersForType
{
private const int MaxCachedWriterLookups = 100;
- private readonly Dictionary _underlyingWriters;
- private readonly ConcurrentDictionary _referenceEqualityWritersCache;
+ private readonly Dictionary _underlyingWriters;
+ private readonly ConcurrentDictionary _referenceEqualityWritersCache;
public WritersForType(Type targetType)
{
- _underlyingWriters = new Dictionary(StringComparer.OrdinalIgnoreCase);
- _referenceEqualityWritersCache = new ConcurrentDictionary(ReferenceEqualityComparer.Instance);
+ _underlyingWriters = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ _referenceEqualityWritersCache = new ConcurrentDictionary(ReferenceEqualityComparer.Instance);
foreach (var propertyInfo in GetCandidateBindableProperties(targetType))
{
@@ -271,7 +271,10 @@ public WritersForType(Type targetType)
$"The type '{targetType.FullName}' declares a parameter matching the name '{propertyName}' that is not public. Parameters must be public.");
}
- var propertySetter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo, cascading: cascadingParameterAttribute != null);
+ var propertySetter = new PropertySetter(targetType, propertyInfo)
+ {
+ Cascading = cascadingParameterAttribute != null,
+ };
if (_underlyingWriters.ContainsKey(propertyName))
{
@@ -298,17 +301,17 @@ public WritersForType(Type targetType)
ThrowForInvalidCaptureUnmatchedValuesParameterType(targetType, propertyInfo);
}
- CaptureUnmatchedValuesWriter = MemberAssignment.CreatePropertySetter(targetType, propertyInfo, cascading: false);
+ CaptureUnmatchedValuesWriter = new PropertySetter(targetType, propertyInfo);
CaptureUnmatchedValuesPropertyName = propertyInfo.Name;
}
}
}
- public IPropertySetter? CaptureUnmatchedValuesWriter { get; }
+ public PropertySetter? CaptureUnmatchedValuesWriter { get; }
public string? CaptureUnmatchedValuesPropertyName { get; }
- public bool TryGetValue(string parameterName, [MaybeNullWhen(false)] out IPropertySetter writer)
+ public bool TryGetValue(string parameterName, [MaybeNullWhen(false)] out PropertySetter writer)
{
// In intensive parameter-passing scenarios, one of the most expensive things we do is the
// lookup from parameterName to writer. Pre-5.0 that was because of the string hashing.
diff --git a/src/Components/Components/src/Reflection/IPropertySetter.cs b/src/Components/Components/src/Reflection/IPropertySetter.cs
index d6a60e2395ae..5cd1cb0494d5 100644
--- a/src/Components/Components/src/Reflection/IPropertySetter.cs
+++ b/src/Components/Components/src/Reflection/IPropertySetter.cs
@@ -1,12 +1,55 @@
// 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.Reflection;
+
namespace Microsoft.AspNetCore.Components.Reflection
{
- internal interface IPropertySetter
+ internal sealed class PropertySetter
{
- bool Cascading { get; }
+ private static readonly MethodInfo CallPropertySetterOpenGenericMethod =
+ typeof(PropertySetter).GetMethod(nameof(CallPropertySetter), BindingFlags.NonPublic | BindingFlags.Static)!;
+
+ private readonly Action