File: src\libraries\Common\src\System\Text\UrlBase64Encoding.cs
Web Access
Project: src\src\libraries\System.Net.Security\src\System.Net.Security.csproj (System.Net.Security)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Diagnostics;
 
namespace System.Text
{
    /// <summary>
    /// This class provides URL-encoded-Base64, which is distinct from the base64url encoding.
    /// </summary>
    internal static class UrlBase64Encoding
    {
        internal static ArraySegment<char> RentEncode(ReadOnlySpan<byte> input)
        {
            // Every 3 bytes turns into 4 chars for the Base64 operation
            int base64Len = ((input.Length + 2) / 3) * 4;
            char[] base64 = ArrayPool<char>.Shared.Rent(base64Len);
 
            if (!Convert.TryToBase64Chars(input, base64, out int charsWritten))
            {
                Debug.Fail($"Convert.TryToBase64 failed with {input.Length} bytes to a {base64.Length} buffer");
                throw new UnreachableException();
            }
 
            Debug.Assert(charsWritten == base64Len);
 
            // In the degenerate case every char will turn into 3 chars.
            int urlEncodedLen = charsWritten * 3;
            char[] urlEncoded = ArrayPool<char>.Shared.Rent(urlEncodedLen);
 
            ReadOnlySpan<char> source = base64.AsSpan(0, base64Len);
            Span<char> dest = urlEncoded;
            int written = 0;
 
            while (!source.IsEmpty)
            {
                int pos = source.IndexOfAny('+', '/', '=');
 
                if (pos < 0)
                {
                    source.CopyTo(dest);
                    written += source.Length;
                    break;
                }
 
                source.Slice(0, pos).CopyTo(dest);
                source = source.Slice(pos);
                dest = dest.Slice(pos);
                written += pos;
 
                dest[0] = '%';
 
                switch (source[0])
                {
                    case '+':
                        dest[1] = '2';
                        dest[2] = 'B';
                        break;
                    case '/':
                        dest[1] = '2';
                        dest[2] = 'F';
                        break;
                    default:
                        Debug.Assert(source[0] == '=');
                        dest[1] = '3';
                        dest[2] = 'D';
                        break;
                }
 
                source = source.Slice(1);
                dest = dest.Slice(3);
                written += 3;
            }
 
            ArrayPool<char>.Shared.Return(base64);
            return new ArraySegment<char>(urlEncoded, 0, written);
        }
    }
}