//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//------------------------------------------------------------------------------
using System;
using System.Text;
using Microsoft.IdentityModel.Logging;
namespace Microsoft.IdentityModel.Tokens
{
///
/// Encodes and Decodes strings as Base64Url encoding.
///
public static class Base64UrlEncoder
{
private const char base64PadCharacter = '=';
#if NET45
private const string doubleBase64PadCharacter = "==";
#endif
private const char base64Character62 = '+';
private const char base64Character63 = '/';
private const char base64UrlCharacter62 = '-';
private const char base64UrlCharacter63 = '_';
///
/// Encoding table
///
internal static readonly char[] s_base64Table =
{
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9',
base64UrlCharacter62,
base64UrlCharacter63
};
///
/// The following functions perform base64url encoding which differs from regular base64 encoding as follows
/// * padding is skipped so the pad character '=' doesn't have to be percent encoded
/// * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_')
/// The changes make the encoding alphabet file and URL safe.
///
/// string to encode.
/// Base64Url encoding of the UTF8 bytes.
public static string Encode(string arg)
{
_ = arg ?? throw LogHelper.LogArgumentNullException(nameof(arg));
return Encode(Encoding.UTF8.GetBytes(arg));
}
///
/// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation which is encoded with base-64-url digits. Parameters specify
/// the subset as an offset in the input array, and the number of elements in the array to convert.
///
/// An array of 8-bit unsigned integers.
/// An offset in inArray.
/// The number of elements of inArray to convert.
/// The string representation in base 64 url encoding of length elements of inArray, starting at position offset.
/// 'inArray' is null.
/// offset or length is negative OR offset plus length is greater than the length of inArray.
public static string Encode(byte[] inArray, int offset, int length)
{
_ = inArray ?? throw LogHelper.LogArgumentNullException(nameof(inArray));
if (length == 0)
return string.Empty;
if (length < 0)
throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(LogHelper.FormatInvariant(LogMessages.IDX10106, LogHelper.MarkAsNonPII(nameof(length)), LogHelper.MarkAsNonPII(length))));
if (offset < 0 || inArray.Length < offset)
throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(LogHelper.FormatInvariant(LogMessages.IDX10106, LogHelper.MarkAsNonPII(nameof(offset)), LogHelper.MarkAsNonPII(offset))));
if (inArray.Length < offset + length)
throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(LogHelper.FormatInvariant(LogMessages.IDX10106, LogHelper.MarkAsNonPII(nameof(length)), LogHelper.MarkAsNonPII(length))));
int lengthmod3 = length % 3;
int limit = offset + (length - lengthmod3);
char[] output = new char[(length + 2) / 3 * 4];
char[] table = s_base64Table;
int i, j = 0;
// takes 3 bytes from inArray and insert 4 bytes into output
for (i = offset; i < limit; i += 3)
{
byte d0 = inArray[i];
byte d1 = inArray[i + 1];
byte d2 = inArray[i + 2];
output[j + 0] = table[d0 >> 2];
output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)];
output[j + 2] = table[((d1 & 0x0f) << 2) | (d2 >> 6)];
output[j + 3] = table[d2 & 0x3f];
j += 4;
}
//Where we left off before
i = limit;
switch (lengthmod3)
{
case 2:
{
byte d0 = inArray[i];
byte d1 = inArray[i + 1];
output[j + 0] = table[d0 >> 2];
output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)];
output[j + 2] = table[(d1 & 0x0f) << 2];
j += 3;
}
break;
case 1:
{
byte d0 = inArray[i];
output[j + 0] = table[d0 >> 2];
output[j + 1] = table[(d0 & 0x03) << 4];
j += 2;
}
break;
//default or case 0: no further operations are needed.
}
return new string(output, 0, j);
}
///
/// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation which is encoded with base-64-url digits.
///
/// An array of 8-bit unsigned integers.
/// The string representation in base 64 url encoding of length elements of inArray, starting at position offset.
/// 'inArray' is null.
/// offset or length is negative OR offset plus length is greater than the length of inArray.
public static string Encode(byte[] inArray)
{
_ = inArray ?? throw LogHelper.LogArgumentNullException(nameof(inArray));
return Encode(inArray, 0, inArray.Length);
}
internal static string EncodeString(string str)
{
_ = str ?? throw LogHelper.LogArgumentNullException(nameof(str));
return Encode(Encoding.UTF8.GetBytes(str));
}
///
/// Converts the specified string, which encodes binary data as base-64-url digits, to an equivalent 8-bit unsigned integer array.
/// base64Url encoded string.
/// UTF8 bytes.
public static byte[] DecodeBytes(string str)
{
_ = str ?? throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(str)));
#if NET45
// 62nd char of encoding
str = str.Replace(base64UrlCharacter62, base64Character62);
// 63rd char of encoding
str = str.Replace(base64UrlCharacter63, base64Character63);
// check for padding
switch (str.Length % 4)
{
case 0:
// No pad chars in this case
break;
case 2:
// Two pad chars
str += doubleBase64PadCharacter;
break;
case 3:
// One pad char
str += base64PadCharacter;
break;
default:
throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, str)));
}
return Convert.FromBase64String(str);
#else
return UnsafeDecode(str);
#endif
}
#if !NET45
private unsafe static byte[] UnsafeDecode(string str)
{
int mod = str.Length % 4;
if (mod == 1)
throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, str)));
bool needReplace = false;
int decodedLength = str.Length + (4 - mod) % 4;
for (int i = 0; i < str.Length; i++)
{
if (str[i] == base64UrlCharacter62 || str[i] == base64UrlCharacter63)
{
needReplace = true;
break;
}
}
if (needReplace)
{
string decodedString = new string(char.MinValue, decodedLength);
fixed (char* dest = decodedString)
{
int i = 0;
for (; i < str.Length; i++)
{
if (str[i] == base64UrlCharacter62)
dest[i] = base64Character62;
else if (str[i] == base64UrlCharacter63)
dest[i] = base64Character63;
else
dest[i] = str[i];
}
for (; i < decodedLength; i++)
dest[i] = base64PadCharacter;
}
return Convert.FromBase64String(decodedString);
}
else
{
if (decodedLength == str.Length)
{
return Convert.FromBase64String(str);
}
else
{
string decodedString = new string(char.MinValue, decodedLength);
fixed (char* src = str)
fixed (char* dest = decodedString)
{
Buffer.MemoryCopy(src, dest, str.Length * 2, str.Length * 2);
dest[str.Length] = base64PadCharacter;
if (str.Length + 2 == decodedLength)
dest[str.Length + 1] = base64PadCharacter;
}
return Convert.FromBase64String(decodedString);
}
}
}
#endif
///
/// Decodes the string from Base64UrlEncoded to UTF8.
///
/// string to decode.
/// UTF8 string.
public static string Decode(string arg)
{
return Encoding.UTF8.GetString(DecodeBytes(arg));
}
}
}