* 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 paddingtags/2.1.0
| @@ -17,6 +17,47 @@ namespace Discord | |||
| /// </remarks> | |||
| 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> | |||
| /// Decodes a base 64 encoded string into a ulong value. | |||
| /// </summary> | |||
| @@ -29,6 +70,8 @@ namespace Discord | |||
| try | |||
| { | |||
| // re-add base64 padding if missing | |||
| encoded = PadBase64String(encoded); | |||
| // decode the base64 string | |||
| var bytes = Convert.FromBase64String(encoded); | |||
| var idStr = Encoding.UTF8.GetString(bytes); | |||
| @@ -46,7 +89,7 @@ namespace Discord | |||
| } | |||
| catch (ArgumentException) | |||
| { | |||
| // ignore exception, can be thrown by BitConverter | |||
| // ignore exception, can be thrown by BitConverter, or by PadBase64String | |||
| } | |||
| return null; | |||
| } | |||
| @@ -77,6 +77,8 @@ namespace Discord | |||
| // 59 char token | |||
| [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs")] | |||
| [InlineData("MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWss")] | |||
| // simulated token with a very old user id | |||
| [InlineData("ODIzNjQ4MDEzNTAxMDcxMzY=.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKW")] | |||
| public void TestBotTokenDoesNotThrowExceptions(string token) | |||
| { | |||
| // 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 | |||
| // indicating if a value is null | |||
| [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 | |||
| [InlineData("", true, 0)] | |||
| [InlineData(" ", true, 0)] | |||
| @@ -164,5 +170,37 @@ namespace Discord | |||
| else | |||
| 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); | |||
| }); | |||
| } | |||
| } | |||
| } | |||