diff --git a/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64EncodingAndDecoding.csproj b/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64EncodingAndDecoding.csproj index 74abf5c976..dfb40caafc 100644 --- a/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64EncodingAndDecoding.csproj +++ b/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64EncodingAndDecoding.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net10.0 enable enable diff --git a/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64Operations.cs b/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64Operations.cs index d08151204e..18d29d14f4 100644 --- a/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64Operations.cs +++ b/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Base64Operations.cs @@ -1,13 +1,15 @@ -using System.Text; +using System.Buffers.Text; +using System.Security.Cryptography; +using System.Text; namespace Base64EncodingAndDecoding; public class Base64Operations { - public string Base64Encoding(string text, bool AddLineBreaks = false) + public string Base64Encoding(string text, bool addLineBreaks = false) { var textBytes = Encoding.UTF8.GetBytes(text); - return !AddLineBreaks ? Convert.ToBase64String(textBytes) : + return !addLineBreaks ? Convert.ToBase64String(textBytes) : Convert.ToBase64String(textBytes, Base64FormattingOptions.InsertLineBreaks); } @@ -22,5 +24,56 @@ public string Base64Decoding(string base64EncodedText) var base64EncodedBytes = Convert.FromBase64String(base64EncodedText); return Encoding.UTF8.GetString(base64EncodedBytes); } -} + public string EncodeFile(string path) + { + return Convert.ToBase64String(File.ReadAllBytes(path)); + } + + public void DecodeToFile(string base64EncodedText, string path) + { + File.WriteAllBytes(path, Convert.FromBase64String(base64EncodedText)); + } + + public void EncodeStream(Stream input, Stream output) + { + using var transform = new ToBase64Transform(); + using var cryptoStream = new CryptoStream(output, transform, CryptoStreamMode.Write, leaveOpen: true); + + input.CopyTo(cryptoStream); + } + + public void DecodeStream(Stream input, Stream output) + { + using var transform = new FromBase64Transform(); + using var cryptoStream = new CryptoStream(output, transform, CryptoStreamMode.Write, leaveOpen: true); + + input.CopyTo(cryptoStream); + } + + public bool TryDecode(string base64EncodedText, out byte[] bytes) + { + var buffer = new byte[base64EncodedText.Length]; + + if (Convert.TryFromBase64String(base64EncodedText, buffer, out var bytesWritten)) + { + bytes = buffer[..bytesWritten]; + + return true; + } + + bytes = []; + + return false; + } + + public string Base64UrlEncoding(string text) + { + return Base64Url.EncodeToString(Encoding.UTF8.GetBytes(text)); + } + + public string Base64UrlDecoding(string base64UrlEncodedText) + { + return Encoding.UTF8.GetString(Base64Url.DecodeFromChars(base64UrlEncodedText)); + } +} diff --git a/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Program.cs b/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Program.cs index 7bcbe97f87..097b4fe60e 100644 --- a/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Program.cs +++ b/csharp-basic-topics/Base64EncodingAndDecoding/Base64EncodingAndDecoding/Program.cs @@ -1,26 +1,27 @@ - + using Base64EncodingAndDecoding; Run(); void Run() { - var _base64Operations = new Base64Operations(); - var sampletext = "To what length can the human lifespan be extended?"; + var base64Operations = new Base64Operations(); + var sampletext = "To what length can the human lifespan be extended?"; Console.WriteLine($"{sampletext} \n"); - var base64String = _base64Operations.Base64Encoding(sampletext); - Console.WriteLine(string.Format("Base64 string is '{0}' ", base64String)); - Console.WriteLine(string.Format("Decoded value is '{0}' \n", _base64Operations.Base64Decoding(base64String))); + var base64String = base64Operations.Base64Encoding(sampletext); + Console.WriteLine($"Base64 string is '{base64String}' "); + Console.WriteLine($"Decoded value is '{base64Operations.Base64Decoding(base64String)}' \n"); - base64String = _base64Operations.Base64Encoding(sampletext, 3, 4); - Console.WriteLine(string.Format("Base64 string is '{0}' ", base64String)); - Console.WriteLine(string.Format("Decoded value is '{0}' \n", _base64Operations.Base64Decoding(base64String))); + base64String = base64Operations.Base64Encoding(sampletext, 3, 4); + Console.WriteLine($"Base64 string is '{base64String}' "); + Console.WriteLine($"Decoded value is '{base64Operations.Base64Decoding(base64String)}' \n"); Console.WriteLine(); sampletext = "The great crocodile of Queensland has been known to attain a length of 30 feet;"; - base64String = _base64Operations.Base64Encoding(sampletext, true); - Console.WriteLine(string.Format("Base64 string is '{0}' ", base64String)); - Console.WriteLine(string.Format("Decoded value is '{0}' \n", _base64Operations.Base64Decoding(base64String))); - Console.ReadLine(); -} \ No newline at end of file + base64String = base64Operations.Base64Encoding(sampletext, true); + Console.WriteLine($"Base64 string is '{base64String}' "); + Console.WriteLine($"Decoded value is '{base64Operations.Base64Decoding(base64String)}' \n"); + + Console.WriteLine($"Base64Url string is '{base64Operations.Base64UrlEncoding(sampletext)}' "); +} diff --git a/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.cs b/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.cs index 18a74a1ee6..0e9173e46c 100644 --- a/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.cs +++ b/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.cs @@ -1,5 +1,8 @@ using Base64EncodingAndDecoding; using System; +using System.Buffers.Text; +using System.IO; +using System.Text; using Xunit; namespace Tests; @@ -7,7 +10,7 @@ namespace Tests; public class Tests { private Base64Operations _base64Operations = new Base64Operations(); - private string _shortString = "To what length can the human lifespan be extended?"; + private string _shortString = "To what length can the human lifespan be extended?"; private string _longString = "The great crocodile of Queensland has been known to attain a length of 30 ft.; there is a smaller one about 6 ft."; [Fact] @@ -16,7 +19,7 @@ public void GivenAText_WhenEncoding_ThenReturnStringOutput() var base64String = _base64Operations.Base64Encoding(_shortString); var decodedString = _base64Operations.Base64Decoding(base64String); - Assert.Equal(decodedString, _shortString); + Assert.Equal(_shortString, decodedString); } [Fact] @@ -25,7 +28,7 @@ public void GivenAText_WhenEncodingWithOffset_ThenReturnStringOutput() var base64String = _base64Operations.Base64Encoding(_shortString, 2, 9); var decodedString = _base64Operations.Base64Decoding(base64String); - Assert.Equal(decodedString, _shortString.Substring(2, 9)); + Assert.Equal(_shortString.Substring(2, 9), decodedString); } [Fact] @@ -33,7 +36,7 @@ public void GivenATextLessThan76Length_WhenEncodingWithLineBreaks_ThenReturnStri { var base64String = _base64Operations.Base64Encoding("Test", true); - Assert.Equal(base64String, _base64Operations.Base64Encoding("Test")); + Assert.Equal(_base64Operations.Base64Encoding("Test"), base64String); Assert.DoesNotContain(Environment.NewLine, base64String); } @@ -42,7 +45,108 @@ public void GivenATextWithMoreThan76Length_WhenEncodingWithLineBreaks_ThenReturn { var base64String = _base64Operations.Base64Encoding(_longString, true); - Assert.NotEqual(base64String, _base64Operations.Base64Encoding(_longString)); + Assert.NotEqual(_base64Operations.Base64Encoding(_longString), base64String); Assert.Contains(Environment.NewLine, base64String); } -} \ No newline at end of file + + [Fact] + public void GivenTextEncodedAsUtf8_WhenDecodedAsUtf16_ThenReturnsMangledTextWithoutThrowing() + { + var utf8Bytes = Encoding.UTF8.GetBytes("Hello world!"); + + var mangled = Encoding.Unicode.GetString(utf8Bytes); + + Assert.NotEqual("Hello world!", mangled); + Assert.Equal(6, mangled.Length); + Assert.DoesNotContain('\uFFFD', mangled); + } + + [Fact] + public void GivenAFile_WhenEncodingAndDecodingBack_ThenFileContentSurvives() + { + var sourcePath = Path.GetTempFileName(); + var targetPath = Path.GetTempFileName(); + + try + { + var content = new byte[] { 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E, 0x37, 0x0A, 0xFF, 0x00, 0x80 }; + File.WriteAllBytes(sourcePath, content); + + var base64String = _base64Operations.EncodeFile(sourcePath); + _base64Operations.DecodeToFile(base64String, targetPath); + + Assert.Equal(Convert.ToBase64String(content), base64String); + Assert.Equal(content, File.ReadAllBytes(targetPath)); + } + finally + { + File.Delete(sourcePath); + File.Delete(targetPath); + } + } + + [Fact] + public void GivenAStream_WhenEncoding_ThenOutputMatchesConvertToBase64String() + { + var content = Encoding.UTF8.GetBytes(_longString); + + using var input = new MemoryStream(content); + using var output = new MemoryStream(); + + _base64Operations.EncodeStream(input, output); + + Assert.Equal(Convert.ToBase64String(content), Encoding.ASCII.GetString(output.ToArray())); + } + + [Fact] + public void GivenABase64Stream_WhenDecoding_ThenOriginalBytesComeBack() + { + var content = Encoding.UTF8.GetBytes(_longString); + var base64Bytes = Encoding.ASCII.GetBytes(Convert.ToBase64String(content)); + + using var input = new MemoryStream(base64Bytes); + using var output = new MemoryStream(); + + _base64Operations.DecodeStream(input, output); + + Assert.Equal(content, output.ToArray()); + } + + [Fact] + public void GivenValidBase64_WhenTryDecoding_ThenReturnsTrueAndTheBytes() + { + var base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(_shortString)); + + var succeeded = _base64Operations.TryDecode(base64String, out var bytes); + + Assert.True(succeeded); + Assert.Equal(_shortString, Encoding.UTF8.GetString(bytes)); + } + + [Fact] + public void GivenInvalidBase64_WhenTryDecoding_ThenReturnsFalseInsteadOfThrowing() + { + var succeeded = _base64Operations.TryDecode("SGVsbG8gd29ybGQ!", out var bytes); + + Assert.False(succeeded); + Assert.Empty(bytes); + } + + [Fact] + public void GivenText_WhenBase64UrlEncoding_ThenRoundTripsAndOmitsPadding() + { + var base64UrlString = _base64Operations.Base64UrlEncoding(_shortString); + + Assert.DoesNotContain("=", base64UrlString); + Assert.Equal(_shortString, _base64Operations.Base64UrlDecoding(base64UrlString)); + } + + [Fact] + public void GivenBytesThatUsePlusAndSlash_WhenBase64UrlEncoding_ThenTheAlphabetIsSwapped() + { + var bytes = new byte[] { 0xFB, 0xFF, 0xFE }; + + Assert.Equal("+//+", Convert.ToBase64String(bytes)); + Assert.Equal("-__-", Base64Url.EncodeToString(bytes)); + } +} diff --git a/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.csproj b/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.csproj index 4bd37c5279..74dc81693d 100644 --- a/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.csproj +++ b/csharp-basic-topics/Base64EncodingAndDecoding/Tests/Tests/Tests.csproj @@ -1,20 +1,20 @@ - net6.0 + net10.0 enable false - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all