1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-18 04:58:51 +02:00

Add base64url encoding.

For now only encoding is supported. Decoding is not needed and may never be.
This commit is contained in:
David Steele 2021-02-19 19:21:06 -05:00
parent a1f4fd32a1
commit 00b60e564e
4 changed files with 148 additions and 2 deletions

View File

@ -14,7 +14,7 @@ Binary to String Encode/Decode
Assert that encoding type is valid. This needs to be kept up to date with the last item in the enum.
***********************************************************************************************************************************/
#define ASSERT_ENCODE_TYPE_VALID(type) \
ASSERT(type <= encodeBase64);
ASSERT(type <= encodeBase64Url);
/***********************************************************************************************************************************
Base64 encoding/decoding
@ -224,6 +224,96 @@ decodeToBinSizeBase64(const char *source)
FUNCTION_TEST_RETURN(destinationSize);
}
/***********************************************************************************************************************************
Base64 encoding
***********************************************************************************************************************************/
static const char encodeBase64LookupUrl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
static void
encodeToStrBase64Url(const unsigned char *source, size_t sourceSize, char *destination)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(UCHARDATA, source);
FUNCTION_TEST_PARAM(SIZE, sourceSize);
FUNCTION_TEST_PARAM_P(CHARDATA, destination);
FUNCTION_TEST_END();
ASSERT(source != NULL);
ASSERT(destination != NULL);
unsigned int destinationIdx = 0;
// Encode the string from three bytes to four characters
for (unsigned int sourceIdx = 0; sourceIdx < sourceSize; sourceIdx += 3)
{
// First encoded character is always used completely
destination[destinationIdx++] = encodeBase64LookupUrl[source[sourceIdx] >> 2];
// If there is only one byte to encode then the second encoded character is only partly used
if (sourceSize - sourceIdx == 1)
{
destination[destinationIdx++] = encodeBase64LookupUrl[(source[sourceIdx] & 0x03) << 4];
}
// Else if more than one byte to encode
else
{
// If there is more than one byte to encode then the second encoded character is used completely
destination[destinationIdx++] =
encodeBase64LookupUrl[((source[sourceIdx] & 0x03) << 4) | ((source[sourceIdx + 1] & 0xf0) >> 4)];
// If there are only two bytes to encode then the third encoded character is only partly used
if (sourceSize - sourceIdx == 2)
{
destination[destinationIdx++] = encodeBase64LookupUrl[(source[sourceIdx + 1] & 0x0f) << 2];
}
// Else the third and fourth encoded characters are used completely
else
{
destination[destinationIdx++] =
encodeBase64LookupUrl[((source[sourceIdx + 1] & 0x0f) << 2) | ((source[sourceIdx + 2] & 0xc0) >> 6)];
destination[destinationIdx++] = encodeBase64LookupUrl[source[sourceIdx + 2] & 0x3f];
}
}
}
// Zero-terminate the string
destination[destinationIdx] = 0;
FUNCTION_TEST_RETURN_VOID();
}
/**********************************************************************************************************************************/
static size_t
encodeToStrSizeBase64Url(size_t sourceSize)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(SIZE, sourceSize);
FUNCTION_TEST_END();
// Calculate how many groups of three are in the source. Each group of three is encoded with four bytes.
size_t encodeTotal = sourceSize / 3 * 4;
// Dertermine additional required bytes for the partial group, if any
switch (sourceSize % 3)
{
// One byte requires two characters to encode
case 1:
encodeTotal += 2;
break;
// Two bytes require three characters to encode
case 2:
encodeTotal += 3;
break;
// If mod is zero then sourceSize was evenly divisible and no additional bytes are required
case 0:
break;
}
FUNCTION_TEST_RETURN(encodeTotal);
}
/***********************************************************************************************************************************
Generic encoding/decoding
***********************************************************************************************************************************/
@ -244,6 +334,10 @@ encodeToStr(EncodeType type, const unsigned char *source, size_t sourceSize, cha
case encodeBase64:
encodeToStrBase64(source, sourceSize, destination);
break;
case encodeBase64Url:
encodeToStrBase64Url(source, sourceSize, destination);
break;
}
FUNCTION_TEST_RETURN_VOID();
@ -267,6 +361,10 @@ encodeToStrSize(EncodeType type, size_t sourceSize)
case encodeBase64:
destinationSize = encodeToStrSizeBase64(sourceSize);
break;
case encodeBase64Url:
destinationSize = encodeToStrSizeBase64Url(sourceSize);
break;
}
FUNCTION_TEST_RETURN(destinationSize);
@ -289,6 +387,9 @@ decodeToBin(EncodeType type, const char *source, unsigned char *destination)
case encodeBase64:
decodeToBinBase64(source, destination);
break;
default:
ASSERT_MSG("unsupported");
}
FUNCTION_TEST_RETURN_VOID();
@ -312,6 +413,9 @@ decodeToBinSize(EncodeType type, const char *source)
case encodeBase64:
destinationSize = decodeToBinSizeBase64(source);
break;
default:
ASSERT_MSG("unsupported");
}
FUNCTION_TEST_RETURN(destinationSize);

