Skip to content

Commit 4a4d930

Browse files
committed
Merge in 'release/6.0' changes
2 parents 029e526 + b1d2844 commit 4a4d930

File tree

7 files changed

+220
-9
lines changed

7 files changed

+220
-9
lines changed

src/Http/WebUtilities/src/FileBufferingReadStream.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ public override int Read(Span<byte> buffer)
317317
{
318318
_buffer.Write(buffer.Slice(0, read));
319319
}
320-
else
320+
// Allow zero-byte reads
321+
else if (buffer.Length > 0)
321322
{
322323
_completelyBuffered = true;
323324
}
@@ -392,7 +393,8 @@ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, Cancellation
392393
{
393394
await _buffer.WriteAsync(buffer.Slice(0, read), cancellationToken);
394395
}
395-
else
396+
// Allow zero-byte reads
397+
else if (buffer.Length > 0)
396398
{
397399
_completelyBuffered = true;
398400
}

src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,39 @@ public void FileBufferingReadStream_Properties_ExpectedValues()
3535
}
3636
}
3737

38+
[Fact]
39+
public void FileBufferingReadStream_Sync0ByteReadUnderThreshold_DoesntCreateFile()
40+
{
41+
var inner = MakeStream(1024);
42+
using (var stream = new FileBufferingReadStream(inner, 1024 * 2, null, Directory.GetCurrentDirectory()))
43+
{
44+
var bytes = new byte[1000];
45+
var read0 = stream.Read(bytes, 0, 0);
46+
Assert.Equal(0, read0);
47+
Assert.Equal(read0, stream.Length);
48+
Assert.Equal(read0, stream.Position);
49+
Assert.True(stream.InMemory);
50+
Assert.Null(stream.TempFileName);
51+
52+
var read1 = stream.Read(bytes, 0, bytes.Length);
53+
Assert.Equal(bytes.Length, read1);
54+
Assert.Equal(read0 + read1, stream.Length);
55+
Assert.Equal(read0 + read1, stream.Position);
56+
Assert.True(stream.InMemory);
57+
Assert.Null(stream.TempFileName);
58+
59+
var read2 = stream.Read(bytes, 0, bytes.Length);
60+
Assert.Equal(inner.Length - read0 - read1, read2);
61+
Assert.Equal(read0 + read1 + read2, stream.Length);
62+
Assert.Equal(read0 + read1 + read2, stream.Position);
63+
Assert.True(stream.InMemory);
64+
Assert.Null(stream.TempFileName);
65+
66+
var read3 = stream.Read(bytes, 0, bytes.Length);
67+
Assert.Equal(0, read3);
68+
}
69+
}
70+
3871
[Fact]
3972
public void FileBufferingReadStream_SyncReadUnderThreshold_DoesntCreateFile()
4073
{
@@ -164,6 +197,39 @@ public void FileBufferingReadStream_SyncReadWithOnDiskLimit_EnforcesLimit()
164197

165198
///////////////////
166199

200+
[Fact]
201+
public async Task FileBufferingReadStream_Async0ByteReadUnderThreshold_DoesntCreateFile()
202+
{
203+
var inner = MakeStream(1024);
204+
using (var stream = new FileBufferingReadStream(inner, 1024 * 2, null, Directory.GetCurrentDirectory()))
205+
{
206+
var bytes = new byte[1000];
207+
var read0 = await stream.ReadAsync(bytes, 0, 0);
208+
Assert.Equal(0, read0);
209+
Assert.Equal(read0, stream.Length);
210+
Assert.Equal(read0, stream.Position);
211+
Assert.True(stream.InMemory);
212+
Assert.Null(stream.TempFileName);
213+
214+
var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);
215+
Assert.Equal(bytes.Length, read1);
216+
Assert.Equal(read0 + read1, stream.Length);
217+
Assert.Equal(read0 + read1, stream.Position);
218+
Assert.True(stream.InMemory);
219+
Assert.Null(stream.TempFileName);
220+
221+
var read2 = await stream.ReadAsync(bytes, 0, bytes.Length);
222+
Assert.Equal(inner.Length - read0 - read1, read2);
223+
Assert.Equal(read0 + read1 + read2, stream.Length);
224+
Assert.Equal(read0 + read1 + read2, stream.Position);
225+
Assert.True(stream.InMemory);
226+
Assert.Null(stream.TempFileName);
227+
228+
var read3 = await stream.ReadAsync(bytes, 0, bytes.Length);
229+
Assert.Equal(0, read3);
230+
}
231+
}
232+
167233
[Fact]
168234
public async Task FileBufferingReadStream_AsyncReadUnderThreshold_DoesntCreateFile()
169235
{
@@ -236,6 +302,47 @@ public async Task FileBufferingReadStream_AsyncReadOverThreshold_CreatesFile()
236302
Assert.False(File.Exists(tempFileName));
237303
}
238304

