From ff29e18901ea136dc5d5cbcf338836be10e21040 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 7 Jun 2026 10:28:13 -0400 Subject: [PATCH 1/2] Avoid double allocation when joining To/Cc headers Folding repeated To:/Cc: headers built a throwaway buffer with emalloc + strcpy + two strcat calls, then handed it to add_assoc_string, which copied it again into a zend_string before the temporary was freed. That is two allocations and two copies per fold. Build the joined value directly into a single zend_string with memcpy and store it with add_assoc_str, which takes ownership. One allocation, one copy. Output ("a, b, c") is unchanged. --- php_mailparse_mime.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/php_mailparse_mime.c b/php_mailparse_mime.c index e71bfec..2381388 100644 --- a/php_mailparse_mime.c +++ b/php_mailparse_mime.c @@ -435,17 +435,16 @@ static int php_mimepart_process_header(php_mimepart *part) * join multiple To: or Cc: lines together */ header_zstring = zend_string_init(header_key, strlen(header_key), 0); if ((strcmp(header_key, "to") == 0 || strcmp(header_key, "cc") == 0) && (zheaderval = zend_hash_find(Z_ARRVAL_P(&part->headerhash), header_zstring)) != NULL) { - int newlen; - char *newstr; - - newlen = strlen(header_val) + Z_STRLEN_P(zheaderval) + 3; - newstr = emalloc(newlen); - - strcpy(newstr, Z_STRVAL_P(zheaderval)); - strcat(newstr, ", "); - strcat(newstr, header_val); - add_assoc_string(&part->headerhash, header_key, newstr); - efree(newstr); + zend_string *existing = Z_STR_P(zheaderval); + size_t existing_len = ZSTR_LEN(existing); + size_t add_len = strlen(header_val); + zend_string *joined = zend_string_alloc(existing_len + 2 + add_len, 0); + + memcpy(ZSTR_VAL(joined), ZSTR_VAL(existing), existing_len); + memcpy(ZSTR_VAL(joined) + existing_len, ", ", 2); + memcpy(ZSTR_VAL(joined) + existing_len + 2, header_val, add_len); + ZSTR_VAL(joined)[existing_len + 2 + add_len] = '\0'; + add_assoc_str(&part->headerhash, header_key, joined); } else { if((zheaderval = zend_hash_find(Z_ARRVAL_P(&part->headerhash), header_zstring)) != NULL) { if(Z_TYPE_P(zheaderval) == IS_ARRAY) { From 7059ea10ab8a9c5fed4fbbb2283fe2e1606b8d05 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 12 Jun 2026 10:19:42 -0400 Subject: [PATCH 2/2] Copy header_val null terminator in memcpy, drop explicit terminator header_val is null-terminated, so copying add_len + 1 bytes pulls in its NUL and makes the separate ZSTR_VAL(joined)[...] = '\0' assignment redundant. zend_string_alloc already reserves the +1 byte for it. Suggested by @remicollet in review of GH-58. --- php_mailparse_mime.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/php_mailparse_mime.c b/php_mailparse_mime.c index 2381388..6ab0610 100644 --- a/php_mailparse_mime.c +++ b/php_mailparse_mime.c @@ -442,8 +442,7 @@ static int php_mimepart_process_header(php_mimepart *part) memcpy(ZSTR_VAL(joined), ZSTR_VAL(existing), existing_len); memcpy(ZSTR_VAL(joined) + existing_len, ", ", 2); - memcpy(ZSTR_VAL(joined) + existing_len + 2, header_val, add_len); - ZSTR_VAL(joined)[existing_len + 2 + add_len] = '\0'; + memcpy(ZSTR_VAL(joined) + existing_len + 2, header_val, add_len + 1); add_assoc_str(&part->headerhash, header_key, joined); } else { if((zheaderval = zend_hash_find(Z_ARRVAL_P(&part->headerhash), header_zstring)) != NULL) {