2020-08-06 12:14:45 +01:00
|
|
|
using System;
|
|
|
|
using System.Globalization;
|
2024-02-10 11:02:50 +00:00
|
|
|
#if NET8_0_OR_GREATER
|
2024-02-10 10:56:20 +00:00
|
|
|
using System.Text;
|
2024-02-10 11:02:50 +00:00
|
|
|
#endif
|
2020-08-06 12:14:45 +01:00
|
|
|
|
|
|
|
namespace SpotifyAPI.Web
|
|
|
|
{
|
|
|
|
internal class Base64Util
|
|
|
|
{
|
|
|
|
internal const string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length.";
|
2024-02-10 10:56:20 +00:00
|
|
|
|
|
|
|
#if NET8_0_OR_GREATER
|
|
|
|
internal static CompositeFormat WebEncoders_MalformedInput = CompositeFormat.Parse("Malformed input: {0} is an invalid input length.");
|
|
|
|
#else
|
2020-08-06 12:14:45 +01:00
|
|
|
internal const string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length.";
|
2024-02-10 10:56:20 +00:00
|
|
|
#endif
|
2020-08-06 12:14:45 +01:00
|
|
|
|
|
|
|
public static string UrlEncode(byte[] input)
|
|
|
|
{
|
2024-02-10 10:56:20 +00:00
|
|
|
#if NET8_0_OR_GREATER
|
|
|
|
ArgumentNullException.ThrowIfNull(input);
|
|
|
|
#else
|
2020-08-06 12:14:45 +01:00
|
|
|
if (input == null)
|
|
|
|
{
|
|
|
|
throw new ArgumentNullException(nameof(input));
|
|
|
|
}
|
2024-02-10 10:56:20 +00:00
|
|
|
#endif
|
|
|
|
|
2020-08-06 12:14:45 +01:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2020-08-06 12:35:39 +01:00
|
|
|
// 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] = '_';
|
|
|
|
}
|
2020-08-06 19:48:49 +01:00
|
|
|
else if (ch == '=')
|
|
|
|
{
|
|
|
|
return new string(buffer, startIndex: 0, length: i);
|
|
|
|
}
|
2020-08-06 12:35:39 +01:00
|
|
|
}
|
|
|
|
|
2020-08-06 12:14:45 +01:00
|
|
|
return new string(buffer, startIndex: 0, length: numBase64Chars);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static byte[] UrlDecode(string input)
|
|
|
|
{
|
2024-02-10 10:56:20 +00:00
|
|
|
#if NET8_0_OR_GREATER
|
|
|
|
ArgumentNullException.ThrowIfNull(input);
|
|
|
|
#else
|
2020-08-06 12:14:45 +01:00
|
|
|
if (input == null)
|
|
|
|
{
|
|
|
|
throw new ArgumentNullException(nameof(input));
|
|
|
|
}
|
2024-02-10 10:56:20 +00:00
|
|
|
#endif
|
|
|
|
var buffer = new char[GetArraySizeRequiredToDecode(input.Length)];
|
2020-08-06 12:14:45 +01:00
|
|
|
|
|
|
|
// Assumption: input is base64url encoded without padding and contains no whitespace.
|
|
|
|
|
2020-08-06 12:35:39 +01:00
|
|
|
var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(input.Length);
|
|
|
|
var arraySizeRequired = checked(input.Length + paddingCharsToAdd);
|
2020-08-06 12:14:45 +01:00
|
|
|
|
|
|
|
// Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'.
|
|
|
|
var i = 0;
|
2020-08-06 12:35:39 +01:00
|
|
|
for (var j = 0; i < input.Length; i++, j++)
|
2020-08-06 12:14:45 +01:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2024-02-10 10:56:20 +00:00
|
|
|
#if NET8_0_OR_GREATER
|
|
|
|
ArgumentOutOfRangeException.ThrowIfNegative(count);
|
|
|
|
#else
|
2020-08-06 12:14:45 +01:00
|
|
|
if (count < 0)
|
|
|
|
{
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
|
|
}
|
2024-02-10 10:56:20 +00:00
|
|
|
#endif
|
2020-08-06 12:14:45 +01:00
|
|
|
|
|
|
|
if (count == 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count);
|
|
|
|
|
|
|
|
return checked(count + numPaddingCharsToAdd);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength)
|
|
|
|
{
|
2020-12-26 17:28:44 +00:00
|
|
|
return (inputLength % 4) switch
|
2020-08-06 12:14:45 +01:00
|
|
|
{
|
2020-12-26 17:28:44 +00:00
|
|
|
0 => 0,
|
|
|
|
2 => 2,
|
|
|
|
3 => 1,
|
|
|
|
_ => throw new FormatException(
|
|
|
|
string.Format(
|
|
|
|
CultureInfo.CurrentCulture,
|
|
|
|
WebEncoders_MalformedInput,
|
|
|
|
inputLength)),
|
|
|
|
};
|
2020-08-06 12:14:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|