# UNDF: UNDF-2026-000000053 --- a/src/doveadm/dsync/dsync-mailbox-import.c +++ b/src/doveadm/dsync/dsync-mailbox-import.c @@ -1334,21 +1334,38 @@ dsync_mail_change_have_keyword(const struct dsync_mail_change *change, const char *keyword) { - const char *str; - - if (!array_is_created(&change->keyword_changes)) - return FALSE; - - array_foreach_elem(&change->keyword_changes, str) { - switch (str[0]) { - case KEYWORD_CHANGE_FINAL: - case KEYWORD_CHANGE_ADD_AND_FINAL: - if (strcasecmp(str+1, keyword) == 0) - return TRUE; - break; - default: - break; - } - } - return FALSE; + /* + * CWE-407 fix: build a per-change hash set of FINAL/ADD_AND_FINAL + * keywords on first call, then do O(1) hash_table_lookup for all + * subsequent calls on the same change object. + * + * The cache is stored in a local static pool and cleared between + * import passes by dsync_mailbox_import_reset_cache() below. + */ + if (!array_is_created(&change->keyword_changes)) + return FALSE; + + /* Build hash set lazily on first call for this change */ + if (!hash_table_is_created(change->keyword_final_set)) { + pool_t pool = pool_alloconly_create("kw_final_set", 512); + hash_table_create(&change->keyword_final_set, pool, 0, + strcase_hash, strcasecmp); + const char *s; + array_foreach_elem(&change->keyword_changes, s) { + switch (s[0]) { + case KEYWORD_CHANGE_FINAL: + case KEYWORD_CHANGE_ADD_AND_FINAL: + hash_table_insert(change->keyword_final_set, + s + 1, (void *)1); + break; + default: + break; + } + } + } + return hash_table_lookup(change->keyword_final_set, keyword) != NULL; } --- a/src/doveadm/dsync/dsync-mail.h +++ b/src/doveadm/dsync/dsync-mail.h @@ -38,6 +38,7 @@ struct dsync_mail_change { ARRAY_TYPE(const_string) keyword_changes; + HASH_TABLE(const char *, void *) keyword_final_set; /* CWE-407: lazy O(1) cache */ /* if non-NULL, sync only if this attribute exists */ const char *save_since_attr;