REDESIGNED: Base64 encoding/decoding functions

Found some issues with output size when padding required, just re-implemented both functions from scratch.
This commit is contained in:
Ray 2025-05-28 17:19:19 +02:00
parent 5ddd13b775
commit d7148f5f9d
2 changed files with 83 additions and 70 deletions

View file

@ -1154,7 +1154,7 @@ RLAPI long GetFileModTime(const char *fileName); // Get file mo
RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree()
RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree()
RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree()
RLAPI unsigned char *DecodeDataBase64(const char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() RLAPI unsigned char *DecodeDataBase64(const char *text, int *outputSize); // Decode Base64 string (expected NULL terminated), memory must be MemFree()
RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code
RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes)
RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes)

View file

@ -2542,96 +2542,109 @@ unsigned char *DecompressData(const unsigned char *compData, int compDataSize, i
// NOTE: Returned string includes NULL terminator, considered on outputSize // NOTE: Returned string includes NULL terminator, considered on outputSize
char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize)
{ {
static const unsigned char base64encodeTable[] = { // Base64 conversion table from RFC 4648 [0..63]
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // NOTE: They represent 64 values (6 bits), to encode 3 bytes of data into 4 "sixtets" (6bit characters)
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', static const char base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
static const int modTable[] = { 0, 2, 1 }; // Compute expected size and padding
int paddedSize = dataSize;
while (paddedSize%3 != 0) paddedSize++; // Padding bytes to round 4*(dataSize/3) to 4 bytes
int estimatedOutputSize = 4*(paddedSize/3);
int padding = paddedSize - dataSize;
*outputSize = 4*((dataSize + 2)/3) + 1; // Adding +1 for NULL terminator // Adding null terminator to string
estimatedOutputSize += 1;
char *encodedData = (char *)RL_MALLOC(*outputSize); // Load some memory to store encoded string
char *encodedData = (char *)RL_CALLOC(estimatedOutputSize, 1);
if (encodedData == NULL) return NULL;
if (encodedData == NULL) return NULL; // Security check int outputCount = 0;
for (int i = 0; i < dataSize;)
for (int i = 0, j = 0; i < dataSize;)
{ {
unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0; unsigned int octetA = 0;
unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0; unsigned int octetB = 0;
unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0; unsigned int octetC = 0;
unsigned int octetPack = 0;
unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC; octetA = data[i]; // Generates 2 sextets
octetB = ((i + 1) < dataSize)? data[i + 1] : 0; // Generates 3 sextets
octetC = ((i + 2) < dataSize)? data[i + 2] : 0; // Generates 4 sextets
encodedData[j++] = base64encodeTable[(triple >> 3*6) & 0x3F]; octetPack = (octetA << 16) | (octetB << 8) | octetC;
encodedData[j++] = base64encodeTable[(triple >> 2*6) & 0x3F];
encodedData[j++] = base64encodeTable[(triple >> 1*6) & 0x3F]; encodedData[outputCount + 0] = (unsigned char)(base64EncodeTable[(octetPack >> 18) & 0x3f]);
encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F]; encodedData[outputCount + 1] = (unsigned char)(base64EncodeTable[(octetPack >> 12) & 0x3f]);
encodedData[outputCount + 2] = (unsigned char)(base64EncodeTable[(octetPack >> 6) & 0x3f]);
encodedData[outputCount + 3] = (unsigned char)(base64EncodeTable[octetPack & 0x3f]);
outputCount += 4;
i += 3;
} }
// Add required padding bytes
for (int p = 0; p < padding; p++) encodedData[outputCount - p - 1] = '=';
for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character // Add null terminator to string
encodedData[outputCount] = '\0';
outputCount++;
encodedData[*outputSize - 1] = '\0'; if (outputCount != estimatedOutputSize) TRACELOG(LOG_WARNING, "BASE64: Output size differs from estimation");
*outputSize = estimatedOutputSize;
return encodedData; return encodedData;
} }
// Decode Base64 string data // Decode Base64 string (expected NULL terminated)
unsigned char *DecodeDataBase64(const char *data, int *outputSize) unsigned char *DecodeDataBase64(const char *text, int *outputSize)
{ {
static const unsigned char base64decodeTable[] = { // Base64 decode table
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // NOTE: Following ASCII order [0..255] assigning the expected sixtet value to
0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // every character in the corresponding ASCII position
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, static const unsigned char base64DecodeTable[256] = {
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7,
['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15,
['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25,
['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33,
['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41,
['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51,
['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59,
['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63
}; };
// Get output size of Base64 input data // Compute expected size and padding
int outSize = 0; int dataSize = (int)strlen(text); // WARNING: Expecting NULL terminated strings!
for (int i = 0; data[4*i] != 0; i++) int ending = dataSize - 1;
int padding = 0;
while (text[ending] == '=') { padding++; ending--; }
int estimatedOutputSize = 3*(dataSize/4) - padding;
int maxOutputSize = 3*(dataSize/4);
// Load some memory to store decoded data
// NOTE: Allocated enough size to include padding
unsigned char *decodedData = (unsigned char *)RL_CALLOC(maxOutputSize, 1);
if (decodedData == NULL) return NULL;
int outputCount = 0;
for (int i = 0, j = 0; i < dataSize;)
{ {
if (data[4*i + 3] == '=') // Every 4 sixtets must generate 3 octets
{ unsigned int sixtetA = base64DecodeTable[(unsigned char)text[i]];
if (data[4*i + 2] == '=') outSize += 1; unsigned int sixtetB = base64DecodeTable[(unsigned char)text[i + 1]];
else outSize += 2; unsigned int sixtetC = ((unsigned char)text[i + 2] != '=')? base64DecodeTable[(unsigned char)text[i + 2]] : 0;
} unsigned int sixtetD = ((unsigned char)text[i + 3] != '=')? base64DecodeTable[(unsigned char)text[i + 3]] : 0;
else outSize += 3;
unsigned int octetPack = (sixtetA << 18) | (sixtetB << 12) | (sixtetC << 6) | sixtetD;
decodedData[outputCount + 0] = (octetPack >> 16) & 0xff;
decodedData[outputCount + 1] = (octetPack >> 8) & 0xff;
decodedData[outputCount + 2] = octetPack & 0xff;
outputCount += 3;
i += 4;
} }
// Allocate memory to store decoded Base64 data if (estimatedOutputSize != (outputCount - padding)) TRACELOG(LOG_WARNING, "BASE64: Decoded size differs from estimation");
unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize);
for (int i = 0; i < outSize/3; i++) *outputSize = estimatedOutputSize;
{
unsigned char a = base64decodeTable[(int)data[4*i]];
unsigned char b = base64decodeTable[(int)data[4*i + 1]];
unsigned char c = base64decodeTable[(int)data[4*i + 2]];
unsigned char d = base64decodeTable[(int)data[4*i + 3]];
decodedData[3*i] = (a << 2) | (b >> 4);
decodedData[3*i + 1] = (b << 4) | (c >> 2);
decodedData[3*i + 2] = (c << 6) | d;
}
if (outSize%3 == 1)
{
int n = outSize/3;
unsigned char a = base64decodeTable[(int)data[4*n]];
unsigned char b = base64decodeTable[(int)data[4*n + 1]];
decodedData[outSize - 1] = (a << 2) | (b >> 4);
}
else if (outSize%3 == 2)
{
int n = outSize/3;
unsigned char a = base64decodeTable[(int)data[4*n]];
unsigned char b = base64decodeTable[(int)data[4*n + 1]];
unsigned char c = base64decodeTable[(int)data[4*n + 2]];
decodedData[outSize - 2] = (a << 2) | (b >> 4);
decodedData[outSize - 1] = (b << 4) | (c >> 2);
}
*outputSize = outSize;
return decodedData; return decodedData;
} }