From 38465a6cea2f05c980fa6f7da86ba858d84a6956 Mon Sep 17 00:00:00 2001 From: Jesse Black Date: Tue, 9 Apr 2024 03:17:45 +0000 Subject: [PATCH 1/2] throw exception when parsing invalid characters --- .gitignore | 1 + src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs | 13 ++++++++++++- src/Ulid/Ulid.cs | 13 ++++++++++++- tests/Ulid.Tests/UlidTest.cs | 2 ++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7ec2ae4..c0a485c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +.mono/ # Build results [Dd]ebug/ diff --git a/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs b/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs index 0e6b351..2d218ca 100644 --- a/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs +++ b/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs @@ -278,6 +278,10 @@ public static Ulid Parse(string base32) public static Ulid Parse(ReadOnlySpan base32) { if (base32.Length != 26) throw new ArgumentException("invalid base32 length, length:" + base32.Length); + for (int i = 0; i < base32.Length; i++) + { + if (CharToBase32[base32[i]] == 255) throw new ArgumentException("invalid base32 character, character:" + base32[i]); + } return new Ulid(base32); } @@ -299,7 +303,14 @@ public static bool TryParse(ReadOnlySpan base32, out Ulid ulid) ulid = default(Ulid); return false; } - + for (int i = 0; i < base32.Length; i++) + { + if (CharToBase32[base32[i]] == 255) + { + ulid = default(Ulid); + return false; + } + } try { ulid = new Ulid(base32); diff --git a/src/Ulid/Ulid.cs b/src/Ulid/Ulid.cs index 0e6b351..2d218ca 100644 --- a/src/Ulid/Ulid.cs +++ b/src/Ulid/Ulid.cs @@ -278,6 +278,10 @@ public static Ulid Parse(string base32) public static Ulid Parse(ReadOnlySpan base32) { if (base32.Length != 26) throw new ArgumentException("invalid base32 length, length:" + base32.Length); + for (int i = 0; i < base32.Length; i++) + { + if (CharToBase32[base32[i]] == 255) throw new ArgumentException("invalid base32 character, character:" + base32[i]); + } return new Ulid(base32); } @@ -299,7 +303,14 @@ public static bool TryParse(ReadOnlySpan base32, out Ulid ulid) ulid = default(Ulid); return false; } - + for (int i = 0; i < base32.Length; i++) + { + if (CharToBase32[base32[i]] == 255) + { + ulid = default(Ulid); + return false; + } + } try { ulid = new Ulid(base32); diff --git a/tests/Ulid.Tests/UlidTest.cs b/tests/Ulid.Tests/UlidTest.cs index efad835..9c4c790 100644 --- a/tests/Ulid.Tests/UlidTest.cs +++ b/tests/Ulid.Tests/UlidTest.cs @@ -123,6 +123,7 @@ public void UlidParseRejectsInvalidStrings() { Assert.Throws(() => Ulid.Parse("1234")); Assert.Throws(() => Ulid.Parse(Guid.NewGuid().ToString())); + Assert.Throws(() => Ulid.Parse("01HV0CXYMHVZD8AETQVAYVDT0U")); } [Fact] @@ -130,6 +131,7 @@ public void UlidTryParseFailsForInvalidStrings() { Assert.False(Ulid.TryParse("1234", out _)); Assert.False(Ulid.TryParse(Guid.NewGuid().ToString(), out _)); + Assert.False(Ulid.TryParse("01HV0CXYMHVZD8AETQVAYVDT0U", out _)); } #if NET6_0_OR_GREATER From 25ca4edeefb465d16ebc2b34ae5c0de2c58aea57 Mon Sep 17 00:00:00 2001 From: Jesse Black Date: Tue, 9 Apr 2024 03:52:23 +0000 Subject: [PATCH 2/2] map i,l,o to 1,1,0 per crockford base32 spec --- src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs | 2 +- src/Ulid/Ulid.cs | 2 +- tests/Ulid.Tests/UlidTest.cs | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs b/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs index 2d218ca..c1cf005 100644 --- a/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs +++ b/src/Ulid.Unity/Assets/Scripts/Ulid/Ulid.cs @@ -40,7 +40,7 @@ namespace System // wa-o, System Namespace!? // https://en.wikipedia.org/wiki/Base32 static readonly char[] Base32Text = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".ToCharArray(); static readonly byte[] Base32Bytes = Encoding.UTF8.GetBytes(Base32Text); - static readonly byte[] CharToBase32 = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31 }; + static readonly byte[] CharToBase32 = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31 }; static readonly DateTimeOffset UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static readonly Ulid MinValue = new Ulid(UnixEpoch.ToUnixTimeMilliseconds(), new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); diff --git a/src/Ulid/Ulid.cs b/src/Ulid/Ulid.cs index 2d218ca..c1cf005 100644 --- a/src/Ulid/Ulid.cs +++ b/src/Ulid/Ulid.cs @@ -40,7 +40,7 @@ namespace System // wa-o, System Namespace!? // https://en.wikipedia.org/wiki/Base32 static readonly char[] Base32Text = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".ToCharArray(); static readonly byte[] Base32Bytes = Encoding.UTF8.GetBytes(Base32Text); - static readonly byte[] CharToBase32 = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31 }; + static readonly byte[] CharToBase32 = new byte[] { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31 }; static readonly DateTimeOffset UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static readonly Ulid MinValue = new Ulid(UnixEpoch.ToUnixTimeMilliseconds(), new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); diff --git a/tests/Ulid.Tests/UlidTest.cs b/tests/Ulid.Tests/UlidTest.cs index 9c4c790..6d64745 100644 --- a/tests/Ulid.Tests/UlidTest.cs +++ b/tests/Ulid.Tests/UlidTest.cs @@ -134,6 +134,12 @@ public void UlidTryParseFailsForInvalidStrings() Assert.False(Ulid.TryParse("01HV0CXYMHVZD8AETQVAYVDT0U", out _)); } + [Fact] + public void CrockfordBase32DecodingMapsILO() + { + Ulid.Parse("01HV0CXYMHVZD8AETQVAILOilo").Should().Be(Ulid.Parse("01HV0CXYMHVZD8AETQVA110110")); + } + #if NET6_0_OR_GREATER [Fact] public void UlidTryFormatReturnsStringAndLength()