diff --git a/src/Components/Server/src/Circuits/RemoteJSDataStream.cs b/src/Components/Server/src/Circuits/RemoteJSDataStream.cs index f29cfb757d9b47f57829d170bc39a9d0f609d34b..c278237a675ff3bc17bda48c10ed8e9ec399698c 100644 --- a/src/Components/Server/src/Circuits/RemoteJSDataStream.cs +++ b/src/Components/Server/src/Circuits/RemoteJSDataStream.cs @@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private readonly RemoteJSRuntime _runtime; private readonly long _streamId; private readonly long _totalLength; + private readonly int _chunkSize; private readonly TimeSpan _jsInteropDefaultCallTimeout; private readonly CancellationToken _streamCancellationToken; private readonly Stream _pipeReaderStream; @@ -49,11 +50,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // transfer per chunk with a 1 kb message size. // Additionally, to maintain interactivity, we put an upper limit of 50 kb on the message size. var chunkSize = signalRMaximumIncomingBytes > 1024 ? - Math.Min(signalRMaximumIncomingBytes, 50*1024) - 512 : + (int)Math.Min(signalRMaximumIncomingBytes, 50*1024) - 512 : throw new ArgumentException($"SignalR MaximumIncomingBytes must be at least 1 kb."); var streamId = runtime.RemoteJSDataStreamNextInstanceId++; - var remoteJSDataStream = new RemoteJSDataStream(runtime, streamId, totalLength, jsInteropDefaultCallTimeout, cancellationToken); + var remoteJSDataStream = new RemoteJSDataStream(runtime, streamId, totalLength, chunkSize, jsInteropDefaultCallTimeout, cancellationToken); await runtime.InvokeVoidAsync("Blazor._internal.sendJSDataStream", jsStreamReference, streamId, chunkSize); return remoteJSDataStream; } @@ -62,12 +63,14 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits RemoteJSRuntime runtime, long streamId, long totalLength, + int chunkSize, TimeSpan jsInteropDefaultCallTimeout, CancellationToken cancellationToken) { _runtime = runtime; _streamId = streamId; _totalLength = totalLength; + _chunkSize = chunkSize; _jsInteropDefaultCallTimeout = jsInteropDefaultCallTimeout; _streamCancellationToken = cancellationToken; @@ -107,6 +110,11 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits throw new EndOfStreamException("The incoming data chunk cannot be empty."); } + if (chunk.Length > _chunkSize) + { + throw new EndOfStreamException("The incoming data chunk exceeded the permitted length."); + } + _bytesRead += chunk.Length; if (_bytesRead > _totalLength) diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index 4461e9812a96e0095bedd8dc636d8fb5f043ed0c..bb802199a7abca9a9e98d5ed2660179bc7329f1f 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -361,9 +361,6 @@ namespace Microsoft.AspNetCore.Components.Server private static readonly Action<ILogger, string, Exception> _invalidCircuitId = LoggerMessage.Define<string>(LogLevel.Debug, new EventId(8, "InvalidCircuitId"), "ConnectAsync received an invalid circuit id '{CircuitIdSecret}'"); - private static readonly Action<ILogger, Exception> _sendingDotNetStreamFailed = - LoggerMessage.Define(LogLevel.Debug, new EventId(9, "SendingDotNetStreamFailed"), "Sending the .NET stream data to JS failed"); - public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) => _receivedConfirmationForBatch(logger, batchId, null); public static void CircuitAlreadyInitialized(ILogger logger, CircuitId circuitId) => _circuitAlreadyInitialized(logger, circuitId, null); @@ -376,8 +373,6 @@ namespace Microsoft.AspNetCore.Components.Server public static void CircuitInitializationFailed(ILogger logger, Exception exception) => _circuitInitializationFailed(logger, exception); - public static void SendingDotNetStreamFailed(ILogger logger, Exception exception) => _sendingDotNetStreamFailed(logger, exception); - public static void CreatedCircuit(ILogger logger, CircuitId circuitId, string circuitSecret, string connectionId) { // Redact the secret unless tracing is on. diff --git a/src/Components/Server/test/Circuits/RemoteJSDataStreamTest.cs b/src/Components/Server/test/Circuits/RemoteJSDataStreamTest.cs index d0a433fbf18339725b6c3d6021ce2b4c37e79b37..ee440582fcf3d17bfdd9edfdff2bacd245e8e7af 100644 --- a/src/Components/Server/test/Circuits/RemoteJSDataStreamTest.cs +++ b/src/Components/Server/test/Circuits/RemoteJSDataStreamTest.cs @@ -140,6 +140,25 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits Assert.Equal("The incoming data chunk cannot be empty.", ex.Message); } + [Fact] + public async Task ReceiveData_WithLargerChunksThanPermitted() + { + // Arrange + var jsRuntime = new TestRemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions()), Mock.Of<ILogger<RemoteJSRuntime>>()); + var remoteJSDataStream = await CreateRemoteJSDataStreamAsync(jsRuntime); + var streamId = GetStreamId(remoteJSDataStream, jsRuntime); + var chunk = new byte[50_000]; // more than the 32k maximum chunk size + + // Act & Assert 1 + var ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await RemoteJSDataStream.ReceiveData(jsRuntime, streamId, chunkId: 0, chunk, error: null).DefaultTimeout()); + Assert.Equal("The incoming data chunk exceeded the permitted length.", ex.Message); + + // Act & Assert 2 + using var mem = new MemoryStream(); + ex = await Assert.ThrowsAsync<EndOfStreamException>(async () => await remoteJSDataStream.CopyToAsync(mem).DefaultTimeout()); + Assert.Equal("The incoming data chunk exceeded the permitted length.", ex.Message); + } + [Fact] public async Task ReceiveData_ProvidedWithMoreBytesThanRemaining() { diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index 79326a6ce35149be3b22d6342c63fa49177aa391..032b5baf59470610d8d680b1bbd40d40ff219ce4 100644 Binary files a/src/Components/Web.JS/dist/Release/blazor.server.js and b/src/Components/Web.JS/dist/Release/blazor.server.js differ diff --git a/src/Components/Web.JS/dist/Release/blazor.webview.js b/src/Components/Web.JS/dist/Release/blazor.webview.js index 46ae3c2d0617e9bdc81ac469c4a5e9d7dca44d47..27c5fd09222a11c740c652ca4b9513f49c79842d 100644 Binary files a/src/Components/Web.JS/dist/Release/blazor.webview.js and b/src/Components/Web.JS/dist/Release/blazor.webview.js differ