1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Fix GH-19188: Add support for new INI mail.cr_lf_mode (#19238)

This commit is contained in:
Alexandre Daubois
2025-09-08 09:58:27 +02:00
committed by GitHub
parent 4432083f38
commit ae7def78fb
12 changed files with 261 additions and 2 deletions

2
NEWS
View File

@@ -57,6 +57,8 @@ PHP NEWS
(Girgias) (Girgias)
. Fixed bug GH-19577 (Avoid integer overflow when using a small offset . Fixed bug GH-19577 (Avoid integer overflow when using a small offset
and PHP_INT_MAX with LimitIterator). (alexandre-daubois) and PHP_INT_MAX with LimitIterator). (alexandre-daubois)
. Implement GH-19188: Add support for new INI mail.cr_lf_mode.
(alexandre-daubois)
- Streams: - Streams:
. Fixed bug GH-14506 (Closing a userspace stream inside a userspace handler . Fixed bug GH-14506 (Closing a userspace stream inside a userspace handler

View File

@@ -494,7 +494,27 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
MAIL_RET(false); MAIL_RET(false);
} }
char *line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : "\r\n"; char *line_sep;
const char *cr_lf_mode = PG(mail_cr_lf_mode);
if (cr_lf_mode && strcmp(cr_lf_mode, "crlf") != 0) {
if (strcmp(cr_lf_mode, "lf") == 0) {
line_sep = "\n";
} else if (strcmp(cr_lf_mode, "mixed") == 0) {
line_sep = "\n";
} else if (strcmp(cr_lf_mode, "os") == 0) {
#ifdef PHP_WIN32
line_sep = "\r\n";
#else
line_sep = "\n";
#endif
} else {
ZEND_ASSERT(0 && "Unexpected cr_lf_mode value");
}
} else {
/* CRLF is default mode, but respect mail.mixed_lf_and_crlf for backward compatibility */
line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : "\r\n";
}
if (PG(mail_x_header)) { if (PG(mail_x_header)) {
const char *tmp = zend_get_executed_filename(); const char *tmp = zend_get_executed_filename();
@@ -586,7 +606,43 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
if (hdr != NULL) { if (hdr != NULL) {
fprintf(sendmail, "%s%s", hdr, line_sep); fprintf(sendmail, "%s%s", hdr, line_sep);
} }
fprintf(sendmail, "%s%s%s", line_sep, message, line_sep);
fprintf(sendmail, "%s", line_sep);
if (cr_lf_mode && strcmp(cr_lf_mode, "lf") == 0) {
char *converted_message = NULL;
size_t msg_len = strlen(message);
size_t new_len = 0;
for (size_t i = 0; i < msg_len - 1; ++i) {
if (message[i] == '\r' && message[i + 1] == '\n') {
++new_len;
}
}
if (new_len == 0) {
fprintf(sendmail, "%s", message);
} else {
converted_message = emalloc(msg_len - new_len + 1);
size_t j = 0;
for (size_t i = 0; i < msg_len; ++i) {
if (i < msg_len - 1 && message[i] == '\r' && message[i + 1] == '\n') {
converted_message[j++] = '\n';
++i; /* skip LF part */
} else {
converted_message[j++] = message[i];
}
}
converted_message[j] = '\0';
fprintf(sendmail, "%s", converted_message);
efree(converted_message);
}
} else {
fprintf(sendmail, "%s", message);
}
fprintf(sendmail, "%s", line_sep);
#ifdef PHP_WIN32 #ifdef PHP_WIN32
ret = pclose(sendmail); ret = pclose(sendmail);

View File

@@ -0,0 +1,23 @@
--TEST--
GH-19188: new INI mail.cr_lf_mode
--INI--
sendmail_path={MAIL:gh19188_cr_lf_mode.out}
mail.cr_lf_mode=crlf
--FILE--
<?php
var_dump(mail('user@example.com', 'Test Subject', 'A Message', 'X-Test: crlf'));
$mail = file_get_contents('gh19188_cr_lf_mode.out');
echo "CRLF mode:\n";
var_dump(preg_match_all('/\r\n/', $mail));
var_dump(preg_match_all('/(?<!\r)\n/', $mail));
?>
--CLEAN--
<?php
@unlink('gh19188_cr_lf_mode.out');
?>
--EXPECT--
bool(true)
CRLF mode:
int(5)
int(0)

View File

@@ -0,0 +1,26 @@
--TEST--
GH-19188: mail.cr_lf_mode runtime changes should fail
--FILE--
<?php
var_dump(ini_set('mail.cr_lf_mode', 'lf'));
var_dump(ini_get('mail.cr_lf_mode'));
var_dump(ini_set('mail.cr_lf_mode', 'mixed'));
var_dump(ini_get('mail.cr_lf_mode'));
var_dump(ini_set('mail.cr_lf_mode', 'os'));
var_dump(ini_get('mail.cr_lf_mode'));
var_dump(ini_set('mail.cr_lf_mode', 'invalid'));
var_dump(ini_get('mail.cr_lf_mode'));
?>
--EXPECT--
bool(false)
string(4) "crlf"
bool(false)
string(4) "crlf"
bool(false)
string(4) "crlf"
bool(false)
string(4) "crlf"

View File

@@ -0,0 +1,23 @@
--TEST--
GH-19188: mail.cr_lf_mode=lf
--INI--
sendmail_path={MAIL:gh19188_lf_mode.out}
mail.cr_lf_mode=lf
--FILE--
<?php
var_dump(mail('user@example.com', 'Test Subject', "A Message\r\nWith CRLF", 'X-Test: lf'));
$mail = file_get_contents('gh19188_lf_mode.out');
echo "LF mode:\n";
var_dump(preg_match_all('/\r\n/', $mail));
var_dump(preg_match_all('/(?<!\r)\n/', $mail));
?>
--CLEAN--
<?php
@unlink('gh19188_lf_mode.out');
?>
--EXPECT--
bool(true)
LF mode:
int(0)
int(6)

View File

@@ -0,0 +1,20 @@
--TEST--
GH-19188: mail.cr_lf_mode=mixed
--INI--
sendmail_path={MAIL:gh19188_mixed_mode.out}
mail.cr_lf_mode=mixed
--FILE--
<?php
var_dump(mail('user@example.com', 'Test Subject', 'A Message', 'X-Test: mixed'));
$mail = file_get_contents('gh19188_mixed_mode.out');
echo "Mixed mode:\n";
var_dump(preg_match_all('/(?<!\r)\n/', $mail));
?>
--CLEAN--
<?php
@unlink('gh19188_mixed_mode.out');
?>
--EXPECT--
bool(true)
Mixed mode:
int(5)

View File

@@ -0,0 +1,30 @@
--TEST--
GH-19188: mail.cr_lf_mode=os (Unix)
--SKIPIF--
<?php
if (PHP_OS_FAMILY === 'Windows') die("skip Non-Windows only");
?>
--INI--
sendmail_path={MAIL:gh19188_os_mode.out}
mail.cr_lf_mode=os
--FILE--
<?php
var_dump(mail('user@example.com', 'Test Subject', 'A Message', 'X-Test: os'));
$mail = file_get_contents('gh19188_os_mode.out');
echo "OS mode:\n";
$crlf_count = preg_match_all('/\r\n/', $mail);
$lf_only_count = preg_match_all('/(?<!\r)\n/', $mail);
echo "CRLF count: ";
var_dump($crlf_count);
echo "LF-only count: ";
var_dump($lf_only_count);
?>
--CLEAN--
<?php
@unlink('gh19188_os_mode.out');
?>
--EXPECT--
bool(true)
OS mode:
CRLF count: int(0)
LF-only count: int(5)

View File

@@ -0,0 +1,31 @@
--TEST--
GH-19188: mail.cr_lf_mode=os (Windows)
--SKIPIF--
<?php
if (PHP_OS_FAMILY !== 'Windows') die("skip Windows only");
?>
--INI--
sendmail_path={MAIL:gh19188_os_mode.out}
mail.cr_lf_mode=os
--FILE--
<?php
var_dump(mail('user@example.com', 'Test Subject', 'A Message', 'X-Test: os'));
$mail = file_get_contents('gh19188_os_mode.out');
echo "OS mode:\n";
$crlf_count = preg_match_all('/\r\n/', $mail);
$lf_only_count = preg_match_all('/(?<!\r)\n/', $mail);
echo "CRLF count: ";
var_dump($crlf_count);
echo "LF-only count: ";
var_dump($lf_only_count);
?>
--CLEAN--
<?php
@unlink('gh19188_os_mode.out');
?>
--EXPECT--
bool(true)
OS mode:
CRLF count: int(5)
LF-only count: int(0)

View File

@@ -721,6 +721,36 @@ static PHP_INI_MH(OnUpdateMailLog)
} }
/* }}} */ /* }}} */
/* {{{ PHP_INI_MH */
static PHP_INI_MH(OnUpdateMailCrLfMode)
{
if (new_value) {
const char *val = ZSTR_VAL(new_value);
if (ZSTR_LEN(new_value) > 0 &&
strcmp(val, "crlf") != 0 &&
strcmp(val, "lf") != 0 &&
strcmp(val, "mixed") != 0 &&
strcmp(val, "os") != 0) {
int err_type;
if (stage == ZEND_INI_STAGE_RUNTIME) {
err_type = E_WARNING;
} else {
err_type = E_ERROR;
}
if (stage != ZEND_INI_STAGE_DEACTIVATE) {
php_error_docref(NULL, err_type, "Invalid value \"%s\" for mail.cr_lf_mode. Must be one of: \"crlf\", \"lf\", \"mixed\", \"os\"", val);
}
return FAILURE;
}
}
OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
return SUCCESS;
}
/* }}} */
/* {{{ PHP_INI_MH */ /* {{{ PHP_INI_MH */
static PHP_INI_MH(OnChangeMailForceExtra) static PHP_INI_MH(OnChangeMailForceExtra)
{ {
@@ -826,6 +856,7 @@ PHP_INI_BEGIN()
PHP_INI_ENTRY("smtp_port", "25", PHP_INI_ALL, NULL) PHP_INI_ENTRY("smtp_port", "25", PHP_INI_ALL, NULL)
STD_PHP_INI_BOOLEAN("mail.add_x_header", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_x_header, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("mail.add_x_header", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_x_header, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_mixed_lf_and_crlf, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_mixed_lf_and_crlf, php_core_globals, core_globals)
STD_PHP_INI_ENTRY("mail.cr_lf_mode", "crlf", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailCrLfMode, mail_cr_lf_mode, php_core_globals, core_globals)
STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals) STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals)
PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap) PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap)

