From 40c291cf932c31a302fa584bb50b6edb199fb07e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 11 Nov 2025 23:31:27 +0100 Subject: [PATCH 01/14] Fix GH-20444: Dom\XMLDocument::C14N() seems broken compared to DOMDocument::C14N() C14N code expects namespace to be in-tree, but we store namespaces in a different way out-of-tree to avoid reconciliations that break the tree structure in a way unexpected by the DOM spec. In the DOM spec, namespace nodes don't exist; they're regular attributes. To solve this, we temporarily make fake namespace nodes that we later remove. Closes GH-20457. --- NEWS | 2 + ext/dom/node.c | 121 +++++++++++++++++- ext/dom/tests/canonicalization.phpt | 30 +++-- .../modern/xml/canonicalize_unattached.phpt | 20 +++ ext/dom/tests/modern/xml/gh20444.phpt | 43 +++++++ 5 files changed, 204 insertions(+), 12 deletions(-) create mode 100644 ext/dom/tests/modern/xml/canonicalize_unattached.phpt create mode 100644 ext/dom/tests/modern/xml/gh20444.phpt diff --git a/NEWS b/NEWS index 604f81d3078b8..e586e31998f6b 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ PHP NEWS - DOM: . Fixed bug GH-20722 (Null pointer dereference in DOM namespace node cloning via clone on malformed objects). (ndossche) + . Fixed bug GH-20444 (Dom\XMLDocument::C14N() seems broken compared + to DOMDocument::C14N()). (ndossche) - GD: . Fixed bug GH-20622 (imagestring/imagestringup overflow). (David Carlier) diff --git a/ext/dom/node.c b/ext/dom/node.c index 3ec1db841571a..0dd1b959752ef 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -2081,6 +2081,97 @@ PHP_METHOD(DOMNode, lookupNamespaceURI) } /* }}} end dom_node_lookup_namespace_uri */ +static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node) +{ + if (node->type == XML_ELEMENT_NODE) { + for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { + if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) { + xmlNsPtr ns = xmlMalloc(sizeof(*ns)); + if (!ns) { + return; + } + + zval *zv = zend_hash_index_lookup(links, (zend_ulong) node); + if (Z_ISNULL_P(zv)) { + ZVAL_LONG(zv, 1); + } else { + Z_LVAL_P(zv)++; + } + + bool should_free; + xmlChar *attr_value = php_libxml_attr_value(attr, &should_free); + + memset(ns, 0, sizeof(*ns)); + ns->type = XML_LOCAL_NAMESPACE; + ns->href = should_free ? attr_value : xmlStrdup(attr_value); + ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL; + ns->next = node->nsDef; + node->nsDef = ns; + + ns->_private = attr; + if (attr->prev) { + attr->prev = attr->next; + } else { + node->properties = attr->next; + } + if (attr->next) { + attr->next->prev = attr->prev; + } + } + } + + /* The default namespace is handled separately from the other namespaces in C14N. + * The default namespace is explicitly looked up while the other namespaces are + * deduplicated and compared to a list of visible namespaces. */ + if (node->ns && !node->ns->prefix) { + /* Workaround for the behaviour where the xmlSearchNs() call inside c14n.c + * can return the current namespace. */ + zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns); + node->ns = xmlSearchNs(node->doc, node, NULL); + } + } +} + +static void dom_relink_ns_decls(HashTable *links, xmlNodePtr root) +{ + dom_relink_ns_decls_element(links, root); + + xmlNodePtr base = root; + xmlNodePtr node = base->children; + while (node != NULL) { + dom_relink_ns_decls_element(links, node); + node = php_dom_next_in_tree_order(node, base); + } +} + +static void dom_unlink_ns_decls(HashTable *links) +{ + ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(links, zend_ulong h, zval *data) { + if (h & 1) { + xmlNodePtr node = (xmlNodePtr) (h ^ 1); + node->ns = Z_PTR_P(data); + } else { + xmlNodePtr node = (xmlNodePtr) h; + while (Z_LVAL_P(data)-- > 0) { + xmlNsPtr ns = node->nsDef; + node->nsDef = ns->next; + + xmlAttrPtr attr = ns->_private; + if (attr->prev) { + attr->prev->next = attr; + } else { + node->properties = attr; + } + if (attr->next) { + attr->next->prev = attr; + } + + xmlFreeNs(ns); + } + } + } ZEND_HASH_FOREACH_END(); +} + static int dom_canonicalize_node_parent_lookup_cb(void *user_data, xmlNodePtr node, xmlNodePtr parent) { xmlNodePtr root = user_data; @@ -2136,7 +2227,23 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ docp = nodep->doc; - if (! docp) { + HashTable links; + bool modern = php_dom_follow_spec_node(nodep); + if (modern) { + xmlNodePtr root = nodep; + while (root->parent) { + root = root->parent; + } + + if (UNEXPECTED(root->type != XML_DOCUMENT_NODE && root->type != XML_HTML_DOCUMENT_NODE)) { + php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Canonicalization can only happen on nodes attached to a document.", /* strict */ true); + RETURN_THROWS(); + } + + zend_hash_init(&links, 0, NULL, NULL, false); + dom_relink_ns_decls(&links, xmlDocGetRootElement(docp)); + } else if (!docp) { + /* Note: not triggerable with modern DOM */ zend_throw_error(NULL, "Node must be associated with a document"); RETURN_THROWS(); } @@ -2158,12 +2265,12 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ if (!tmp) { /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */ zend_argument_value_error(3 + mode, "must have a \"query\" key"); - RETURN_THROWS(); + goto clean_links; } if (Z_TYPE_P(tmp) != IS_STRING) { /* if mode == 0 then $xpath arg is 3, if mode == 1 then $xpath is 4 */ zend_argument_type_error(3 + mode, "\"query\" option must be a string, %s given", zend_zval_value_name(tmp)); - RETURN_THROWS(); + goto clean_links; } xquery = Z_STRVAL_P(tmp); @@ -2195,7 +2302,7 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ } xmlXPathFreeContext(ctxp); zend_throw_error(NULL, "XPath query did not return a nodeset"); - RETURN_THROWS(); + goto clean_links; } } @@ -2264,6 +2371,12 @@ static void dom_canonicalization(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ RETURN_LONG(bytes); } } + +clean_links: + if (modern) { + dom_unlink_ns_decls(&links); + zend_hash_destroy(&links); + } } /* }}} */ diff --git a/ext/dom/tests/canonicalization.phpt b/ext/dom/tests/canonicalization.phpt index 4183b7cd41edc..fcd9b207bc24b 100644 --- a/ext/dom/tests/canonicalization.phpt +++ b/ext/dom/tests/canonicalization.phpt @@ -21,32 +21,46 @@ $dom = new DOMDocument(); $dom->loadXML($xml); $doc = $dom->documentElement->firstChild; +$newDom = Dom\XMLDocument::createFromString($xml); +$newDoc = $newDom->documentElement->firstChild; +$counter = 0; + +function check($doc, $newDoc, ...$args) { + global $counter; + $counter++; + echo $doc->C14N(...$args)."\n\n"; + if ($doc->C14N(...$args) !== $newDoc->C14N(...$args)) { + var_dump($doc->C14N(...$args), $newDoc->C14N(...$args)); + throw new Error("mismatch: $counter"); + } +} + /* inclusive/without comments first child element of doc element is context. */ -echo $doc->C14N()."\n\n"; +check($doc, $newDoc); /* exclusive/without comments first child element of doc element is context. */ -echo $doc->c14N(TRUE)."\n\n"; +check($doc, $newDoc, TRUE); /* inclusive/with comments first child element of doc element is context. */ -echo $doc->C14N(FALSE, TRUE)."\n\n"; +check($doc, $newDoc, FALSE, TRUE); /* exclusive/with comments first child element of doc element is context. */ -echo $doc->C14N(TRUE, TRUE)."\n\n"; +check($doc, $newDoc, TRUE, TRUE); /* exclusive/without comments using xpath query. */ -echo $doc->c14N(TRUE, FALSE, array('query'=>'(//. | //@* | //namespace::*)'))."\n\n"; +check($doc, $newDoc, TRUE, FALSE, array('query'=>'(//. | //@* | //namespace::*)'))."\n\n"; /* exclusive/without comments first child element of doc element is context. using xpath query with registered namespace. test namespace prefix is also included. */ -echo $doc->c14N(TRUE, FALSE, +check($doc, $newDoc, TRUE, FALSE, array('query'=>'(//a:contain | //a:bar | .//namespace::*)', 'namespaces'=>array('a'=>'http://www.example.com/ns/foo')), - array('test'))."\n\n"; + array('test')); /* exclusive/without comments first child element of doc element is context. test namespace prefix is also included */ -echo $doc->C14N(TRUE, FALSE, NULL, array('test')); +check($doc, $newDoc, TRUE, FALSE, NULL, array('test')); ?> --EXPECT-- diff --git a/ext/dom/tests/modern/xml/canonicalize_unattached.phpt b/ext/dom/tests/modern/xml/canonicalize_unattached.phpt new file mode 100644 index 0000000000000..cec5f1085757b --- /dev/null +++ b/ext/dom/tests/modern/xml/canonicalize_unattached.phpt @@ -0,0 +1,20 @@ +--TEST-- +Canonicalize unattached node should fail +--EXTENSIONS-- +dom +--FILE-- +'); +$child = $d->documentElement->firstChild; +$child->remove(); + +try { + $child->C14N(); +} catch (Dom\DOMException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Canonicalization can only happen on nodes attached to a document. diff --git a/ext/dom/tests/modern/xml/gh20444.phpt b/ext/dom/tests/modern/xml/gh20444.phpt new file mode 100644 index 0000000000000..b3a77e3f13edb --- /dev/null +++ b/ext/dom/tests/modern/xml/gh20444.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-20444 (Dom\XMLDocument::C14N() seems broken compared to DOMDocument::C14N()) +--EXTENSIONS-- +dom +--FILE-- + + + + abc + +EOF; + +$d = \Dom\XMLDocument::createFromString($xml); +var_dump($d->C14N(true)); + +$xml = << + + + + 123 + + +EOF; + +$d = \Dom\XMLDocument::createFromString($xml); +var_dump($d->C14N()); + +?> +--EXPECT-- +string(128) " + + abc +" +string(134) " + + + 123 + +" From 99ed66b49fa59fcb5a7a4f2b460d4155027d44d0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:58:09 +0100 Subject: [PATCH 02/14] Fix GH-20582: Heap Buffer Overflow in iptcembed If you can extend the file between the file size gathering (resulting in a buffer allocation), and reading / writing to the file you can trigger a TOC-TOU where you write out of bounds. To solve this, add extra bound checks and make sure that write actions always fail when going out of bounds. The easiest way to trigger this is via a pipe, which is used in the test, but it should be possible with a regular file and a quick race condition as well. Closes GH-20591. --- NEWS | 1 + ext/standard/iptc.c | 80 +++++++++++++++++---------- ext/standard/tests/image/gh20582.phpt | 52 +++++++++++++++++ 3 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 ext/standard/tests/image/gh20582.phpt diff --git a/NEWS b/NEWS index a2690fa84d3ef..77119e29fcfc4 100644 --- a/NEWS +++ b/NEWS @@ -52,6 +52,7 @@ PHP NEWS - Standard: . Fix error check for proc_open() command. (ndossche) + . Fixed bug GH-20582 (Heap Buffer Overflow in iptcembed). (ndossche) 18 Dec 2025, PHP 8.3.29 diff --git a/ext/standard/iptc.c b/ext/standard/iptc.c index 44dd33bab10ac..0f46ecd19bc7c 100644 --- a/ext/standard/iptc.c +++ b/ext/standard/iptc.c @@ -73,19 +73,24 @@ #define M_APP15 0xef /* {{{ php_iptc_put1 */ -static int php_iptc_put1(FILE *fp, int spool, unsigned char c, unsigned char **spoolbuf) +static int php_iptc_put1(FILE *fp, int spool, unsigned char c, unsigned char **spoolbuf, const unsigned char *spoolbuf_end) { if (spool > 0) PUTC(c); - if (spoolbuf) *(*spoolbuf)++ = c; + if (spoolbuf) { + if (UNEXPECTED(*spoolbuf >= spoolbuf_end)) { + return EOF; + } + *(*spoolbuf)++ = c; + } return c; } /* }}} */ /* {{{ php_iptc_get1 */ -static int php_iptc_get1(FILE *fp, int spool, unsigned char **spoolbuf) +static int php_iptc_get1(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end) { int c; char cc; @@ -99,66 +104,71 @@ static int php_iptc_get1(FILE *fp, int spool, unsigned char **spoolbuf) PUTC(cc); } - if (spoolbuf) *(*spoolbuf)++ = c; + if (spoolbuf) { + if (UNEXPECTED(*spoolbuf >= spoolbuf_end)) { + return EOF; + } + *(*spoolbuf)++ = c; + } return c; } /* }}} */ /* {{{ php_iptc_read_remaining */ -static int php_iptc_read_remaining(FILE *fp, int spool, unsigned char **spoolbuf) +static int php_iptc_read_remaining(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end) { - while (php_iptc_get1(fp, spool, spoolbuf) != EOF) continue; + while (php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end) != EOF) continue; return M_EOI; } /* }}} */ /* {{{ php_iptc_skip_variable */ -static int php_iptc_skip_variable(FILE *fp, int spool, unsigned char **spoolbuf) +static int php_iptc_skip_variable(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end) { unsigned int length; int c1, c2; - if ((c1 = php_iptc_get1(fp, spool, spoolbuf)) == EOF) return M_EOI; + if ((c1 = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end)) == EOF) return M_EOI; - if ((c2 = php_iptc_get1(fp, spool, spoolbuf)) == EOF) return M_EOI; + if ((c2 = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end)) == EOF) return M_EOI; length = (((unsigned char) c1) << 8) + ((unsigned char) c2); length -= 2; while (length--) - if (php_iptc_get1(fp, spool, spoolbuf) == EOF) return M_EOI; + if (php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end) == EOF) return M_EOI; return 0; } /* }}} */ /* {{{ php_iptc_next_marker */ -static int php_iptc_next_marker(FILE *fp, int spool, unsigned char **spoolbuf) +static int php_iptc_next_marker(FILE *fp, int spool, unsigned char **spoolbuf, const unsigned char *spoolbuf_end) { int c; /* skip unimportant stuff */ - c = php_iptc_get1(fp, spool, spoolbuf); + c = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end); if (c == EOF) return M_EOI; while (c != 0xff) { - if ((c = php_iptc_get1(fp, spool, spoolbuf)) == EOF) + if ((c = php_iptc_get1(fp, spool, spoolbuf, spoolbuf_end)) == EOF) return M_EOI; /* we hit EOF */ } /* get marker byte, swallowing possible padding */ do { - c = php_iptc_get1(fp, 0, 0); + c = php_iptc_get1(fp, 0, 0, NULL); if (c == EOF) return M_EOI; /* we hit EOF */ else if (c == 0xff) - php_iptc_put1(fp, spool, (unsigned char)c, spoolbuf); + php_iptc_put1(fp, spool, (unsigned char)c, spoolbuf, spoolbuf_end); } while (c == 0xff); return (unsigned int) c; @@ -178,6 +188,7 @@ PHP_FUNCTION(iptcembed) size_t inx; zend_string *spoolbuf = NULL; unsigned char *poi = NULL; + unsigned char *spoolbuf_end = NULL; zend_stat_t sb = {0}; bool written = 0; @@ -210,10 +221,11 @@ PHP_FUNCTION(iptcembed) spoolbuf = zend_string_safe_alloc(1, iptcdata_len + sizeof(psheader) + 1024 + 1, sb.st_size, 0); poi = (unsigned char*)ZSTR_VAL(spoolbuf); + spoolbuf_end = poi + ZSTR_LEN(spoolbuf); memset(poi, 0, iptcdata_len + sizeof(psheader) + sb.st_size + 1024 + 1); } - if (php_iptc_get1(fp, spool, poi?&poi:0) != 0xFF) { + if (php_iptc_get1(fp, spool, poi?&poi:0, spoolbuf_end) != 0xFF) { fclose(fp); if (spoolbuf) { zend_string_efree(spoolbuf); @@ -221,7 +233,8 @@ PHP_FUNCTION(iptcembed) RETURN_FALSE; } - if (php_iptc_get1(fp, spool, poi?&poi:0) != 0xD8) { + if (php_iptc_get1(fp, spool, poi?&poi:0, spoolbuf_end) != 0xD8) { +err: fclose(fp); if (spoolbuf) { zend_string_efree(spoolbuf); @@ -230,20 +243,22 @@ PHP_FUNCTION(iptcembed) } while (!done) { - marker = php_iptc_next_marker(fp, spool, poi?&poi:0); + marker = php_iptc_next_marker(fp, spool, poi?&poi:0, spoolbuf_end); if (marker == M_EOI) { /* EOF */ break; } else if (marker != M_APP13) { - php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0); + if (php_iptc_put1(fp, spool, (unsigned char)marker, poi?&poi:0, spoolbuf_end) < 0) { + goto err; + } } switch (marker) { case M_APP13: /* we are going to write a new APP13 marker, so don't output the old one */ - php_iptc_skip_variable(fp, 0, 0); + php_iptc_skip_variable(fp, 0, 0, spoolbuf_end); fgetc(fp); /* skip already copied 0xFF byte */ - php_iptc_read_remaining(fp, spool, poi?&poi:0); + php_iptc_read_remaining(fp, spool, poi?&poi:0, spoolbuf_end); done = 1; break; @@ -256,7 +271,7 @@ PHP_FUNCTION(iptcembed) } written = 1; - php_iptc_skip_variable(fp, spool, poi?&poi:0); + php_iptc_skip_variable(fp, spool, poi?&poi:0, spoolbuf_end); if (iptcdata_len & 1) { iptcdata_len++; /* make the length even */ @@ -266,25 +281,33 @@ PHP_FUNCTION(iptcembed) psheader[ 3 ] = (iptcdata_len+28)&0xff; for (inx = 0; inx < 28; inx++) { - php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0); + if (php_iptc_put1(fp, spool, psheader[inx], poi?&poi:0, spoolbuf_end) < 0) { + goto err; + } } - php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0); - php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0); + if (php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len>>8), poi?&poi:0, spoolbuf_end) < 0) { + goto err; + } + if (php_iptc_put1(fp, spool, (unsigned char)(iptcdata_len&0xff), poi?&poi:0, spoolbuf_end) < 0) { + goto err; + } for (inx = 0; inx < iptcdata_len; inx++) { - php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0); + if (php_iptc_put1(fp, spool, iptcdata[inx], poi?&poi:0, spoolbuf_end) < 0) { + goto err; + } } break; case M_SOS: /* we hit data, no more marker-inserting can be done! */ - php_iptc_read_remaining(fp, spool, poi?&poi:0); + php_iptc_read_remaining(fp, spool, poi?&poi:0, spoolbuf_end); done = 1; break; default: - php_iptc_skip_variable(fp, spool, poi?&poi:0); + php_iptc_skip_variable(fp, spool, poi?&poi:0, spoolbuf_end); break; } } @@ -292,6 +315,7 @@ PHP_FUNCTION(iptcembed) fclose(fp); if (spool < 2) { + *poi = '\0'; spoolbuf = zend_string_truncate(spoolbuf, poi - (unsigned char*)ZSTR_VAL(spoolbuf), 0); RETURN_NEW_STR(spoolbuf); } else { diff --git a/ext/standard/tests/image/gh20582.phpt b/ext/standard/tests/image/gh20582.phpt new file mode 100644 index 0000000000000..63561534b2fd0 --- /dev/null +++ b/ext/standard/tests/image/gh20582.phpt @@ -0,0 +1,52 @@ +--TEST-- +GH-20582 (Heap Buffer Overflow in iptcembed) +--CREDITS-- +Nikita Sveshnikov (Positive Technologies) +ndossche +--SKIPIF-- + +--FILE-- + STDIN, + 1 => STDOUT, + 2 => STDOUT, +); +$pipes = []; +$proc = proc_open([PHP_BINARY, '-n', '-r', "var_dump(iptcembed('A', '$pipe'));"], $descriptorspec, $pipes); + +// Blocks until a reader opens it +$fp = fopen($pipe, 'wb') or die("Failed to open FIFO"); + +// Write header +$data = "\xFF\xD8"; // SOI marker +$data .= "\xFF\xE0\x00\x10"; // APP0 marker (JFIF) +$data .= "JFIF" . str_repeat("\x00", 9); +$data .= "\xFF\xDA\x00\x08"; // SOS marker +$data .= str_repeat("\x00", 6); +fwrite($fp, $data); + +// Write garbage +fwrite($fp, str_repeat("A", 5120)); + +fclose($fp); + +?> +--CLEAN-- + +--EXPECTF-- +string(1055) "ÿØÿà%0JFIF%0%0%0%0%0%0%0%0%0ÿÿí%0Photoshop 3.0%08BIM%0%0%0%0%0A%0Ú%0%0%0%0%0%0%0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" From 7e8c636b3084c6eb545bb8abfbefce5940d90178 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:14:31 +0100 Subject: [PATCH 03/14] win32/sendmail.c: mark error messages array as const --- win32/sendmail.c | 4 ++-- win32/sendmail.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index 80dff3f390772..fbb2ae0298971 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -63,7 +63,7 @@ char seps[] = " ,\t\n"; char *php_mailer = "PHP 7 WIN32"; /* Error messages */ -static char *ErrorMessages[] = +static const char *ErrorMessages[] = { "Success", /* 0 */ "Bad arguments from form", /* 1 */ @@ -328,7 +328,7 @@ PHPAPI void TSMClose(void) // Author/Date: jcar 20/9/96 // History: //********************************************************************* -PHPAPI char *GetSMErrorText(int index) +PHPAPI const char *GetSMErrorText(int index) { if (MIN_ERROR_INDEX <= index && index < MAX_ERROR_INDEX) { return (ErrorMessages[index]); diff --git a/win32/sendmail.h b/win32/sendmail.h index e6096f789eb78..6cfdc5706f1e6 100644 --- a/win32/sendmail.h +++ b/win32/sendmail.h @@ -36,5 +36,5 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, const char *headers, const char *Subject, const char *mailTo, const char *data, char *mailCc, char *mailBcc, char *mailRPath); PHPAPI void TSMClose(void); -PHPAPI char *GetSMErrorText(int index); +PHPAPI const char *GetSMErrorText(int index); #endif /* sendmail_h */ From 4dad723c413f6ec4e156c51a707285066f71bb5d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:23:52 +0100 Subject: [PATCH 04/14] win32/sendmail.c/Post(): refactor function Change return type to bool as it only ever returns two values --- win32/sendmail.c | 64 ++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index fbb2ae0298971..719175bf74b09 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -115,7 +115,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * const char *headers, char *headers_lc, char **error_message); static int MailConnect(); static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders); -static int Post(LPCSTR msg); +static bool Post(LPCSTR msg); static int Ack(char **server_response); static unsigned long GetAddr(LPSTR szHost); static int FormatEmailAddress(char* Buf, char* EmailAddress, char* FormatString); @@ -421,14 +421,14 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * /* in the beginning of the dialog */ /* attempt reconnect if the first Post fail */ - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { + if (!Post(PW32G(mail_buffer))) { int err = MailConnect(); if (0 != err) { return (FAILED_TO_SEND); } - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { - return (res); + if (!Post(PW32G(mail_buffer))) { + return (FAILED_TO_SEND); } } if ((res = Ack(&server_response)) != SUCCESS) { @@ -438,8 +438,8 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * SMTP_SKIP_SPACE(RPath); FormatEmailAddress(PW32G(mail_buffer), RPath, "MAIL FROM:<%s>\r\n"); - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { - return (res); + if (!Post(PW32G(mail_buffer))) { + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -453,9 +453,9 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * { SMTP_SKIP_SPACE(token); FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { + if (!Post(PW32G(mail_buffer))) { efree(tempMailTo); - return (res); + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -474,9 +474,9 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * { SMTP_SKIP_SPACE(token); FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { + if (!Post(PW32G(mail_buffer))) { efree(tempMailTo); - return (res); + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -514,9 +514,9 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * { SMTP_SKIP_SPACE(token); FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { + if (!Post(PW32G(mail_buffer))) { efree(tempMailTo); - return (res); + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -539,9 +539,9 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * { SMTP_SKIP_SPACE(token); FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { + if (!Post(PW32G(mail_buffer))) { efree(tempMailTo); - return (res); + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -587,9 +587,9 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * { SMTP_SKIP_SPACE(token); FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if ((res = Post(PW32G(mail_buffer))) != SUCCESS) { + if (!Post(PW32G(mail_buffer))) { efree(tempMailTo); - return (res); + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -625,11 +625,11 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * stripped_header = estrndup(headers, strlen(headers)); } - if ((res = Post("DATA\r\n")) != SUCCESS) { + if (!Post("DATA\r\n")) { if (stripped_header) { efree(stripped_header); } - return (res); + return (FAILED_TO_SEND); } if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); @@ -670,24 +670,24 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * e2 = p + 1024; c = *e2; *e2 = '\0'; - if ((res = Post(p)) != SUCCESS) { + if (!Post(p)) { zend_string_free(data_cln); - return(res); + return(FAILED_TO_SEND); } *e2 = c; p = e2; } - if ((res = Post(p)) != SUCCESS) { + if (!Post(p)) { zend_string_free(data_cln); - return(res); + return(FAILED_TO_SEND); } } zend_string_free(data_cln); /*send termination dot */ - if ((res = Post("\r\n.\r\n")) != SUCCESS) - return (res); + if (!Post("\r\n.\r\n")) + return (FAILED_TO_SEND); if ((res = Ack(&server_response)) != SUCCESS) { SMTP_ERROR_RESPONSE(server_response); return (res); @@ -771,14 +771,14 @@ static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char if (headers_lc) { efree(headers_lc); } - if ((res = Post(header_buffer)) != SUCCESS) { + if (!Post(header_buffer)) { efree(header_buffer); - return (res); + return (FAILED_TO_SEND); } efree(header_buffer); - if ((res = Post("\r\n")) != SUCCESS) { - return (res); + if (!Post("\r\n")) { + return (FAILED_TO_SEND); } return (SUCCESS); @@ -896,7 +896,7 @@ return 0; // Author/Date: jcar 20/9/96 // History: //********************************************************************* -static int Post(LPCSTR msg) +static bool Post(LPCSTR msg) { int len = (int)strlen(msg); int slen; @@ -905,16 +905,16 @@ static int Post(LPCSTR msg) #if SENDMAIL_DEBUG if (msg) printf("POST: '%s'\n", msg); - return (SUCCESS); + return true; #endif while (len > 0) { if ((slen = send(PW32G(mail_socket), msg + index, len, 0)) < 1) - return (FAILED_TO_SEND); + return false; len -= slen; index += slen; } - return (SUCCESS); + return true; } From 6f28370d2fdf9f4e20e22a2efc61059b2e66d1db Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:35:15 +0100 Subject: [PATCH 05/14] win32/sendmail.c: remove mailRPath parameter that is always NULL --- ext/standard/mail.c | 2 +- win32/sendmail.c | 6 ++---- win32/sendmail.h | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ext/standard/mail.c b/ext/standard/mail.c index b631d12e4c7e2..e1d514f9e0b40 100644 --- a/ext/standard/mail.c +++ b/ext/standard/mail.c @@ -543,7 +543,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c char *tsm_errmsg = NULL; /* handle old style win smtp sending */ - if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL, NULL, NULL) == FAILURE) { + if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL, NULL) == FAILURE) { if (tsm_errmsg) { php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg); efree(tsm_errmsg); diff --git a/win32/sendmail.c b/win32/sendmail.c index 719175bf74b09..f336cabae774b 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -186,7 +186,7 @@ static zend_string *php_win32_mail_trim_header(const char *header) //********************************************************************* PHPAPI int TSendMail(const char *host, int *error, char **error_message, const char *headers, const char *Subject, const char *mailTo, const char *data, - char *mailCc, char *mailBcc, char *mailRPath) + char *mailCc, char *mailBcc) { int ret; char *RPath = NULL; @@ -216,9 +216,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, } /* Fall back to sendmail_from php.ini setting */ - if (mailRPath && *mailRPath) { - RPath = estrdup(mailRPath); - } else if (INI_STR("sendmail_from")) { + if (INI_STR("sendmail_from")) { RPath = estrdup(INI_STR("sendmail_from")); } else if (headers_lc) { int found = 0; diff --git a/win32/sendmail.h b/win32/sendmail.h index 6cfdc5706f1e6..cf38d1dc281a2 100644 --- a/win32/sendmail.h +++ b/win32/sendmail.h @@ -34,7 +34,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, const char *headers, const char *Subject, const char *mailTo, const char *data, - char *mailCc, char *mailBcc, char *mailRPath); + char *mailCc, char *mailBcc); PHPAPI void TSMClose(void); PHPAPI const char *GetSMErrorText(int index); #endif /* sendmail_h */ From 0e19bc1bfd17c67a2e1fdd1a27cd5318740dfeac Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:36:55 +0100 Subject: [PATCH 06/14] win32/sendmail.c: remove mailBbc parameter that is always NULL --- ext/standard/mail.c | 2 +- win32/sendmail.c | 31 +++++-------------------------- win32/sendmail.h | 2 +- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/ext/standard/mail.c b/ext/standard/mail.c index e1d514f9e0b40..42dc40b90d828 100644 --- a/ext/standard/mail.c +++ b/ext/standard/mail.c @@ -543,7 +543,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c char *tsm_errmsg = NULL; /* handle old style win smtp sending */ - if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL, NULL) == FAILURE) { + if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL) == FAILURE) { if (tsm_errmsg) { php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg); efree(tsm_errmsg); diff --git a/win32/sendmail.c b/win32/sendmail.c index f336cabae774b..6d6866e55223a 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -111,7 +111,7 @@ static const char *ErrorMessages[] = #define PHP_WIN32_MAIL_DOT_PATTERN "\n." #define PHP_WIN32_MAIL_DOT_REPLACE "\n.." -static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, char *mailBcc, const char *data, +static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, const char *data, const char *headers, char *headers_lc, char **error_message); static int MailConnect(); static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders); @@ -186,7 +186,7 @@ static zend_string *php_win32_mail_trim_header(const char *header) //********************************************************************* PHPAPI int TSendMail(const char *host, int *error, char **error_message, const char *headers, const char *Subject, const char *mailTo, const char *data, - char *mailCc, char *mailBcc) + char *mailCc) { int ret; char *RPath = NULL; @@ -279,7 +279,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, PW32G(mail_host), !INI_INT("smtp_port") ? 25 : INI_INT("smtp_port")); return FAILURE; } else { - ret = SendText(RPath, Subject, mailTo, mailCc, mailBcc, data, headers ? ZSTR_VAL(headers_trim) : NULL, headers ? ZSTR_VAL(headers_lc) : NULL, error_message); + ret = SendText(RPath, Subject, mailTo, mailCc, data, headers ? ZSTR_VAL(headers_trim) : NULL, headers ? ZSTR_VAL(headers_lc) : NULL, error_message); TSMClose(); if (RPath) { efree(RPath); @@ -387,7 +387,7 @@ static char *find_address(char *list, char **state) // Author/Date: jcar 20/9/96 // History: //********************************************************************* -static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, char *mailBcc, const char *data, +static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, const char *data, const char *headers, char *headers_lc, char **error_message) { int res; @@ -529,28 +529,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * /* Send mail to all Bcc rcpt's This is basically a rip of the Cc code above. Just don't forget to remove the Bcc: from the header afterwards. */ - if (mailBcc && *mailBcc) { - tempMailTo = estrdup(mailBcc); - /* Send mail to all rcpt's */ - token = find_address(tempMailTo, &token_state); - while (token != NULL) - { - SMTP_SKIP_SPACE(token); - FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if (!Post(PW32G(mail_buffer))) { - efree(tempMailTo); - return (FAILED_TO_SEND); - } - if ((res = Ack(&server_response)) != SUCCESS) { - SMTP_ERROR_RESPONSE(server_response); - efree(tempMailTo); - return (res); - } - token = find_address(NULL, &token_state); - } - efree(tempMailTo); - } - else if (headers) { + if (headers) { if ((pos1 = strstr(headers_lc, "bcc:")) && (pos1 == headers_lc || *(pos1-1) == '\n')) { /* Real offset is memaddress from the original headers + difference of * string found in the lowercase headers + 4 characters to jump over diff --git a/win32/sendmail.h b/win32/sendmail.h index cf38d1dc281a2..7eeb3d1825e32 100644 --- a/win32/sendmail.h +++ b/win32/sendmail.h @@ -34,7 +34,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, const char *headers, const char *Subject, const char *mailTo, const char *data, - char *mailCc, char *mailBcc); + char *mailCc); PHPAPI void TSMClose(void); PHPAPI const char *GetSMErrorText(int index); #endif /* sendmail_h */ From 4431aa94b7fa050dca724467514306f7257ae53c Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:39:11 +0100 Subject: [PATCH 07/14] win32/sendmail.c: remove mailCc parameter that is always NULL --- ext/standard/mail.c | 2 +- win32/sendmail.c | 32 +++++--------------------------- win32/sendmail.h | 3 +-- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/ext/standard/mail.c b/ext/standard/mail.c index 42dc40b90d828..3ffb7d05bb4b0 100644 --- a/ext/standard/mail.c +++ b/ext/standard/mail.c @@ -543,7 +543,7 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c char *tsm_errmsg = NULL; /* handle old style win smtp sending */ - if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message, NULL) == FAILURE) { + if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, hdr, subject, to, message) == FAILURE) { if (tsm_errmsg) { php_error_docref(NULL, E_WARNING, "%s", tsm_errmsg); efree(tsm_errmsg); diff --git a/win32/sendmail.c b/win32/sendmail.c index 6d6866e55223a..fef37e1b897b2 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -111,7 +111,7 @@ static const char *ErrorMessages[] = #define PHP_WIN32_MAIL_DOT_PATTERN "\n." #define PHP_WIN32_MAIL_DOT_REPLACE "\n.." -static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, const char *data, +static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, const char *headers, char *headers_lc, char **error_message); static int MailConnect(); static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders); @@ -185,8 +185,7 @@ static zend_string *php_win32_mail_trim_header(const char *header) // See SendText() for additional args! //********************************************************************* PHPAPI int TSendMail(const char *host, int *error, char **error_message, - const char *headers, const char *Subject, const char *mailTo, const char *data, - char *mailCc) + const char *headers, const char *Subject, const char *mailTo, const char *data) { int ret; char *RPath = NULL; @@ -279,7 +278,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, PW32G(mail_host), !INI_INT("smtp_port") ? 25 : INI_INT("smtp_port")); return FAILURE; } else { - ret = SendText(RPath, Subject, mailTo, mailCc, data, headers ? ZSTR_VAL(headers_trim) : NULL, headers ? ZSTR_VAL(headers_lc) : NULL, error_message); + ret = SendText(RPath, Subject, mailTo, data, headers ? ZSTR_VAL(headers_trim) : NULL, headers ? ZSTR_VAL(headers_lc) : NULL, error_message); TSMClose(); if (RPath) { efree(RPath); @@ -387,7 +386,7 @@ static char *find_address(char *list, char **state) // Author/Date: jcar 20/9/96 // History: //********************************************************************* -static int SendText(char *RPath, const char *Subject, const char *mailTo, char *mailCc, const char *data, +static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, const char *headers, char *headers_lc, char **error_message) { int res; @@ -464,29 +463,8 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, char * } efree(tempMailTo); - if (mailCc && *mailCc) { - tempMailTo = estrdup(mailCc); - /* Send mail to all rcpt's */ - token = find_address(tempMailTo, &token_state); - while (token != NULL) - { - SMTP_SKIP_SPACE(token); - FormatEmailAddress(PW32G(mail_buffer), token, "RCPT TO:<%s>\r\n"); - if (!Post(PW32G(mail_buffer))) { - efree(tempMailTo); - return (FAILED_TO_SEND); - } - if ((res = Ack(&server_response)) != SUCCESS) { - SMTP_ERROR_RESPONSE(server_response); - efree(tempMailTo); - return (res); - } - token = find_address(NULL, &token_state); - } - efree(tempMailTo); - } /* Send mail to all Cc rcpt's */ - else if (headers && (pos1 = strstr(headers_lc, "cc:")) && ((pos1 == headers_lc) || (*(pos1-1) == '\n'))) { + if (headers && (pos1 = strstr(headers_lc, "cc:")) && ((pos1 == headers_lc) || (*(pos1-1) == '\n'))) { /* Real offset is memaddress from the original headers + difference of * string found in the lowercase headers + 3 characters to jump over * the cc: */ diff --git a/win32/sendmail.h b/win32/sendmail.h index 7eeb3d1825e32..9999f9a6dfa58 100644 --- a/win32/sendmail.h +++ b/win32/sendmail.h @@ -33,8 +33,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, - const char *headers, const char *Subject, const char *mailTo, const char *data, - char *mailCc); + const char *headers, const char *Subject, const char *mailTo, const char *data); PHPAPI void TSMClose(void); PHPAPI const char *GetSMErrorText(int index); #endif /* sendmail_h */ From 84e63bfd9b62a812b26479fa824046e3afb225e4 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:43:59 +0100 Subject: [PATCH 08/14] win32/sendmail.c/php_win32_mail_trim_header(): use ZSTR_INIT_LITERAL() --- win32/sendmail.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index fef37e1b897b2..14ad6d6a6c422 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -140,8 +140,8 @@ static zend_string *php_win32_mail_trim_header(const char *header) return NULL; } - replace = zend_string_init(PHP_WIN32_MAIL_UNIFY_REPLACE, strlen(PHP_WIN32_MAIL_UNIFY_REPLACE), 0); - regex = zend_string_init(PHP_WIN32_MAIL_UNIFY_PATTERN, sizeof(PHP_WIN32_MAIL_UNIFY_PATTERN)-1, 0); + replace = ZSTR_INIT_LITERAL(PHP_WIN32_MAIL_UNIFY_REPLACE, false); + regex = ZSTR_INIT_LITERAL(PHP_WIN32_MAIL_UNIFY_PATTERN, false); result = php_pcre_replace(regex, NULL, header, strlen(header), @@ -149,24 +149,24 @@ static zend_string *php_win32_mail_trim_header(const char *header) -1, NULL); - zend_string_release_ex(replace, 0); - zend_string_release_ex(regex, 0); + zend_string_release_ex(replace, false); + zend_string_release_ex(regex, false); if (NULL == result) { return NULL; } - replace = zend_string_init(PHP_WIN32_MAIL_RMVDBL_PATTERN, strlen(PHP_WIN32_MAIL_RMVDBL_PATTERN), 0); - regex = zend_string_init(PHP_WIN32_MAIL_RMVDBL_PATTERN, sizeof(PHP_WIN32_MAIL_RMVDBL_PATTERN)-1, 0); + replace = ZSTR_INIT_LITERAL(PHP_WIN32_MAIL_RMVDBL_PATTERN, false); + regex = ZSTR_INIT_LITERAL(PHP_WIN32_MAIL_RMVDBL_PATTERN, false); result2 = php_pcre_replace(regex, result, ZSTR_VAL(result), ZSTR_LEN(result), replace, -1, NULL); - zend_string_release_ex(replace, 0); - zend_string_release_ex(regex, 0); - zend_string_release_ex(result, 0); + zend_string_release_ex(replace, false); + zend_string_release_ex(regex, false); + zend_string_release_ex(result, false); return result2; } From f1a8944fcde384ce0b5842b6869535306f889ea5 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 24 Dec 2025 18:50:21 +0100 Subject: [PATCH 09/14] win32/sendmail.c/SendText(): use zend_string* for headers{_lc} parameters This prevents some strlen() reconputations --- win32/sendmail.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index 14ad6d6a6c422..3a32f3e4f0df8 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -112,7 +112,7 @@ static const char *ErrorMessages[] = #define PHP_WIN32_MAIL_DOT_REPLACE "\n.." static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, - const char *headers, char *headers_lc, char **error_message); + const zend_string *headers, zend_string *headers_lc, char **error_message); static int MailConnect(); static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders); static bool Post(LPCSTR msg); @@ -278,7 +278,7 @@ PHPAPI int TSendMail(const char *host, int *error, char **error_message, PW32G(mail_host), !INI_INT("smtp_port") ? 25 : INI_INT("smtp_port")); return FAILURE; } else { - ret = SendText(RPath, Subject, mailTo, data, headers ? ZSTR_VAL(headers_trim) : NULL, headers ? ZSTR_VAL(headers_lc) : NULL, error_message); + ret = SendText(RPath, Subject, mailTo, data, headers_trim, headers_lc, error_message); TSMClose(); if (RPath) { efree(RPath); @@ -387,7 +387,7 @@ static char *find_address(char *list, char **state) // History: //********************************************************************* static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, - const char *headers, char *headers_lc, char **error_message) + const zend_string *headers, zend_string *headers_lc, char **error_message) { int res; char *p; @@ -464,11 +464,11 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const efree(tempMailTo); /* Send mail to all Cc rcpt's */ - if (headers && (pos1 = strstr(headers_lc, "cc:")) && ((pos1 == headers_lc) || (*(pos1-1) == '\n'))) { + if (headers && (pos1 = strstr(ZSTR_VAL(headers_lc), "cc:")) && ((pos1 == ZSTR_VAL(headers_lc)) || (*(pos1-1) == '\n'))) { /* Real offset is memaddress from the original headers + difference of * string found in the lowercase headers + 3 characters to jump over * the cc: */ - pos1 = headers + (pos1 - headers_lc) + 3; + pos1 = ZSTR_VAL(headers) + (pos1 - ZSTR_VAL(headers_lc)) + 3; if (NULL == (pos2 = strstr(pos1, "\r\n"))) { tempMailTo = estrndup(pos1, strlen(pos1)); } else { @@ -508,11 +508,11 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const This is basically a rip of the Cc code above. Just don't forget to remove the Bcc: from the header afterwards. */ if (headers) { - if ((pos1 = strstr(headers_lc, "bcc:")) && (pos1 == headers_lc || *(pos1-1) == '\n')) { + if ((pos1 = strstr(ZSTR_VAL(headers_lc), "bcc:")) && (pos1 == ZSTR_VAL(headers_lc) || *(pos1-1) == '\n')) { /* Real offset is memaddress from the original headers + difference of * string found in the lowercase headers + 4 characters to jump over * the bcc: */ - pos1 = headers + (pos1 - headers_lc) + 4; + pos1 = ZSTR_VAL(headers) + (pos1 - ZSTR_VAL(headers_lc)) + 4; if (NULL == (pos2 = strstr(pos1, "\r\n"))) { tempMailTo = estrndup(pos1, strlen(pos1)); /* Later, when we remove the Bcc: out of the @@ -557,19 +557,19 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const /* Now that we've identified that we've a Bcc list, remove it from the current header. */ - stripped_header = ecalloc(1, strlen(headers)); + stripped_header = ecalloc(1, ZSTR_LEN(headers)); /* headers = point to string start of header pos1 = pointer IN headers where the Bcc starts '4' = Length of the characters 'bcc:' Because we've added +4 above for parsing the Emails we've to subtract them here. */ - memcpy(stripped_header, headers, pos1 - headers - 4); + memcpy(stripped_header, ZSTR_VAL(headers), pos1 - ZSTR_VAL(headers) - 4); if (pos1 != pos2) { /* if pos1 != pos2 , pos2 points to the rest of the headers. Since pos1 != pos2 if "\r\n" was found, we know those characters are there and so we jump over them (else we would generate a new header which would look like "\r\n\r\n". */ - memcpy(stripped_header + (pos1 - headers - 4), pos2 + 2, strlen(pos2) - 2); + memcpy(stripped_header + (pos1 - ZSTR_VAL(headers) - 4), pos2 + 2, strlen(pos2) - 2); } } } @@ -577,7 +577,7 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const /* Simplify the code that we create a copy of stripped_header no matter if we actually strip something or not. So we've a single efree() later. */ if (headers && !stripped_header) { - stripped_header = estrndup(headers, strlen(headers)); + stripped_header = estrndup(ZSTR_VAL(headers), ZSTR_LEN(headers)); } if (!Post("DATA\r\n")) { From 6bd9c555eed1a1e5963a0b5144f83ef3878f9d2d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 26 Dec 2025 00:58:51 +0100 Subject: [PATCH 10/14] win32/sendmail.c/SendText(): move string duplication code to a more logical place --- win32/sendmail.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index 3a32f3e4f0df8..2b72ae328e0ac 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -571,15 +571,13 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const which would look like "\r\n\r\n". */ memcpy(stripped_header + (pos1 - ZSTR_VAL(headers) - 4), pos2 + 2, strlen(pos2) - 2); } + } else { + /* Simplify the code that we create a copy of stripped_header no matter if + we actually strip something or not. So we've a single efree() later. */ + stripped_header = estrndup(ZSTR_VAL(headers), ZSTR_LEN(headers)); } } - /* Simplify the code that we create a copy of stripped_header no matter if - we actually strip something or not. So we've a single efree() later. */ - if (headers && !stripped_header) { - stripped_header = estrndup(ZSTR_VAL(headers), ZSTR_LEN(headers)); - } - if (!Post("DATA\r\n")) { if (stripped_header) { efree(stripped_header); From 004c630106cc36049b5b3e5751b654fa7656a8a5 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 26 Dec 2025 01:03:43 +0100 Subject: [PATCH 11/14] win32/sendmail.c/addToHeader(): voidify function It always returns 1 and thus a bunch of error handling code is useless --- win32/sendmail.c | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index 2b72ae328e0ac..ea8deadeba549 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -649,13 +649,12 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const return (SUCCESS); } -static int addToHeader(char **header_buffer, const char *specifier, const char *string) +static void addToHeader(char **header_buffer, const char *specifier, const char *string) { size_t header_buffer_size = strlen(*header_buffer); size_t total_size = header_buffer_size + strlen(specifier) + strlen(string) + 1; *header_buffer = erealloc(*header_buffer, total_size); snprintf(*header_buffer + header_buffer_size, total_size - header_buffer_size, specifier, string); - return 1; } //********************************************************************* @@ -701,24 +700,16 @@ static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char } if (!headers_lc || !strstr(headers_lc, "from:")) { - if (!addToHeader(&header_buffer, "From: %s\r\n", RPath)) { - goto PostHeader_outofmem; - } - } - if (!addToHeader(&header_buffer, "Subject: %s\r\n", Subject)) { - goto PostHeader_outofmem; + addToHeader(&header_buffer, "From: %s\r\n", RPath); } + addToHeader(&header_buffer, "Subject: %s\r\n", Subject); /* Only add the To: field from the $to parameter if isn't in the custom headers */ if ((headers_lc && (!strstr(headers_lc, "\r\nto:") && (strncmp(headers_lc, "to:", 3) != 0))) || !headers_lc) { - if (!addToHeader(&header_buffer, "To: %s\r\n", mailTo)) { - goto PostHeader_outofmem; - } + addToHeader(&header_buffer, "To: %s\r\n", mailTo); } if (xheaders) { - if (!addToHeader(&header_buffer, "%s\r\n", xheaders)) { - goto PostHeader_outofmem; - } + addToHeader(&header_buffer, "%s\r\n", xheaders); } if (headers_lc) { @@ -735,12 +726,6 @@ static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char } return (SUCCESS); - -PostHeader_outofmem: - if (headers_lc) { - efree(headers_lc); - } - return OUT_OF_MEMORY; } From 8aa64bb976583a67556c1605b3749e8cdfd6da63 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 26 Dec 2025 01:21:56 +0100 Subject: [PATCH 12/14] win32/sendmail.c/SendText(): move posting of DATA prior to stripped header computation Removes some error handling and work if it fails --- win32/sendmail.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index ea8deadeba549..f8b22897a2c6a 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -504,6 +504,14 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const efree(tempMailTo); } + if (!Post("DATA\r\n")) { + return (FAILED_TO_SEND); + } + if ((res = Ack(&server_response)) != SUCCESS) { + SMTP_ERROR_RESPONSE(server_response); + return (res); + } + /* Send mail to all Bcc rcpt's This is basically a rip of the Cc code above. Just don't forget to remove the Bcc: from the header afterwards. */ @@ -578,20 +586,6 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const } } - if (!Post("DATA\r\n")) { - if (stripped_header) { - efree(stripped_header); - } - return (FAILED_TO_SEND); - } - if ((res = Ack(&server_response)) != SUCCESS) { - SMTP_ERROR_RESPONSE(server_response); - if (stripped_header) { - efree(stripped_header); - } - return (res); - } - /* send message header */ if (Subject == NULL) { res = PostHeader(RPath, "No Subject", mailTo, stripped_header); From fc276aee681cf2405b0fcbc33039586555476939 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 26 Dec 2025 01:20:22 +0100 Subject: [PATCH 13/14] win32/sendmail.c/SendText(): use zend_string for stripped_headers This prevents some copying and is in preparation of other refactoring preventing strlen() recomputations --- win32/sendmail.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index f8b22897a2c6a..5f79f1ada79f0 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -112,7 +112,7 @@ static const char *ErrorMessages[] = #define PHP_WIN32_MAIL_DOT_REPLACE "\n.." static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, - const zend_string *headers, zend_string *headers_lc, char **error_message); + zend_string *headers, zend_string *headers_lc, char **error_message); static int MailConnect(); static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders); static bool Post(LPCSTR msg); @@ -387,14 +387,14 @@ static char *find_address(char *list, char **state) // History: //********************************************************************* static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, - const zend_string *headers, zend_string *headers_lc, char **error_message) + zend_string *headers, zend_string *headers_lc, char **error_message) { int res; char *p; char *tempMailTo, *token, *token_state; const char *pos1, *pos2; char *server_response = NULL; - char *stripped_header = NULL; + zend_string *stripped_header = NULL; zend_string *data_cln; /* check for NULL parameters */ @@ -565,35 +565,37 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const /* Now that we've identified that we've a Bcc list, remove it from the current header. */ - stripped_header = ecalloc(1, ZSTR_LEN(headers)); /* headers = point to string start of header pos1 = pointer IN headers where the Bcc starts '4' = Length of the characters 'bcc:' Because we've added +4 above for parsing the Emails we've to subtract them here. */ - memcpy(stripped_header, ZSTR_VAL(headers), pos1 - ZSTR_VAL(headers) - 4); + size_t header_length_prior_to_bcc = pos1 - ZSTR_VAL(headers) - 4; if (pos1 != pos2) { /* if pos1 != pos2 , pos2 points to the rest of the headers. Since pos1 != pos2 if "\r\n" was found, we know those characters are there and so we jump over them (else we would generate a new header which would look like "\r\n\r\n". */ - memcpy(stripped_header + (pos1 - ZSTR_VAL(headers) - 4), pos2 + 2, strlen(pos2) - 2); + stripped_header = zend_string_concat2(ZSTR_VAL(headers), header_length_prior_to_bcc, pos2 + 2, strlen(pos2) - 2); + } else { + stripped_header = zend_string_truncate(headers, header_length_prior_to_bcc, false); + ZSTR_VAL(stripped_header)[ZSTR_LEN(stripped_header)] = '\0'; } } else { /* Simplify the code that we create a copy of stripped_header no matter if - we actually strip something or not. So we've a single efree() later. */ - stripped_header = estrndup(ZSTR_VAL(headers), ZSTR_LEN(headers)); + we actually strip something or not. So we've a single zend_string_release() later. */ + stripped_header = zend_string_copy(headers); } } /* send message header */ if (Subject == NULL) { - res = PostHeader(RPath, "No Subject", mailTo, stripped_header); + res = PostHeader(RPath, "No Subject", mailTo, stripped_header ? ZSTR_VAL(stripped_header) : NULL); } else { - res = PostHeader(RPath, Subject, mailTo, stripped_header); + res = PostHeader(RPath, Subject, mailTo, stripped_header ? ZSTR_VAL(stripped_header) : NULL); } if (stripped_header) { - efree(stripped_header); + zend_string_release_ex(stripped_header, false); } if (res != SUCCESS) { return (res); From 77d306ef385e4f1108efab6f3f7a4ee28386d950 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 26 Dec 2025 01:45:47 +0100 Subject: [PATCH 14/14] win32/sendmail.c/PostHeader(): refactor function Input as zend_string Use smart_str to concatenate strings Change return type to bool --- win32/sendmail.c | 82 +++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/win32/sendmail.c b/win32/sendmail.c index 5f79f1ada79f0..394676f30316d 100644 --- a/win32/sendmail.c +++ b/win32/sendmail.c @@ -32,6 +32,7 @@ #include "php_win32_globals.h" +#include "Zend/zend_smart_str.h" #include "ext/pcre/php_pcre.h" #include "ext/standard/php_string.h" #include "ext/date/php_date.h" @@ -114,7 +115,7 @@ static const char *ErrorMessages[] = static int SendText(char *RPath, const char *Subject, const char *mailTo, const char *data, zend_string *headers, zend_string *headers_lc, char **error_message); static int MailConnect(); -static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders); +static bool PostHeader(char *RPath, const char *Subject, const char *mailTo, zend_string *xheaders); static bool Post(LPCSTR msg); static int Ack(char **server_response); static unsigned long GetAddr(LPSTR szHost); @@ -589,16 +590,17 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const } /* send message header */ + bool PostHeaderIsSuccessful = false; if (Subject == NULL) { - res = PostHeader(RPath, "No Subject", mailTo, stripped_header ? ZSTR_VAL(stripped_header) : NULL); + PostHeaderIsSuccessful = PostHeader(RPath, "No Subject", mailTo, stripped_header); } else { - res = PostHeader(RPath, Subject, mailTo, stripped_header ? ZSTR_VAL(stripped_header) : NULL); + PostHeaderIsSuccessful = PostHeader(RPath, Subject, mailTo, stripped_header); } if (stripped_header) { zend_string_release_ex(stripped_header, false); } - if (res != SUCCESS) { - return (res); + if (!PostHeaderIsSuccessful) { + return FAILED_TO_SEND; } /* Escape \n. sequences @@ -645,14 +647,6 @@ static int SendText(char *RPath, const char *Subject, const char *mailTo, const return (SUCCESS); } -static void addToHeader(char **header_buffer, const char *specifier, const char *string) -{ - size_t header_buffer_size = strlen(*header_buffer); - size_t total_size = header_buffer_size + strlen(specifier) + strlen(string) + 1; - *header_buffer = erealloc(*header_buffer, total_size); - snprintf(*header_buffer + header_buffer_size, total_size - header_buffer_size, specifier, string); -} - //********************************************************************* // Name: PostHeader // Input: 1) return path @@ -664,64 +658,58 @@ static void addToHeader(char **header_buffer, const char *specifier, const char // Author/Date: jcar 20/9/96 // History: //********************************************************************* -static int PostHeader(char *RPath, const char *Subject, const char *mailTo, char *xheaders) +static bool PostHeader(char *RPath, const char *Subject, const char *mailTo, zend_string *xheaders) { /* Print message header according to RFC 822 */ /* Return-path, Received, Date, From, Subject, Sender, To, cc */ - int res; - char *header_buffer; - char *headers_lc = NULL; - size_t i; + zend_string *headers_lc = NULL; + smart_str combined_headers = {0}; if (xheaders) { - size_t headers_lc_len; - - headers_lc = estrdup(xheaders); - headers_lc_len = strlen(headers_lc); - - for (i = 0; i < headers_lc_len; i++) { - headers_lc[i] = tolower(headers_lc[i]); - } + headers_lc = zend_string_tolower(xheaders); } - header_buffer = ecalloc(1, MAIL_BUFFER_SIZE); - - if (!xheaders || !strstr(headers_lc, "date:")) { + if (!xheaders || !strstr(ZSTR_VAL(headers_lc), "date:")) { time_t tNow = time(NULL); zend_string *dt = php_format_date("r", 1, tNow, 1); - snprintf(header_buffer, MAIL_BUFFER_SIZE, "Date: %s\r\n", ZSTR_VAL(dt)); + smart_str_appends(&combined_headers, "Date: "); + smart_str_append(&combined_headers, dt); + smart_str_appends(&combined_headers, "\r\n"); zend_string_free(dt); } - if (!headers_lc || !strstr(headers_lc, "from:")) { - addToHeader(&header_buffer, "From: %s\r\n", RPath); + if (!headers_lc || !strstr(ZSTR_VAL(headers_lc), "from:")) { + smart_str_appends(&combined_headers, "From: "); + smart_str_appends(&combined_headers, RPath); + smart_str_appends(&combined_headers, "\r\n"); } - addToHeader(&header_buffer, "Subject: %s\r\n", Subject); + smart_str_appends(&combined_headers, "Subject: "); + smart_str_appends(&combined_headers, Subject); + smart_str_appends(&combined_headers, "\r\n"); /* Only add the To: field from the $to parameter if isn't in the custom headers */ - if ((headers_lc && (!strstr(headers_lc, "\r\nto:") && (strncmp(headers_lc, "to:", 3) != 0))) || !headers_lc) { - addToHeader(&header_buffer, "To: %s\r\n", mailTo); + if (!headers_lc || (!strstr(ZSTR_VAL(headers_lc), "\r\nto:") && (strncmp(ZSTR_VAL(headers_lc), "to:", 3) != 0))) { + smart_str_appends(&combined_headers, "To: "); + smart_str_appends(&combined_headers, mailTo); + smart_str_appends(&combined_headers, "\r\n"); } if (xheaders) { - addToHeader(&header_buffer, "%s\r\n", xheaders); + smart_str_append(&combined_headers, xheaders); + smart_str_appends(&combined_headers, "\r\n"); } + /* End of headers */ + smart_str_appends(&combined_headers, "\r\n"); + zend_string *combined_headers_str = smart_str_extract(&combined_headers); if (headers_lc) { - efree(headers_lc); - } - if (!Post(header_buffer)) { - efree(header_buffer); - return (FAILED_TO_SEND); + zend_string_release_ex(headers_lc, false); } - efree(header_buffer); - if (!Post("\r\n")) { - return (FAILED_TO_SEND); - } - - return (SUCCESS); + bool header_post_status = Post(ZSTR_VAL(combined_headers_str)); + zend_string_efree(combined_headers_str); + return header_post_status; }