diff --git a/libutils/string_lib.c b/libutils/string_lib.c index 2cf4607f..9de4f969 100644 --- a/libutils/string_lib.c +++ b/libutils/string_lib.c @@ -32,6 +32,7 @@ #include // CF_BUFSIZE #include // nt_static_assert() #include +#include // CF_SHA1_LEN char *StringVFormat(const char *fmt, va_list ap) { @@ -456,6 +457,30 @@ bool StringIsPrintable(const char *s) return true; } +bool StringIsSHA1Hex(const char *str) +{ + if (str == NULL) + { + return false; + } + + int i; + for (i = 0; str[i] != '\0'; i++) + { + if (i >= (2 * CF_SHA1_LEN)) + { + return false; + } + + if (!isxdigit(str[i])) + { + return false; + } + } + + return (i == (2 * CF_SHA1_LEN)); +} + bool EmptyString(const char *s) { const char *sp; diff --git a/libutils/string_lib.h b/libutils/string_lib.h index b409d505..f9fb1e2c 100644 --- a/libutils/string_lib.h +++ b/libutils/string_lib.h @@ -88,6 +88,13 @@ char *NULLStringToEmpty(char *str); bool StringIsNumeric(const char *name); bool StringIsPrintable(const char *name); +/** + * @brief Check whether a string is a valid SHA1 hash in hex form. + * @param str String to evaluate (NULL is allowed and yields false). + * @return True if str is exactly 40 hex digits, false otherwise. + */ +bool StringIsSHA1Hex(const char *str); + /** * @brief Check if a char is "printable", replacement for isprint * diff --git a/tests/unit/string_lib_test.c b/tests/unit/string_lib_test.c index b62b099c..7386d743 100644 --- a/tests/unit/string_lib_test.c +++ b/tests/unit/string_lib_test.c @@ -1470,6 +1470,43 @@ static void test_StringMatchesOption(void) assert_true(StringMatchesOption("--host", "--hosts", "-H")); } +static void test_StringIsSHA1Hex(void) +{ + // A valid 40-character SHA1 hex string (lower case): + assert_true(StringIsSHA1Hex( + "da39a3ee5e6b4b0d3255bfef95601890afd80709")); + + // Upper case and mixed case are accepted: + assert_true(StringIsSHA1Hex( + "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")); + assert_true(StringIsSHA1Hex( + "Da39A3ee5e6b4b0d3255bfef95601890afd80709")); + + // Too short (39 chars): + assert_false(StringIsSHA1Hex( + "da39a3ee5e6b4b0d3255bfef95601890afd8070")); + + // Too long (41 chars): + assert_false(StringIsSHA1Hex( + "da39a3ee5e6b4b0d3255bfef95601890afd807090")); + + // Empty string: + assert_false(StringIsSHA1Hex("")); + + // Correct length but contains a non-hex character ('g' / 'z'): + assert_false(StringIsSHA1Hex( + "ga39a3ee5e6b4b0d3255bfef95601890afd80709")); + assert_false(StringIsSHA1Hex( + "da39a3ee5e6b4b0d3255bfef95601890afd8070z")); + + // Correct length but contains a space: + assert_false(StringIsSHA1Hex( + "da39a3ee5e6b4b0d3255bfef9560189 afd80709")); + + // NULL is allowed and yields false: + assert_false(StringIsSHA1Hex(NULL)); +} + static void test_StringFind(void) { static const char *const str = "Hello CFEngine"; @@ -1585,6 +1622,7 @@ int main() unit_test(test_StrCatDelim), unit_test(test_StringMatchesOption), + unit_test(test_StringIsSHA1Hex), unit_test(test_StringFind), };