diff --git a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs index 2a24c23e82d7ea..81f5be0688a6b2 100644 --- a/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64/Base64DecoderUnitTests.cs @@ -770,5 +770,110 @@ public void BasicDecodingWithExtraWhitespaceShouldBeCountedInConsumedBytes(strin Assert.Equal(expectedWritten, decodedByteCount); Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes)); } + + [Fact] + public void DecodingWithWhiteSpaceIntoSmallDestination() + { + // Input " zAww " (8 bytes) contains "zAww" which decodes to 3 bytes. + // 'z' = 51, 'A' = 0, 'w' = 48, 'w' = 48 -> bits: 110011 000000 110000 110000 -> 0xCC 0x0C 0x30 + // With destination of 3 bytes, this should succeed, not report "Destination too short". + byte[] input = Encoding.UTF8.GetBytes(" zAww "); + + byte[] destination5 = new byte[5]; + OperationStatus status5 = Base64.DecodeFromUtf8(input, destination5, out int consumed5, out int written5); + Assert.Equal(OperationStatus.Done, status5); + Assert.Equal(input.Length, consumed5); + Assert.Equal(3, written5); + + byte[] destination3 = new byte[3]; + OperationStatus status3 = Base64.DecodeFromUtf8(input, destination3, out int consumed3, out int written3); + Assert.Equal(OperationStatus.Done, status3); + Assert.Equal(input.Length, consumed3); + Assert.Equal(3, written3); + } + + [Fact] + public void DecodingWithOnlyWhiteSpaceIntoSmallDestination() + { + // Input " " (8 spaces) decodes to 0 bytes. + // With destination of 1 byte, this should succeed, not report "Destination too short". + byte[] allSpaces = Encoding.UTF8.GetBytes(new string(' ', 8)); + + byte[] destination = new byte[1]; + OperationStatus status = Base64.DecodeFromUtf8(allSpaces, destination, out int consumed, out int written); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(allSpaces.Length, consumed); + Assert.Equal(0, written); + + // Also test with empty destination buffer + byte[] emptyDestination = Array.Empty(); + OperationStatus statusEmpty = Base64.DecodeFromUtf8(allSpaces, emptyDestination, out int consumedEmpty, out int writtenEmpty); + Assert.Equal(OperationStatus.Done, statusEmpty); + Assert.Equal(allSpaces.Length, consumedEmpty); + Assert.Equal(0, writtenEmpty); + } + + [Fact] + public void DecodingWithWhiteSpaceIntoSmallDestination_ActualDestinationTooSmall() + { + // Input " AQID" (leading whitespace only) decodes to 3 bytes. + // With destination of 1 byte, this should correctly report "Destination too short". + // Note: Base64 requires input length to be multiple of 4, so we use "AQIDBA==" which decodes to 4 bytes. + byte[] input = Encoding.UTF8.GetBytes(" AQIDBA=="); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64.DecodeFromUtf8(input, destination1, out _, out _); + Assert.Equal(OperationStatus.DestinationTooSmall, status1); + + // With destination of 4 bytes, this should succeed. + byte[] destination4 = new byte[4]; + OperationStatus status4 = Base64.DecodeFromUtf8(input, destination4, out int consumed4, out int written4); + Assert.Equal(OperationStatus.Done, status4); + Assert.Equal(input.Length, consumed4); + Assert.Equal(4, written4); + Assert.Equal(new byte[] { 1, 2, 3, 4 }, destination4); + } + + [Fact] + public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination() + { + // Tests DecodeWithWhiteSpaceBlockwiseWrapper path - whitespace embedded in Base64 data. + // Input "z A w w" has whitespace in the middle. "zAww" decodes to 3 bytes. + byte[] input = Encoding.UTF8.GetBytes("z A w w"); + + byte[] destination3 = new byte[3]; + OperationStatus status3 = Base64.DecodeFromUtf8(input, destination3, out int consumed3, out int written3); + Assert.Equal(OperationStatus.Done, status3); + Assert.Equal(input.Length, consumed3); + Assert.Equal(3, written3); + + // Also test with larger embedded whitespace + byte[] input2 = Encoding.UTF8.GetBytes("z A w w"); + byte[] destination2 = new byte[3]; + OperationStatus status2 = Base64.DecodeFromUtf8(input2, destination2, out int consumed2, out int written2); + Assert.Equal(OperationStatus.Done, status2); + Assert.Equal(input2.Length, consumed2); + Assert.Equal(3, written2); + } + + [Fact] + public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_ActualDestinationTooSmall() + { + // Tests DecodeWithWhiteSpaceBlockwiseWrapper path with actual destination too small. + // Input "A Q I D B A = =" (embedded whitespace) decodes to 4 bytes. + byte[] input = Encoding.UTF8.GetBytes("A Q I D B A = ="); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64.DecodeFromUtf8(input, destination1, out _, out _); + Assert.Equal(OperationStatus.DestinationTooSmall, status1); + + // With destination of 4 bytes, this should succeed. + byte[] destination4 = new byte[4]; + OperationStatus status4 = Base64.DecodeFromUtf8(input, destination4, out int consumed4, out int written4); + Assert.Equal(OperationStatus.Done, status4); + Assert.Equal(input.Length, consumed4); + Assert.Equal(4, written4); + Assert.Equal(new byte[] { 1, 2, 3, 4 }, destination4); + } } } diff --git a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs index c43af5769df061..207af91054e998 100644 --- a/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs +++ b/src/libraries/System.Memory/tests/Base64Url/Base64UrlDecoderUnitTests.cs @@ -895,5 +895,136 @@ public void BasicDecodingWithExtraWhitespaceShouldBeCountedInConsumedBytes(strin Assert.Equal(expectedWritten, decodedByteCount); Assert.True(Base64TestHelper.VerifyDecodingCorrectness(expectedConsumed, expectedWritten, source, decodedBytes)); } + + [Fact] + public void DecodingWithWhiteSpaceIntoSmallDestination_DecodeFromUtf8() + { + // Input " zA== " (8 bytes) contains "zA==" which decodes to 1 byte (0xCC). + // 'z' = 51, 'A' = 0 -> bits: 110011 000000 -> 0b11001100 = 0xCC + // With destination of 1 byte, this should succeed, not report "Destination too short". + byte[] input = Encoding.UTF8.GetBytes(" zA== "); + + byte[] destination5 = new byte[5]; + OperationStatus status5 = Base64Url.DecodeFromUtf8(input, destination5, out int consumed5, out int written5); + Assert.Equal(OperationStatus.Done, status5); + Assert.Equal(input.Length, consumed5); + Assert.Equal(1, written5); + Assert.Equal(0xCC, destination5[0]); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64Url.DecodeFromUtf8(input, destination1, out int consumed1, out int written1); + Assert.Equal(OperationStatus.Done, status1); + Assert.Equal(input.Length, consumed1); + Assert.Equal(1, written1); + Assert.Equal(0xCC, destination1[0]); + } + + [Fact] + public void DecodingWithWhiteSpaceIntoSmallDestination_DecodeFromChars() + { + // Input " " (8 spaces) decodes to 0 bytes. + // With destination of 1 byte, this should succeed, not report "Destination too short". + char[] allSpaces = new string(' ', 8).ToCharArray(); + + byte[] destination = new byte[1]; + OperationStatus status = Base64Url.DecodeFromChars(allSpaces, destination, out int consumed, out int written); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(allSpaces.Length, consumed); + Assert.Equal(0, written); + + // Also test with empty destination buffer + byte[] emptyDestination = Array.Empty(); + OperationStatus statusEmpty = Base64Url.DecodeFromChars(allSpaces, emptyDestination, out int consumedEmpty, out int writtenEmpty); + Assert.Equal(OperationStatus.Done, statusEmpty); + Assert.Equal(allSpaces.Length, consumedEmpty); + Assert.Equal(0, writtenEmpty); + } + + [Fact] + public void DecodingWithWhiteSpaceIntoSmallDestination_ActualDestinationTooSmall() + { + // Input " AQID" (leading whitespace only) decodes to 3 bytes. + // With destination of 1 byte, this should correctly report "Destination too short". + byte[] input = Encoding.UTF8.GetBytes(" AQID"); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64Url.DecodeFromUtf8(input, destination1, out _, out _); + Assert.Equal(OperationStatus.DestinationTooSmall, status1); + + // With destination of 3 bytes, this should succeed. + byte[] destination3 = new byte[3]; + OperationStatus status3 = Base64Url.DecodeFromUtf8(input, destination3, out int consumed3, out int written3); + Assert.Equal(OperationStatus.Done, status3); + Assert.Equal(input.Length, consumed3); + Assert.Equal(3, written3); + Assert.Equal(new byte[] { 1, 2, 3 }, destination3); + } + + [Fact] + public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_DecodeFromUtf8() + { + // Tests DecodeWithWhiteSpaceBlockwiseWrapper path - whitespace embedded in Base64Url data. + // Input "z A==" has whitespace in the middle. Decodes to 1 byte (0xCC). + byte[] input = Encoding.UTF8.GetBytes("z A=="); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64Url.DecodeFromUtf8(input, destination1, out int consumed1, out int written1); + Assert.Equal(OperationStatus.Done, status1); + Assert.Equal(input.Length, consumed1); + Assert.Equal(1, written1); + Assert.Equal(0xCC, destination1[0]); + + // Also test with larger embedded whitespace + byte[] input2 = Encoding.UTF8.GetBytes("z A = ="); + byte[] destination2 = new byte[1]; + OperationStatus status2 = Base64Url.DecodeFromUtf8(input2, destination2, out int consumed2, out int written2); + Assert.Equal(OperationStatus.Done, status2); + Assert.Equal(input2.Length, consumed2); + Assert.Equal(1, written2); + Assert.Equal(0xCC, destination2[0]); + } + + [Fact] + public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_DecodeFromChars() + { + // Tests DecodeWithWhiteSpaceBlockwiseWrapper path for chars - whitespace embedded in Base64Url data. + char[] input = "z A==".ToCharArray(); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64Url.DecodeFromChars(input, destination1, out int consumed1, out int written1); + Assert.Equal(OperationStatus.Done, status1); + Assert.Equal(input.Length, consumed1); + Assert.Equal(1, written1); + Assert.Equal(0xCC, destination1[0]); + + // Also test with larger embedded whitespace + char[] input2 = "z A = =".ToCharArray(); + byte[] destination2 = new byte[1]; + OperationStatus status2 = Base64Url.DecodeFromChars(input2, destination2, out int consumed2, out int written2); + Assert.Equal(OperationStatus.Done, status2); + Assert.Equal(input2.Length, consumed2); + Assert.Equal(1, written2); + Assert.Equal(0xCC, destination2[0]); + } + + [Fact] + public void DecodingWithEmbeddedWhiteSpaceIntoSmallDestination_ActualDestinationTooSmall() + { + // Tests DecodeWithWhiteSpaceBlockwiseWrapper path with actual destination too small. + // Input "A Q I D" (embedded whitespace) decodes to 3 bytes. + byte[] input = Encoding.UTF8.GetBytes("A Q I D"); + + byte[] destination1 = new byte[1]; + OperationStatus status1 = Base64Url.DecodeFromUtf8(input, destination1, out _, out _); + Assert.Equal(OperationStatus.DestinationTooSmall, status1); + + // With destination of 3 bytes, this should succeed. + byte[] destination3 = new byte[3]; + OperationStatus status3 = Base64Url.DecodeFromUtf8(input, destination3, out int consumed3, out int written3); + Assert.Equal(OperationStatus.Done, status3); + Assert.Equal(input.Length, consumed3); + Assert.Equal(3, written3); + Assert.Equal(new byte[] { 1, 2, 3 }, destination3); + } } } 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 2f60ae68400692..d63377faf110ec 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 @@ -244,6 +244,12 @@ internal static unsafe OperationStatus DecodeFrom(TBase64Deco goto InvalidDataExit; // if input is not a multiple of 4, and there is no more data, return invalid data instead } + if (ignoreWhiteSpace) + { + // Fall through to InvalidDataFallback which strips whitespace and re-evaluates destination size requirement + goto InvalidDataExit; + } + bytesConsumed = (int)(src - srcBytes); bytesWritten = (int)(dest - destBytes); return OperationStatus.DestinationTooSmall;