* add a util method for padding base64 strings if they are not of an expected length * return the original string if it already contains padding, do not throw * add tests for padding method, and for token that needs paddingdispose-tweaks
@@ -17,6 +17,47 @@ namespace Discord | |||||
/// </remarks> | /// </remarks> | ||||
internal const int MinBotTokenLength = 58; | internal const int MinBotTokenLength = 58; | ||||
internal const char Base64Padding = '='; | |||||
/// <summary> | |||||
/// Pads a base64-encoded string with 0, 1, or 2 '=' characters, | |||||
/// if the string is not a valid multiple of 4. | |||||
/// Does not ensure that the provided string contains only valid base64 characters. | |||||
/// Strings that already contain padding will not have any more padding applied. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// A string that would require 3 padding characters is considered to be already corrupt. | |||||
/// Some older bot tokens may require padding, as the format provided by Discord | |||||
/// does not include this padding in the token. | |||||
/// </remarks> | |||||
/// <param name="encodedBase64">The base64 encoded string to pad with characters.</param> | |||||
/// <returns>A string containing the base64 padding.</returns> | |||||
/// <exception cref="FormatException"> | |||||
/// Thrown if <paramref name="encodedBase64"/> would require an invalid number of padding characters. | |||||
/// </exception> | |||||
/// <exception cref="ArgumentNullException"> | |||||
/// Thrown if <paramref name="encodedBase64"/> is null, empty, or whitespace. | |||||
/// </exception> | |||||
internal static string PadBase64String(string encodedBase64) | |||||
{ | |||||
if (string.IsNullOrWhiteSpace(encodedBase64)) | |||||
throw new ArgumentNullException(paramName: encodedBase64, | |||||
message: "The supplied base64-encoded string was null or whitespace."); | |||||
// do not pad if already contains padding characters | |||||
if (encodedBase64.IndexOf(Base64Padding) != -1) | |||||
return encodedBase64; | |||||
// based from https://stackoverflow.com/a/1228744 | |||||
var padding = (4 - (encodedBase64.Length % 4)) % 4; | |||||
if (padding == 3) | |||||
// can never have 3 characters of padding | |||||
throw new FormatException("The provided base64 string is corrupt, as it requires an invalid amount of padding."); | |||||
else if (padding == 0) | |||||
return encodedBase64; | |||||
return encodedBase64.PadRight(encodedBase64.Length + padding, Base64Padding); | |||||
} | |||||
/// <summary> | /// <summary> | ||||
/// Decodes a base 64 encoded string into a ulong value. | /// Decodes a base 64 encoded string into a ulong value. | ||||
/// </summary> | /// </summary> | ||||
@@ -29,6 +70,8 @@ namespace Discord | |||||
try | try | ||||
{ | { | ||||
// re-add base64 padding if missing | |||||
encoded = PadBase64String(encoded); | |||||
// decode the base64 string | // decode the base64 string | ||||
var bytes = Convert.FromBase64String(encoded); | var bytes = Convert.FromBase64String(encoded); | ||||
var idStr = Encoding.UTF8.GetString(bytes); | var idStr = Encoding.UTF8.GetString(bytes); | ||||
@@ -46,7 +89,7 @@ namespace Discord | |||||
} | } | ||||
catch (ArgumentException) | catch (ArgumentException) | ||||
{ | { | ||||
// ignore exception, can be thrown by BitConverter | |||||
// ignore exception, can be thrown by BitConverter, or by PadBase64String | |||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
@@ -77,6 +77,8 @@ namespace Discord | |||||
// 59 char token | // 59 char token | ||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | ||||
[InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] | [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] | ||||
// simulated token with a very old user id | |||||
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY=.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")] | |||||
public void TestBotTokenDoesNotThrowExceptions(string token) | public void TestBotTokenDoesNotThrowExceptions(string token) | ||||
{ | { | ||||
// This example token is pulled from the Discord Docs | // This example token is pulled from the Discord Docs | ||||
@@ -151,6 +153,10 @@ namespace Discord | |||||
// cannot pass a ulong? as a param in InlineData, so have to have a separate param | // cannot pass a ulong? as a param in InlineData, so have to have a separate param | ||||
// indicating if a value is null | // indicating if a value is null | ||||
[InlineData("NDI4NDc3OTQ0MDA5MTk1NTIw", false, 428477944009195520)] | [InlineData("NDI4NDc3OTQ0MDA5MTk1NTIw", false, 428477944009195520)] | ||||
// user id that has base 64 '=' padding | |||||
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY=", false, 82364801350107136)] | |||||
// user id that does not have '=' padding, and needs it | |||||
[InlineData("ODIzNjQ4MDEzNTAxMDcxMzY", false, 82364801350107136)] | |||||
// should return null w/o throwing other exceptions | // should return null w/o throwing other exceptions | ||||
[InlineData("", true, 0)] | [InlineData("", true, 0)] | ||||
[InlineData(" ", true, 0)] | [InlineData(" ", true, 0)] | ||||
@@ -164,5 +170,37 @@ namespace Discord | |||||
else | else | ||||
Assert.Equal(expectedUserId, result); | Assert.Equal(expectedUserId, result); | ||||
} | } | ||||
[Theory] | |||||
[InlineData("QQ", "QQ==")] // "A" encoded | |||||
[InlineData("QUE", "QUE=")] // "AA" | |||||
[InlineData("QUFB", "QUFB")] // "AAA" | |||||
[InlineData("QUFBQQ", "QUFBQQ==")] // "AAAA" | |||||
[InlineData("QUFBQUFB", "QUFBQUFB")] // "AAAAAA" | |||||
// strings that already contain padding will be returned, even if invalid | |||||
[InlineData("QUFBQQ==", "QUFBQQ==")] | |||||
[InlineData("QUFBQQ=", "QUFBQQ=")] | |||||
[InlineData("=", "=")] | |||||
public void TestPadBase64String(string input, string expected) | |||||
{ | |||||
Assert.Equal(expected, TokenUtils.PadBase64String(input)); | |||||
} | |||||
[Theory] | |||||
// no null, empty, or whitespace | |||||
[InlineData("", typeof(ArgumentNullException))] | |||||
[InlineData(" ", typeof(ArgumentNullException))] | |||||
[InlineData("\t", typeof(ArgumentNullException))] | |||||
[InlineData(null, typeof(ArgumentNullException))] | |||||
// cannot require 3 padding chars | |||||
[InlineData("A", typeof(FormatException))] | |||||
[InlineData("QUFBQ", typeof(FormatException))] | |||||
public void TestPadBase64StringException(string input, Type type) | |||||
{ | |||||
Assert.Throws(type, () => | |||||
{ | |||||
TokenUtils.PadBase64String(input); | |||||
}); | |||||
} | |||||
} | } | ||||
} | } |