Spotify.NET/SpotifyAPI.Web/Util/Base64Util.cs
2020-12-26 18:28:44 +01:00

133 lines
3.3 KiB
C#

using System;
using System.Globalization;
namespace SpotifyAPI.Web
{
internal class Base64Util
{
internal const string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length.";
internal const string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length.";
public static string UrlEncode(byte[] input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
// Special-case empty input
if (input.Length == 0)
{
return string.Empty;
}
var buffer = new char[GetArraySizeRequiredToEncode(input.Length)];
var numBase64Chars = Convert.ToBase64CharArray(input, 0, input.Length, buffer, 0);
// Fix up '+' -> '-' and '/' -> '_'. Drop padding characters.
for (var i = 0; i < numBase64Chars; i++)
{
var ch = buffer[i];
if (ch == '+')
{
buffer[i] = '-';
}
else if (ch == '/')
{
buffer[i] = '_';
}
else if (ch == '=')
{
return new string(buffer, startIndex: 0, length: i);
}
}
return new string(buffer, startIndex: 0, length: numBase64Chars);
}
public static byte[] UrlDecode(string input)
{
var buffer = new char[GetArraySizeRequiredToDecode(input.Length)];
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
// Assumption: input is base64url encoded without padding and contains no whitespace.
var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(input.Length);
var arraySizeRequired = checked(input.Length + paddingCharsToAdd);
// Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'.
var i = 0;
for (var j = 0; i < input.Length; i++, j++)
{
var ch = input[j];
if (ch == '-')
{
buffer[i] = '+';
}
else if (ch == '_')
{
buffer[i] = '/';
}
else
{
buffer[i] = ch;
}
}
// Add the padding characters back.
for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--)
{
buffer[i] = '=';
}
// Decode.
// If the caller provided invalid base64 chars, they'll be caught here.
return Convert.FromBase64CharArray(buffer, 0, arraySizeRequired);
}
private static int GetArraySizeRequiredToEncode(int count)
{
var numWholeOrPartialInputBlocks = checked(count + 2) / 3;
return checked(numWholeOrPartialInputBlocks * 4);
}
private static int GetArraySizeRequiredToDecode(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (count == 0)
{
return 0;
}
var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
return checked(count + numPaddingCharsToAdd);
}
private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength)
{
return (inputLength % 4) switch
{
0 => 0,
2 => 2,
3 => 1,
_ => throw new FormatException(
string.Format(
CultureInfo.CurrentCulture,
WebEncoders_MalformedInput,
inputLength)),
};
}
}
}