Skip to content

Commit 9d60f25

Browse files
authored
Merge branch 'main' of https://github.com/dotnet/aspnetcore into darc-main-1709b9ed-50a0-4e28-b9b8-0e3759aee720
2 parents b782842 + 885cbe3 commit 9d60f25

File tree

84 files changed

+1196
-546
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1196
-546
lines changed

eng/targets/CSharp.Common.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<LangVersion>9.0</LangVersion>
4+
<LangVersion>10.0</LangVersion>
55

66
<!-- Enables Strict mode for Roslyn compiler -->
77
<Features>strict;nullablePublicOnly</Features>

eng/targets/CSharp.Common.targets

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
'$(IsMicrobenchmarksProject)' == 'true'">$(NoWarn);CA1416</NoWarn>
3030

3131
<!-- Enable .NET code style analysis during build for src projects. -->
32+
<!-- Workaround bug where turning this on produces warnings in VS -->
33+
<EnforceCodeStyleInBuild Condition="'$(VisualStudioVersion)' &lt; '17.0'">false</EnforceCodeStyleInBuild>
3234
<EnforceCodeStyleInBuild Condition="'$(EnforceCodeStyleInBuild)' == ''">true</EnforceCodeStyleInBuild>
3335
</PropertyGroup>
3436

eng/tools/Directory.Build.props

+1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
<IsPackable>false</IsPackable>
66
<DisablePackageReferenceRestrictions>true</DisablePackageReferenceRestrictions>
77
<Nullable>disable</Nullable>
8+
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
89
</PropertyGroup>
910
</Project>

