From 3fd8900b1afeea6915305da45025df85801d52ba Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Sat, 3 Oct 2026 04:47:36 +0800 Subject: [PATCH 1/3] Accept canonical hash names in templates --- hash_print.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/hash_print.c b/hash_print.c index c5698e81..b9c628d7 100644 --- a/hash_print.c +++ b/hash_print.c @@ -273,7 +273,8 @@ static unsigned get_file_escaping_flags(file_t* file, unsigned esc_flags, unsign } /** - * Convert given case-insensitive name to a printf directive id + * Convert given case-insensitive name to a printf directive id, + * ignoring dashes in the name. * * @param name printf directive name (not a 0-terminated) * @param length name length @@ -283,13 +284,15 @@ static unsigned get_file_escaping_flags(file_t* file, unsigned esc_flags, unsign static unsigned printf_name_to_id(const char* name, size_t length, unsigned* flags) { char buf[20]; - size_t i; + size_t i, normalized_length = 0; print_hash_info* info = hash_info_table; if (length > (sizeof(buf) - 1)) return 0; - for (i = 0; i < length; i++) - buf[i] = tolower(name[i]); + for (i = 0; i < length; i++) { + if (name[i] != '-') + buf[normalized_length++] = (char)tolower((unsigned char)name[i]); + } /* check for legacy '%{urlname}' directive for compatibility */ if (length == 7 && memcmp(buf, "urlname", 7) == 0) { @@ -302,8 +305,15 @@ static unsigned printf_name_to_id(const char* name, size_t length, unsigned* fla /* loop by hash functions */ for (info = hash_info_table; info->hash_id; info++) { - if (memcmp(buf, info->short_name, length) == 0 && - info->short_name[length] == 0) + const char* p = info->short_name; + size_t pos = 0; + for (; *p && pos < normalized_length; p++) { + if (*p != '-') { + if (buf[pos++] != *p) break; + } + } + while (*p == '-') p++; + if (pos == normalized_length && *p == 0) return info->hash_id; } return 0; From 239c29c730a627306386ade9357edf67857848ae Mon Sep 17 00:00:00 2001 From: dajiaohuang Date: Mon, 5 Oct 2026 01:45:14 +0800 Subject: [PATCH 2/3] test: verify canonical template names through the RHash CLI --- hash_print.c | 2 +- tests/test_template_hash_names.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/test_template_hash_names.py diff --git a/hash_print.c b/hash_print.c index b9c628d7..2ab5e18b 100644 --- a/hash_print.c +++ b/hash_print.c @@ -283,7 +283,7 @@ static unsigned get_file_escaping_flags(file_t* file, unsigned esc_flags, unsign */ static unsigned printf_name_to_id(const char* name, size_t length, unsigned* flags) { - char buf[20]; + char buf[20] = { 0 }; size_t i, normalized_length = 0; print_hash_info* info = hash_info_table; diff --git a/tests/test_template_hash_names.py b/tests/test_template_hash_names.py new file mode 100644 index 00000000..6bb260d1 --- /dev/null +++ b/tests/test_template_hash_names.py @@ -0,0 +1,34 @@ +"""Exercise public hash names through the compiled RHash CLI.""" +import hashlib +import os +from pathlib import Path +import re +import subprocess +import sys +import tempfile + +binary = Path(sys.argv[1] if len(sys.argv) > 1 else "./rhash").resolve() +environment = dict(os.environ) +library = str(binary.parent / "librhash") +environment["LD_LIBRARY_PATH"] = library + os.pathsep + environment.get("LD_LIBRARY_PATH", "") + + +def run(*arguments): + return subprocess.run([str(binary), *arguments], env=environment, + text=True, capture_output=True, check=True, timeout=10).stdout + + +with tempfile.TemporaryDirectory() as temporary: + sample = Path(temporary) / "a b" + sample.write_bytes(b"abc") + names = run("--list-hashes").splitlines() + assert names + for name in names: + canonical = run("--printf", "%{" + name + "}", str(sample)) + alias = run("--printf", "%{" + name.lower().replace("-", "") + "}", str(sample)) + assert re.fullmatch("[0-9A-Za-z]+", canonical), (name, canonical) + assert canonical.lower() == alias.lower(), (name, canonical, alias) + assert run("--printf", "%{SHA-256}", str(sample)).lower() == hashlib.sha256(b"abc").hexdigest() + assert run("--printf", "%{urlname}", str(sample)) == "a%20b" + assert run("--printf", "%{mtime}", str(sample)) + print(f"PASS {len(names)} canonical/alias hash pairs, SHA-256 known value and legacy urlname/mtime") From f90c9c404d04e3874042ee35c866e75cb04951e6 Mon Sep 17 00:00:00 2001 From: Aleksey Kravchenko Date: Mon, 19 Oct 2026 02:13:44 +0300 Subject: [PATCH 3/3] Fix: Accept canonical hash names in templates --- hash_print.c | 87 +++++++++++++++++++++++++++------------------------- hash_print.h | 1 + 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/hash_print.c b/hash_print.c index 2ab5e18b..f70e523c 100644 --- a/hash_print.c +++ b/hash_print.c @@ -273,26 +273,35 @@ static unsigned get_file_escaping_flags(file_t* file, unsigned esc_flags, unsign } /** - * Convert given case-insensitive name to a printf directive id, - * ignoring dashes in the name. + * Convert the given printf directive name to a hash_id, + * case-insensitively, ignoring isolated dashes. + * A name with consecutive dashes (e.g. --) is rejected. + * + * Special directives "urlname" and "mtime" are also recognized. * - * @param name printf directive name (not a 0-terminated) + * @param name printf directive name (not required to be NUL-terminated) * @param length name length - * @param flags pointer to unsigned variable to receive print flags - * @return directive id on success, 0 on fail + * @param flags receives print flags, set only when a special directive + * matches, left untouched otherwise + * @return hash_id on success, 0 on failure or for a special directive + * (in which case @p flags is set and must be checked) */ static unsigned printf_name_to_id(const char* name, size_t length, unsigned* flags) { char buf[20] = { 0 }; - size_t i, normalized_length = 0; - print_hash_info* info = hash_info_table; + size_t i, j; + print_hash_info* info; if (length > (sizeof(buf) - 1)) return 0; - for (i = 0; i < length; i++) { + for (i = 0, j = 0; i < length; i++) { if (name[i] != '-') - buf[normalized_length++] = (char)tolower((unsigned char)name[i]); + buf[j++] = (char)tolower((unsigned char)name[i]); + else if ((i + 1) < length && name[i + 1] == '-') + return 0; } + length = j; + assert(length < sizeof(buf)); /* check for legacy '%{urlname}' directive for compatibility */ if (length == 7 && memcmp(buf, "urlname", 7) == 0) { @@ -305,15 +314,8 @@ static unsigned printf_name_to_id(const char* name, size_t length, unsigned* fla /* loop by hash functions */ for (info = hash_info_table; info->hash_id; info++) { - const char* p = info->short_name; - size_t pos = 0; - for (; *p && pos < normalized_length; p++) { - if (*p != '-') { - if (buf[pos++] != *p) break; - } - } - while (*p == '-') p++; - if (pos == normalized_length && *p == 0) + if (memcmp(buf, info->cmp_name, length) == 0 && + info->cmp_name[length] == 0) return info->hash_id; } return 0; @@ -689,9 +691,9 @@ void init_hash_info_table(void) for (hash_mask = get_all_supported_hash_mask(); hash_mask; hash_mask &= hash_mask - 1) { uint64_t bit64 = hash_mask & -hash_mask; unsigned hash_id = bit64_to_hash_id(bit64); - const char* p; - char* e; - char* d; + const char* name; + char* short_name; + size_t i, j; RSH_REQUIRE((info - hash_info_table) < RHASH_HASH_COUNT, "too many hash ids\n"); @@ -699,28 +701,31 @@ void init_hash_info_table(void) info->short_char = ((bit64 & short_opt_mask) != 0 && *short_opt ? *(short_opt++) : 0); - info->name = rhash_get_name(hash_id); - RSH_REQUIRE(info->name != NULL, "rhash_get_name(0x%08x) failed\n", hash_id); - assert(strlen(info->name) < 19); - p = info->name; - d = info->short_name; - e = info->short_name + 19; /* buffer overflow protection */ - - if (memcmp(info->name, "SHA", 3) == 0 || memcmp(info->name, "GOST", 4) == 0) { - strcpy(d, p); - for (; *d && d < e; d++) { - if ('A' <= *d && *d <= 'Z') { - *d |= 0x20; - } - } - } else { - for (; *p && d < e; p++) { - if (*p != '-' || p[1] >= '9') { - *(d++) = (*p | 0x20); - } + name = rhash_get_name(hash_id); + RSH_REQUIRE(name != NULL, "rhash_get_name(0x%08x) failed\n", hash_id); + assert(strlen(name) < 19); + info->name = name; + short_name = info->short_name; + + /* initialize short_name for printf directive %{short_name} */ + for (i = 0, j = 0; name[i] && j < 19; i++, j++) { + char c = name[i]; + if ('A' <= c && c <= 'Z') { + c = c | 0x20; + /* skip dash if if is in - sequence */ + if (name[i + 1] == '-' && name[i + 2] <= '9') i++; } + short_name[j] = c; } - *d = 0; + short_name[j] = 0; + + /* initialize info->cmp_name by dropping all dashes from short_name */ + for (i = 0, j = 0; short_name[i] && j < 19; i++) { + if (short_name[i] != '-') + info->cmp_name[j++] = short_name[i]; + } + info->cmp_name[j] = 0; + if ((bit64 & custom_bsd_name) != 0) { switch ((unsigned)bit64) { case RHASH_RIPEMD160: diff --git a/hash_print.h b/hash_print.h index 9567311f..dd91906d 100644 --- a/hash_print.h +++ b/hash_print.h @@ -17,6 +17,7 @@ typedef struct print_hash_info const char* name; const char* bsd_name; unsigned hash_id; + char cmp_name[20]; char short_name[20]; char short_char; } print_hash_info;