Implement urlsafe base64 decode

base64 decoding comes in two flavors, "normal" which we already
implement, and "url safe" which modifies the alphabet to create base64
encodings that are safe to use in filenames and urls.  Functionally this
just involves swapping two characters with underscore and minus in the
encode/decode table.  To avoid duplicating a lot of code, this commit
refactors the base64 tables to be generated at compile time.

Tested: Included unit tests pass.  No usage until next commit.

Change-Id: I71724fd2e04000f115c22a40d382d411986d7b39
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/http/utility.hpp b/http/utility.hpp
index 7bedcb9..a059723 100644
--- a/http/utility.hpp
+++ b/http/utility.hpp
@@ -12,6 +12,7 @@
 #include <nlohmann/json.hpp>
 
 #include <array>
+#include <bit>
 #include <concepts>
 #include <cstddef>
 #include <cstdint>
@@ -73,19 +74,44 @@
     return tagValue;
 }
 
+constexpr static std::array<char, 64> base64key = {
+    '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', '+', '/'};
+
+static constexpr char nop = static_cast<char>(-1);
+constexpr std::array<char, 256> getDecodeTable(bool urlSafe)
+{
+    std::array<char, 256> decodeTable{};
+    decodeTable.fill(nop);
+
+    for (size_t index = 0; index < base64key.size(); index++)
+    {
+        char character = base64key[index];
+        decodeTable[std::bit_cast<uint8_t>(character)] =
+            static_cast<char>(index);
+    }
+
+    if (urlSafe)
+    {
+        // Urlsafe decode tables replace the last two characters with - and _
+        decodeTable['+'] = nop;
+        decodeTable['/'] = nop;
+        decodeTable['-'] = 62;
+        decodeTable['_'] = 63;
+    }
+
+    return decodeTable;
+}
+
 class Base64Encoder
 {
     char overflow1 = '\0';
     char overflow2 = '\0';
     uint8_t overflowCount = 0;
 
-    constexpr static std::array<char, 64> key = {
-        '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', '+', '/'};
-
     // Takes 3 ascii chars, and encodes them as 4 base64 chars
     static void encodeTriple(char first, char second, char third,
                              std::string& output)
@@ -93,18 +119,18 @@
         size_t keyIndex = 0;
 
         keyIndex = static_cast<size_t>(first & 0xFC) >> 2;
-        output += key[keyIndex];
+        output += base64key[keyIndex];
 
         keyIndex = static_cast<size_t>(first & 0x03) << 4;
         keyIndex += static_cast<size_t>(second & 0xF0) >> 4;
-        output += key[keyIndex];
+        output += base64key[keyIndex];
 
         keyIndex = static_cast<size_t>(second & 0x0F) << 2;
         keyIndex += static_cast<size_t>(third & 0xC0) >> 6;
-        output += key[keyIndex];
+        output += base64key[keyIndex];
 
         keyIndex = static_cast<size_t>(third & 0x3F);
-        output += key[keyIndex];
+        output += base64key[keyIndex];
     }
 
   public:
@@ -163,19 +189,19 @@
             return;
         }
         size_t keyIndex = static_cast<size_t>(overflow1 & 0xFC) >> 2;
-        output += key[keyIndex];
+        output += base64key[keyIndex];
 
         keyIndex = static_cast<size_t>(overflow1 & 0x03) << 4;
         if (overflowCount == 2)
         {
             keyIndex += static_cast<size_t>(overflow2 & 0xF0) >> 4;
-            output += key[keyIndex];
+            output += base64key[keyIndex];
             keyIndex = static_cast<size_t>(overflow2 & 0x0F) << 2;
-            output += key[keyIndex];
+            output += base64key[keyIndex];
         }
         else
         {
-            output += key[keyIndex];
+            output += base64key[keyIndex];
             output += '=';
         }
         output += '=';
@@ -203,39 +229,17 @@
     return out;
 }
 
-// TODO this is temporary and should be deleted once base64 is refactored out of
-// crow
+template <bool urlsafe = false>
 inline bool base64Decode(std::string_view input, std::string& output)
 {
-    static const char nop = static_cast<char>(-1);
-    // See note on encoding_data[] in above function
-    static const std::array<char, 256> decodingData = {
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, 62,  nop, nop, nop, 63,  52,  53,  54,  55,  56,  57,  58,  59,
-        60,  61,  nop, nop, nop, nop, nop, nop, nop, 0,   1,   2,   3,   4,
-        5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,  17,  18,
-        19,  20,  21,  22,  23,  24,  25,  nop, nop, nop, nop, nop, nop, 26,
-        27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
-        41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
-        nop, nop, nop, nop};
-
     size_t inputLength = input.size();
 
     // allocate space for output string
     output.clear();
     output.reserve(((inputLength + 2) / 3) * 4);
 
+    static constexpr auto decodingData = getDecodeTable(urlsafe);
+
     auto getCodeValue = [](char c) {
         auto code = static_cast<unsigned char>(c);
         // Ensure we cannot index outside the bounds of the decoding array
diff --git a/test/http/utility_test.cpp b/test/http/utility_test.cpp
index 404700a..f369d8c 100644
--- a/test/http/utility_test.cpp
+++ b/test/http/utility_test.cpp
@@ -32,6 +32,13 @@
     EXPECT_EQ(result, "usern4me:passw0rd");
 }
 
+TEST(Utility, Base64DecodeUrlsafe)
+{
+    std::string result;
+    EXPECT_TRUE(base64Decode<true>("-_abcde", result));
+    EXPECT_EQ(result, "\xfb\xf6\x9b\x71\xd7");
+}
+
 TEST(Utility, Base64DecodeNonAscii)
 {
     std::string junkString("\xff\xee\xdd\xcc\x01\x11\x22\x33");