305+
[Fact]
306+
public async Task FileBufferingReadStream_Async0ByteReadAfterBuffering_ReadsFromFile()
307+
{
308+
var inner = MakeStream(1024 * 2);
309+
string tempFileName;
310+
using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
311+
{
312+
await stream.DrainAsync(default);
313+
stream.Position = 0;
314+
Assert.Equal(inner.Length, stream.Length);
315+
Assert.Equal(0, stream.Position);
316+
Assert.False(stream.InMemory);
317+
Assert.NotNull(stream.TempFileName);
318+
tempFileName = stream.TempFileName!;
319+
Assert.True(File.Exists(tempFileName));
320+
321+
var bytes = new byte[1000];
322+
var read0 = await stream.ReadAsync(bytes, 0, 0);
323+
Assert.Equal(0, read0);
324+
Assert.Equal(read0, stream.Position);
325+
326+
var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);
327+
Assert.Equal(bytes.Length, read1);
328+
Assert.Equal(read0 + read1, stream.Position);
329+
330+
var read2 = await stream.ReadAsync(bytes, 0, bytes.Length);
331+
Assert.Equal(bytes.Length, read2);
332+
Assert.Equal(read0 + read1 + read2, stream.Position);
333+
334+
var read3 = await stream.ReadAsync(bytes, 0, bytes.Length);
335+
Assert.Equal(inner.Length - read0 - read1 - read2, read3);
336+
Assert.Equal(read0 + read1 + read2 + read3, stream.Length);
337+
Assert.Equal(read0 + read1 + read2 + read3, stream.Position);
338+
339+
var read4 = await stream.ReadAsync(bytes, 0, bytes.Length);
340+
Assert.Equal(0, read4);
341+
}
342+
343+
Assert.False(File.Exists(tempFileName));
344+
}
345+
239346
[Fact]
240347
public async Task FileBufferingReadStream_AsyncReadWithInMemoryLimit_EnforcesLimit()
241348
{

src/Servers/IIS/IIS/src/Core/IISHttpContext.IO.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ private async Task WriteBody(bool flush = false)
162162
var buffer = result.Buffer;
163163
try
164164
{
165+
if (_bodyOutput.Aborted)
166+
{
167+
break;
168+
}
169+
165170
if (!buffer.IsEmpty)
166171
{
167172
await AsyncIO!.WriteAsync(buffer);

src/Servers/IIS/IIS/src/Core/OutputProducer.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ namespace Microsoft.AspNetCore.Server.IIS.Core
1313
{
1414
internal class OutputProducer
1515
{
16-
// This locks access to _completed.
16+
// This locks access to _completed and _aborted.
1717
private readonly object _contextLock = new object();
1818
private bool _completed;
19+
private volatile bool _aborted;
1920

2021
private readonly Pipe _pipe;
2122

@@ -32,6 +33,8 @@ public OutputProducer(Pipe pipe)
3233
}
3334

3435
public PipeReader Reader => _pipe.Reader;
36+
37+
public bool Aborted => _aborted;
3538

3639
public Task FlushAsync(CancellationToken cancellationToken)
3740
{
@@ -44,7 +47,7 @@ public void Complete()
4447
{
4548
lock (_contextLock)
4649
{
47-
if (_completed)
50+
if (_completed || _aborted)
4851
{
4952
return;
5053
}
@@ -58,12 +61,12 @@ public void Abort(Exception error)
5861
{
5962
lock (_contextLock)
6063
{
61-
if (_completed)
64+
if (_completed || _aborted)
6265
{
6366
return;
6467
}
6568

66-
_completed = true;
69+
_aborted = true;
6770

6871
_pipe.Reader.CancelPendingRead();
6972
_pipe.Writer.Complete();
@@ -74,7 +77,7 @@ public Task WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellati
7477
{
7578
lock (_contextLock)
7679
{
77-
if (_completed)
80+
if (_completed || _aborted)
7881
{
7982
return Task.CompletedTask;
8083
}

src/Servers/IIS/IIS/test/IIS.Tests/ResponseAbortTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public async Task ClosesWithoutSendingAnything()
3333
}
3434

3535
[ConditionalFact]
36-
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/31404")]
3736
public async Task ClosesAfterDataSent()
3837
{
3938
var bodyReceived = CreateTaskCompletionSource();

src/Servers/Kestrel/Core/src/Internal/SniOptionsSelector.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,17 @@ private LongestStringFirstComparer()
225225
public int Compare(string? x, string? y)
226226
{
227227
// Flip x and y to put the longest instead of the shortest string first in the SortedList.
228-
return y!.Length.CompareTo(x!.Length);
228+
// SortedList does not support duplicate entries, so fall back to
229+
// StringComparison.OrdinalIgnoreCase behavior for equal length strings.
230+
var lengthResult = y!.Length.CompareTo(x!.Length);
231+
if (lengthResult != 0)
232+
{
233+
return lengthResult;
234+
}
235+
else
236+
{
237+
return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
238+
}
229239
}
230240
}
231241
}

src/Servers/Kestrel/Core/test/SniOptionsSelectorTests.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,91 @@ public void ServerNameMatchingIsCaseInsensitive()
186186
Assert.Equal("WildcardPrefix", pathDictionary[aSubdomainOptions.ServerCertificate]);
187187
}
188188

189+
[Fact]
190+
public void MultipleWildcardPrefixServerNamesOfSameLengthAreAllowed()
191+
{
192+
var sniDictionary = new Dictionary<string, SniConfig>
193+
{
194+
{
195+
"*.a.example.org",
196+
new SniConfig
197+
{
198+
Certificate = new CertificateConfig
199+
{
200+
Path = "a"
201+
}
202+
}
203+
},
204+
{
205+
"*.b.example.org",
206+
new SniConfig
207+
{
208+
Certificate = new CertificateConfig
209+
{
210+
Path = "b"
211+
}
212+
}
213+
}
214+
};
215+
216+
var mockCertificateConfigLoader = new MockCertificateConfigLoader();
217+
var pathDictionary = mockCertificateConfigLoader.CertToPathDictionary;
218+
219+
var sniOptionsSelector = new SniOptionsSelector(
220+
"TestEndpointName",
221+
sniDictionary,
222+
mockCertificateConfigLoader,
223+
fallbackHttpsOptions: new HttpsConnectionAdapterOptions(),
224+
fallbackHttpProtocols: HttpProtocols.Http1AndHttp2,
225+
logger: Mock.Of<ILogger<HttpsConnectionMiddleware>>());
226+
227+
var (aSubdomainOptions, _) = sniOptionsSelector.GetOptions(new MockConnectionContext(), "c.a.example.org");
228+
Assert.Equal("a", pathDictionary[aSubdomainOptions.ServerCertificate]);
229+
230+
var (bSubdomainOptions, _) = sniOptionsSelector.GetOptions(new MockConnectionContext(), "c.b.example.org");
231+
Assert.Equal("b", pathDictionary[bSubdomainOptions.ServerCertificate]);
232+
}
233+
234+
[Fact]
235+
public void DuplicateWildcardPrefixServerNamesThrowsArgumentException()
236+
{
237+
var sniDictionary = new Dictionary<string, SniConfig>
238+
{
239+
{
240+
"*.example.org",
241+
new SniConfig
242+
{
243+
Certificate = new CertificateConfig
244+
{
245+
Path = "a"
246+
}
247+
}
248+
},
249+
{
250+
"*.EXAMPLE.org",
251+
new SniConfig
252+
{
253+
Certificate = new CertificateConfig
254+
{
255+
Path = "b"
256+
}
257+
}
258+
}
259+
};
260+
261+
var mockCertificateConfigLoader = new MockCertificateConfigLoader();
262+
var pathDictionary = mockCertificateConfigLoader.CertToPathDictionary;
263+
264+
var exception = Assert.Throws<ArgumentException>(() => new SniOptionsSelector(
265+
"TestEndpointName",
266+
sniDictionary,
267+
mockCertificateConfigLoader,
268+
fallbackHttpsOptions: new HttpsConnectionAdapterOptions(),
269+
fallbackHttpProtocols: HttpProtocols.Http1AndHttp2,
270+
logger: Mock.Of<ILogger<HttpsConnectionMiddleware>>()));
271+
Assert.Equal("An item with the same key has already been added. Key: .EXAMPLE.org (Parameter 'key')", exception.Message);
272+
}
273+
189274
[Fact]
190275
public void GetOptionsThrowsAnAuthenticationExceptionIfThereIsNoMatchingSniSection()
191276
{

0 commit comments

Comments
 (0)