View File

@ -12,6 +12,7 @@ Encoding types
typedef enum
{
encodeBase64,
encodeBase64Url,
} EncodeType;
/***********************************************************************************************************************************

View File

@ -115,7 +115,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: encode
total: 1
total: 2
coverage:
- common/encode

View File

@ -98,5 +98,46 @@ testRun(void)
"base64 last character must be '=' if second to last is");
}
// *****************************************************************************************************************************
if (testBegin("base64url"))
{
TEST_TITLE("encode");
const unsigned char *encode = (const unsigned char *)"string_to_encode\r\n";
char destinationEncode[256];
encodeToStr(encodeBase64Url, encode, 1, destinationEncode);
TEST_RESULT_Z(destinationEncode, "cw", "1 character encode");
TEST_RESULT_UINT(encodeToStrSize(encodeBase64Url, 1), strlen(destinationEncode), "check size");
encodeToStr(encodeBase64Url, encode, 2, destinationEncode);
TEST_RESULT_Z(destinationEncode, "c3Q", "2 character encode");
TEST_RESULT_UINT(encodeToStrSize(encodeBase64Url, 2), strlen(destinationEncode), "check size");
encodeToStr(encodeBase64Url, encode, 3, destinationEncode);
TEST_RESULT_Z(destinationEncode, "c3Ry", "3 character encode");
TEST_RESULT_UINT(encodeToStrSize(encodeBase64Url, 3), strlen(destinationEncode), "check size");
encodeToStr(encodeBase64Url, encode, strlen((char *)encode) - 2, destinationEncode);
TEST_RESULT_Z(destinationEncode, "c3RyaW5nX3RvX2VuY29kZQ", "encode full string");
TEST_RESULT_UINT(encodeToStrSize(encodeBase64Url, strlen((char *)encode) - 2), strlen(destinationEncode), "check size");
encodeToStr(encodeBase64Url, encode, strlen((char *)encode), destinationEncode);
TEST_RESULT_Z(destinationEncode, "c3RyaW5nX3RvX2VuY29kZQ0K", "encode full string with \\r\\n");
TEST_RESULT_UINT(encodeToStrSize(encodeBase64Url, strlen((char *)encode)), strlen(destinationEncode), "check size");
encodeToStr(encodeBase64Url, encode, strlen((char *)encode) + 1, destinationEncode);
TEST_RESULT_Z(destinationEncode, "c3RyaW5nX3RvX2VuY29kZQ0KAA", "encode full string with \\r\\n and null");
TEST_RESULT_UINT(encodeToStrSize(encodeBase64Url, strlen((char *)encode) + 1), strlen(destinationEncode), "check size");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("decode unsupported");
unsigned char destinationDecode[256];
TEST_ERROR(decodeToBinSize(encodeBase64Url, "c3"), AssertError, "unsupported");
TEST_ERROR(decodeToBin(encodeBase64Url, "c3", destinationDecode), AssertError, "unsupported");
}
FUNCTION_HARNESS_RESULT_VOID();
}