global.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"sdk": {
3-
"version": "6.0.100-preview.7.21356.3"
3+
"version": "6.0.100-preview.7.21359.3"
44
},
55
"tools": {
6-
"dotnet": "6.0.100-preview.7.21356.3",
6+
"dotnet": "6.0.100-preview.7.21359.3",
77
"runtimes": {
88
"dotnet/x64": [
99
"2.1.27",

src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<SignAssembly>false</SignAssembly>
1010
<IsTestAssetProject>true</IsTestAssetProject>
1111
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
12+
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
1213
</PropertyGroup>
1314

1415
<ItemGroup>

src/Http/Headers/src/ContentDispositionHeaderValue.cs

+57-18
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class ContentDispositionHeaderValue
3030
private const string ModificationDateString = "modification-date";
3131
private const string ReadDateString = "read-date";
3232
private const string SizeString = "size";
33+
private const int MaxStackAllocSizeBytes = 256;
3334
private static readonly char[] QuestionMark = new char[] { '?' };
3435
private static readonly char[] SingleQuote = new char[] { '\'' };
3536
private static readonly char[] EscapeChars = new char[] { '\\', '"' };
@@ -543,14 +544,17 @@ private static bool RequiresEncoding(StringSegment input)
543544

544545
// Encode using MIME encoding
545546
// And adds surrounding quotes, Encoded data must always be quoted, the equals signs are invalid in tokens
547+
[SkipLocalsInit]
546548
private string EncodeMimeWithQuotes(StringSegment input)
547549
{
548550
var requiredLength = MimePrefix.Length +
549551
Base64.GetMaxEncodedToUtf8Length(Encoding.UTF8.GetByteCount(input.AsSpan())) +
550552
MimeSuffix.Length;
551-
Span<byte> buffer = requiredLength <= 256
552-
? (stackalloc byte[256]).Slice(0, requiredLength)
553-
: new byte[requiredLength];
553+
byte[]? bufferFromPool = null;
554+
Span<byte> buffer = requiredLength <= MaxStackAllocSizeBytes
555+
? stackalloc byte[MaxStackAllocSizeBytes]
556+
: bufferFromPool = ArrayPool<byte>.Shared.Rent(requiredLength);
557+
buffer = buffer[..requiredLength];
554558

555559
MimePrefix.CopyTo(buffer);
556560
var bufferContent = buffer.Slice(MimePrefix.Length);
@@ -560,7 +564,14 @@ private string EncodeMimeWithQuotes(StringSegment input)
560564

561565
MimeSuffix.CopyTo(bufferContent.Slice(base64ContentLength));
562566

563-
return Encoding.UTF8.GetString(buffer.Slice(0, MimePrefix.Length + base64ContentLength + MimeSuffix.Length));
567+
var result = Encoding.UTF8.GetString(buffer.Slice(0, MimePrefix.Length + base64ContentLength + MimeSuffix.Length));
568+
569+
if (bufferFromPool is not null)
570+
{
571+
ArrayPool<byte>.Shared.Return(bufferFromPool);
572+
}
573+
574+
return result;
564575
}
565576

566577
// Attempt to decode MIME encoded strings
@@ -607,32 +618,60 @@ private bool TryDecodeMime(StringSegment input, [NotNullWhen(true)] out string?
607618

608619
// Encode a string using RFC 5987 encoding
609620
// encoding'lang'PercentEncodedSpecials
621+
[SkipLocalsInit]
610622
private static string Encode5987(StringSegment input)
611623
{
612624
var builder = new StringBuilder("UTF-8\'\'");
613-
for (int i = 0; i < input.Length; i++)
625+
626+
var maxInputBytes = Encoding.UTF8.GetMaxByteCount(input.Length);
627+
byte[]? bufferFromPool = null;
628+
Span<byte> inputBytes = maxInputBytes <= MaxStackAllocSizeBytes
629+
? stackalloc byte[MaxStackAllocSizeBytes]
630+
: bufferFromPool = ArrayPool<byte>.Shared.Rent(maxInputBytes);
631+
632+
var bytesWritten = Encoding.UTF8.GetBytes(input, inputBytes);
633+
inputBytes = inputBytes[..bytesWritten];
634+
635+
int totalBytesConsumed = 0;
636+
while (totalBytesConsumed < inputBytes.Length)
614637
{
615-
var c = input[i];
616-
// attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
617-
// ; token except ( "*" / "'" / "%" )
618-
if (c > 0x7F) // Encodes as multiple utf-8 bytes
638+
if (inputBytes[totalBytesConsumed] <= 0x7F)
619639
{
620-
var bytes = Encoding.UTF8.GetBytes(c.ToString());
621-
foreach (byte b in bytes)
640+
// This is an ASCII char. Let's handle it ourselves.
641+
642+
char c = (char)inputBytes[totalBytesConsumed];
643+
if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
622644
{
623-
HexEscape(builder, (char)b);
645+
HexEscape(builder, c);
624646
}
625-
}
626-
else if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
627-
{
628-
// ASCII - Only one encoded byte
629-
HexEscape(builder, c);
647+
else
648+
{
649+
builder.Append(c);
650+
}
651+
652+
totalBytesConsumed++;
630653
}
631654
else
632655
{
633-
builder.Append(c);
656+
// Non-ASCII, let's rely on Rune to decode it.
657+
658+
Rune.DecodeFromUtf8(inputBytes.Slice(totalBytesConsumed), out Rune r, out int bytesConsumedForRune);
659+
Contract.Assert(!r.IsAscii, "We shouldn't have gotten here if the Rune is ASCII.");
660+
661+
for (int i = 0; i < bytesConsumedForRune; i++)
662+
{
663+
HexEscape(builder, (char)inputBytes[totalBytesConsumed + i]);
664+
}
665+
666+
totalBytesConsumed += bytesConsumedForRune;
634667
}
635668
}
669+
670+
if (bufferFromPool is not null)
671+
{
672+
ArrayPool<byte>.Shared.Return(bufferFromPool);
673+
}
674+
636675
return builder.ToString();
637676
}
638677

src/Http/Headers/src/Microsoft.Net.Http.Headers.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<PackageTags>http</PackageTags>
99
<IsPackable>false</IsPackable>
1010
<Nullable>enable</Nullable>
11+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1112
</PropertyGroup>
1213

1314
<ItemGroup>

src/Http/Headers/test/ContentDispositionHeaderValueTest.cs

+11
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,17 @@ public void HeaderNamesWithQuotes_ExpectNamesToNotHaveQuotes()
600600
Assert.Equal(expectedFileName, result.FileName);
601601
}
602602

603+
[Fact]
604+
public void FileNameWithSurrogatePairs_EncodedCorrectly()
605+
{
606+
var contentDisposition = new ContentDispositionHeaderValue("attachment");
607+
608+
contentDisposition.SetHttpFileName("File 🤩 name.txt");
609+
Assert.Equal("File __ name.txt", contentDisposition.FileName);
610+
Assert.Equal(2, contentDisposition.Parameters.Count);
611+
Assert.Equal("UTF-8\'\'File%20%F0%9F%A4%A9%20name.txt", contentDisposition.Parameters[1].Value);
612+
}
613+
603614
public class ContentDispositionValue
604615
{
605616
public ContentDispositionValue(string value, string description, bool valid)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text.Json.Serialization;
7+
using Microsoft.AspNetCore.Mvc;
8+
9+
namespace Microsoft.AspNetCore.Http.Extensions
10+
{
11+
/// <summary>
12+
/// A <see cref="ProblemDetails"/> for validation errors.
13+
/// </summary>
14+
[JsonConverter(typeof(HttpValidationProblemDetailsJsonConverter))]
15+
public class HttpValidationProblemDetails : ProblemDetails
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of <see cref="HttpValidationProblemDetails"/>.
19+
/// </summary>
20+
public HttpValidationProblemDetails()
21+
: this(new Dictionary<string, string[]>(StringComparer.Ordinal))
22+
{
23+
}
24+
25+
/// <summary>
26+
/// Initializes a new instance of <see cref="HttpValidationProblemDetails"/> using the specified <paramref name="errors"/>.
27+
/// </summary>
28+
/// <param name="errors">The validation errors.</param>
29+
public HttpValidationProblemDetails(IDictionary<string, string[]> errors)
30+
: this(new Dictionary<string, string[]>(errors, StringComparer.Ordinal))
31+
{
32+
}
33+
34+
private HttpValidationProblemDetails(Dictionary<string, string[]> errors)
35+
{
36+
Title = "One or more validation errors occurred.";
37+
Errors = errors;
38+
}
39+
40+
/// <summary>
41+
/// Gets the validation errors associated with this instance of <see cref="HttpValidationProblemDetails"/>.
42+
/// </summary>
43+
public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
44+
}
45+
}

src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>ASP.NET Core common extension methods for HTTP abstractions, HTTP headers, HTTP request/response, and session state.</Description>
@@ -11,9 +11,11 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" />
15-
<Compile Include="$(SharedSourceRoot)TryParseMethodCache.cs" />
16-
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" />
14+
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\**\*.cs" LinkBase="Shared"/>
15+
<Compile Include="$(SharedSourceRoot)TryParseMethodCache.cs" LinkBase="Shared"/>
16+
<Compile Include="..\..\Shared\StreamCopyOperationInternal.cs" LinkBase="Shared" />
17+
<Compile Include="$(SharedSourceRoot)ProblemDetailsJsonConverter.cs" LinkBase="Shared"/>
18+
<Compile Include="$(SharedSourceRoot)HttpValidationProblemDetailsJsonConverter.cs" LinkBase="Shared" />
1719
</ItemGroup>
1820

1921
<ItemGroup>

src/Mvc/Mvc.Core/src/ProblemDetails.cs renamed to src/Http/Http.Extensions/src/ProblemDetails.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Text.Json.Serialization;
7-
using Microsoft.AspNetCore.Mvc.Infrastructure;
7+
using Microsoft.AspNetCore.Http.Extensions;
88

99
namespace Microsoft.AspNetCore.Mvc
1010
{

src/Http/Http.Extensions/src/PublicAPI.Unshipped.txt

+17
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@
8989
*REMOVED*~static Microsoft.AspNetCore.Http.SessionExtensions.GetString(this Microsoft.AspNetCore.Http.ISession session, string key) -> string
9090
*REMOVED*~static Microsoft.AspNetCore.Http.SessionExtensions.SetInt32(this Microsoft.AspNetCore.Http.ISession session, string key, int value) -> void
9191
*REMOVED*~static Microsoft.AspNetCore.Http.SessionExtensions.SetString(this Microsoft.AspNetCore.Http.ISession session, string key, string value) -> void
92+
Microsoft.AspNetCore.Http.Extensions.HttpValidationProblemDetails
93+
Microsoft.AspNetCore.Http.Extensions.HttpValidationProblemDetails.Errors.get -> System.Collections.Generic.IDictionary<string!, string![]!>!
94+
Microsoft.AspNetCore.Http.Extensions.HttpValidationProblemDetails.HttpValidationProblemDetails() -> void
95+
Microsoft.AspNetCore.Http.Extensions.HttpValidationProblemDetails.HttpValidationProblemDetails(System.Collections.Generic.IDictionary<string!, string![]!>! errors) -> void
9296
Microsoft.AspNetCore.Http.Extensions.QueryBuilder.Add(string! key, System.Collections.Generic.IEnumerable<string!>! values) -> void
9397
Microsoft.AspNetCore.Http.Extensions.QueryBuilder.Add(string! key, string! value) -> void
9498
Microsoft.AspNetCore.Http.Extensions.QueryBuilder.GetEnumerator() -> System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string!, string!>>!
@@ -153,6 +157,19 @@ Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetCookie.get -> System.Collec
153157
Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetCookie.set -> void
154158
Microsoft.AspNetCore.Http.Headers.ResponseHeaders.SetList<T>(string! name, System.Collections.Generic.IList<T>? values) -> void
155159
Microsoft.AspNetCore.Http.RequestDelegateFactory
160+
Microsoft.AspNetCore.Mvc.ProblemDetails
161+
Microsoft.AspNetCore.Mvc.ProblemDetails.Detail.get -> string?
162+
Microsoft.AspNetCore.Mvc.ProblemDetails.Detail.set -> void
163+
Microsoft.AspNetCore.Mvc.ProblemDetails.Extensions.get -> System.Collections.Generic.IDictionary<string!, object?>!
164+
Microsoft.AspNetCore.Mvc.ProblemDetails.Instance.get -> string?
165+
Microsoft.AspNetCore.Mvc.ProblemDetails.Instance.set -> void
166+
Microsoft.AspNetCore.Mvc.ProblemDetails.ProblemDetails() -> void
167+
Microsoft.AspNetCore.Mvc.ProblemDetails.Status.get -> int?
168+
Microsoft.AspNetCore.Mvc.ProblemDetails.Status.set -> void
169+
Microsoft.AspNetCore.Mvc.ProblemDetails.Title.get -> string?
170+
Microsoft.AspNetCore.Mvc.ProblemDetails.Title.set -> void
171+
Microsoft.AspNetCore.Mvc.ProblemDetails.Type.get -> string?
172+
Microsoft.AspNetCore.Mvc.ProblemDetails.Type.set -> void
156173
override Microsoft.AspNetCore.Http.Extensions.QueryBuilder.Equals(object? obj) -> bool
157174
override Microsoft.AspNetCore.Http.Extensions.QueryBuilder.ToString() -> string!
158175
static Microsoft.AspNetCore.Http.Extensions.HttpRequestMultipartExtensions.GetMultipartBoundary(this Microsoft.AspNetCore.Http.HttpRequest! request) -> string!

0 commit comments

Comments
 (0)