diff --git a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs index 81f5be0688a6b2..4019db358e0061 100644 --- a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs @@ -875,5 +875,22 @@ public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_ActualDestination Assert.Equal(4, written4); Assert.Equal(new byte[] { 1, 2, 3, 4 }, destination4); } + + [Fact] + public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_TrailingWhiteSpacesAreConsumed() + { + byte[] input = " 8J+N i f C f jYk="u8.ToArray(); + + // The actual decoded data is 8 bytes long. + // If we provide a destination buffer with 6 bytes, we can decode two blocks (6 bytes) and leave 2 bytes undecoded. + // But even though there are 2 bytes left undecoded, we should still consume as much input as possible, + // such that all trailing whitespace are also consumed. + + byte[] destination = new byte[6]; + Assert.Equal(OperationStatus.DestinationTooSmall, Base64.DecodeFromUtf8(input, destination, out int consumed, out int written)); + Assert.Equal((byte)'j', input[consumed]); // byte right after the spaces + Assert.Equal(destination.Length, written); + Assert.Equal(new byte[] { 240, 159, 141, 137, 240, 159 }, destination); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs index d63377faf110ec..9541f13860809c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Base64Helper/Base64DecoderHelper.cs @@ -304,11 +304,16 @@ static OperationStatus InvalidDataFallback(TBase64Decoder decoder, ReadOnlySpan< status = DecodeFrom(decoder, source, bytes, out localConsumed, out int localWritten, isFinalBlock, ignoreWhiteSpace: false); bytesConsumed += localConsumed; bytesWritten += localWritten; - if (status is not OperationStatus.InvalidData) + + if (status is OperationStatus.Done or OperationStatus.NeedMoreData) { break; } + // The DecodeFrom helper will return DestinationTooSmall if the destination is too small, + // regardless of whether it's actually too small once you skip whitespace characters. + // In that case we loop again and fall back to block-wise decoding if we can't make progress. + source = source.Slice(localConsumed); bytes = bytes.Slice(localWritten); } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.cs index c3ba20a5d76d22..fa785c8d29b5a7 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.cs @@ -8,6 +8,8 @@ using System.Collections.Generic; using Test.Cryptography; +using System.Buffers.Text; +using System.Buffers; namespace System.Tests { @@ -297,6 +299,8 @@ public static void TryFromBase64String(string encoded, byte[] expected) bool success = Convert.TryFromBase64String(encoded, actual, out int bytesWritten); Assert.False(success); Assert.Equal(0, bytesWritten); + + Assert.Equal(OperationStatus.InvalidData, Base64.DecodeFromUtf8(Encoding.UTF8.GetBytes(encoded), actual, out _, out _)); } else { @@ -307,6 +311,10 @@ public static void TryFromBase64String(string encoded, byte[] expected) Assert.True(success); Assert.Equal(expected, actual); Assert.Equal(expected.Length, bytesWritten); + + Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(Encoding.UTF8.GetBytes(encoded), actual, out int bytesConsumed, out bytesWritten)); + Assert.Equal(encoded.Length, bytesConsumed); + Assert.Equal(expected.Length, bytesWritten); } // Buffer too short @@ -316,6 +324,11 @@ public static void TryFromBase64String(string encoded, byte[] expected) bool success = Convert.TryFromBase64String(encoded, actual, out int bytesWritten); Assert.False(success); Assert.Equal(0, bytesWritten); + + Assert.Equal(OperationStatus.DestinationTooSmall, Base64.DecodeFromUtf8(Encoding.UTF8.GetBytes(encoded), actual, out int bytesConsumed, out bytesWritten)); + Assert.Equal(actual.Length / 3 * 3, bytesWritten); + Assert.InRange(bytesConsumed, Base64.GetMaxEncodedToUtf8Length(bytesWritten), encoded.Length - 1); + Assert.NotEqual(' ', encoded[bytesConsumed]); } // Buffer larger than needed @@ -327,6 +340,10 @@ public static void TryFromBase64String(string encoded, byte[] expected) Assert.Equal(99, actual[expected.Length]); Assert.Equal(expected, actual.Take(expected.Length)); Assert.Equal(expected.Length, bytesWritten); + + Assert.Equal(OperationStatus.Done, Base64.DecodeFromUtf8(Encoding.UTF8.GetBytes(encoded), actual, out int bytesConsumed, out bytesWritten)); + Assert.Equal(encoded.Length, bytesConsumed); + Assert.Equal(expected.Length, bytesWritten); } } }