Commit f817996280d for php

commit f817996280d7dc184a27b93dca29a2e4a8633090
Author: Nicolas Grekas <nicolas.grekas@gmail.com>
Date:   Mon Oct 5 19:44:36 2026 +0200

    Fix wordwrap() ignoring an existing break at the end of the string (#24116)

    When a multi-character break or $cut_long_words is used, an existing
    break was only recognized when at least one character followed it. A
    trailing break was handled as regular text, so wordwrap("abc\n", 3,
    "\n", true) returned "abc\n\n" and wordwrap("ab cd\n", 5, "\n", true)
    returned "ab\ncd\n". The single-character fast path was not affected.

diff --git a/NEWS b/NEWS
index 6134971c6ba..14b0d0b08db 100644
--- a/NEWS
+++ b/NEWS
@@ -219,6 +219,8 @@ PHP                                                                        NEWS
     PROCESS_INFORMATION, an indeterminate comspec pointer after a failed
     lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky)
   . Fix persistent stream context lifetime during shutdown (Levi Morrison)
+  . Fix wordwrap() ignoring an existing break at the end of the string
+    (Nicolas Grekas)

 - Tidy:
   . Fixed a use-after-free when a tidyNode is used after its document is
diff --git a/ext/standard/string.c b/ext/standard/string.c
index 34444d80d18..40b05135a8e 100644
--- a/ext/standard/string.c
+++ b/ext/standard/string.c
@@ -779,7 +779,7 @@ PHP_FUNCTION(wordwrap)
 			/* when we hit an existing break, copy to new buffer, and
 			 * fix up laststart and lastspace */
 			if (ZSTR_VAL(text)[current] == breakchar[0]
-				&& current + breakchar_len < ZSTR_LEN(text)
+				&& current + breakchar_len <= ZSTR_LEN(text)
 				&& !strncmp(ZSTR_VAL(text) + current, breakchar, breakchar_len)) {
 				memcpy(ZSTR_VAL(newtext) + newtextlen, ZSTR_VAL(text) + laststart, current - laststart + breakchar_len);
 				newtextlen += current - laststart + breakchar_len;
diff --git a/ext/standard/tests/strings/wordwrap_trailing_break.phpt b/ext/standard/tests/strings/wordwrap_trailing_break.phpt
new file mode 100644
index 00000000000..2e57ce17666
--- /dev/null
+++ b/ext/standard/tests/strings/wordwrap_trailing_break.phpt
@@ -0,0 +1,28 @@
+--TEST--
+wordwrap() recognizes an existing break at the end of the string
+--FILE--
+<?php
+$tests = [
+    ["abc\n", 3, "\n", true],
+    ["ab cd\n", 5, "\n", true],
+    ["abcd\n", 3, "\n", true],
+    ["abc\nabc\n", 3, "\n", true],
+    ["abc-", 3, "-", true],
+    ["abc\r\n", 3, "\r\n", true],
+    ["\r\n", 1, "\r\n", true],
+    ["ab cd\r\n", 5, "\r\n", false],
+];
+
+foreach ($tests as [$text, $width, $break, $cut]) {
+    echo json_encode(wordwrap($text, $width, $break, $cut)), "\n";
+}
+?>
+--EXPECT--
+"abc\n"
+"ab cd\n"
+"abc\nd\n"
+"abc\nabc\n"
+"abc-"
+"abc\r\n"
+"\r\n"
+"ab cd\r\n"