View File

@@ -154,6 +154,7 @@ struct _php_core_globals {
char *mail_log; char *mail_log;
bool mail_x_header; bool mail_x_header;
bool mail_mixed_lf_and_crlf; bool mail_mixed_lf_and_crlf;
char *mail_cr_lf_mode;
bool in_error_log; bool in_error_log;

View File

@@ -1086,6 +1086,14 @@ mail.add_x_header = Off
; RFC 2822 non conformant MTA. ; RFC 2822 non conformant MTA.
mail.mixed_lf_and_crlf = Off mail.mixed_lf_and_crlf = Off
; Control line ending mode for mail messages and headers.
; Possible values: "crlf" (default), "lf", "mixed", "os"
; - crlf: Use CRLF line endings
; - lf: Use LF line endings only (converts CRLF in message to LF)
; - mixed: Same as mail.mixed_lf_and_crlf = On
; - os: Use CRLF on Windows, LF on other systems
mail.cr_lf_mode = crlf
; The path to a log file that will log all mail() calls. Log entries include ; The path to a log file that will log all mail() calls. Log entries include
; the full path of the script, line number, To address and headers. ; the full path of the script, line number, To address and headers.
;mail.log = ;mail.log =

View File

@@ -1088,6 +1088,14 @@ mail.add_x_header = Off
; RFC 2822 non conformant MTA. ; RFC 2822 non conformant MTA.
mail.mixed_lf_and_crlf = Off mail.mixed_lf_and_crlf = Off
; Control line ending mode for mail messages and headers.
; Possible values: "crlf" (default), "lf", "mixed", "os"
; - crlf: Use CRLF line endings
; - lf: Use LF line endings only (converts CRLF in message to LF)
; - mixed: Same as mail.mixed_lf_and_crlf = On
; - os: Use CRLF on Windows, LF on other systems
mail.cr_lf_mode = crlf
; The path to a log file that will log all mail() calls. Log entries include ; The path to a log file that will log all mail() calls. Log entries include
; the full path of the script, line number, To address and headers. ; the full path of the script, line number, To address and headers.
;mail.log = ;mail.log =