Skip to content

Commit ae7def7

Browse files
Fix phpGH-19188: Add support for new INI mail.cr_lf_mode (php#19238)
1 parent 4432083 commit ae7def7

File tree

12 files changed

+261
-2
lines changed

12 files changed

+261
-2
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ PHP NEWS
5757
(Girgias)
5858
. Fixed bug GH-19577 (Avoid integer overflow when using a small offset
5959
and PHP_INT_MAX with LimitIterator). (alexandre-daubois)
60+
. Implement GH-19188: Add support for new INI mail.cr_lf_mode.
61+
(alexandre-daubois)
6062

6163
- Streams:
6264
. Fixed bug GH-14506 (Closing a userspace stream inside a userspace handler

ext/standard/mail.c

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,27 @@ PHPAPI bool php_mail(const char *to, const char *subject, const char *message, c
494494
MAIL_RET(false);
495495
}
496496

497-
char *line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : "\r\n";
497+
char *line_sep;
498+
const char *cr_lf_mode = PG(mail_cr_lf_mode);
499+
500+
if (cr_lf_mode && strcmp(cr_lf_mode, "crlf") != 0) {
501+
if (strcmp(cr_lf_mode, "lf") == 0) {
502+
line_sep = "\n";
503+
} else if (strcmp(cr_lf_mode, "mixed") == 0) {
504+
line_sep = "\n";
505+
} else if (strcmp(cr_lf_mode, "os") == 0) {
506+
#ifdef PHP_WIN32
507+
line_sep = "\r\n";
508+
#else
509+
line_sep = "\n";
510+
#endif
511+
} else {
512+
ZEND_ASSERT(0 && "Unexpected cr_lf_mode value");
513+
}
514+
} else {
515+
/* CRLF is default mode, but respect mail.mixed_lf_and_crlf for backward compatibility */
516+
line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : "\r\n";
517+
}
498518

499519
if (PG(mail_x_header)) {
500520
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
586606
if (hdr != NULL) {
587607
fprintf(sendmail, "%s%s", hdr, line_sep);
588608
}
589-
fprintf(sendmail, "%s%s%s", line_sep, message, line_sep);
609+
610+
fprintf(sendmail, "%s", line_sep);
611+
612+
if (cr_lf_mode && strcmp(cr_lf_mode, "lf") == 0) {
613+
char *converted_message = NULL;
614+
size_t msg_len = strlen(message);
615+
size_t new_len = 0;
616+
617+
for (size_t i = 0; i < msg_len - 1; ++i) {
618+
if (message[i] == '\r' && message[i + 1] == '\n') {
619+
++new_len;
620+
}
621+
}
622+
623+
if (new_len == 0) {
624+
fprintf(sendmail, "%s", message);
625+
} else {
626+
converted_message = emalloc(msg_len - new_len + 1);
627+
size_t j = 0;
628+
for (size_t i = 0; i < msg_len; ++i) {
629+
if (i < msg_len - 1 && message[i] == '\r' && message[i + 1] == '\n') {
630+
converted_message[j++] = '\n';
631+
++i; /* skip LF part */
632+
} else {
633+
converted_message[j++] = message[i];
634+
}
635+
}
636+
637+
converted_message[j] = '\0';
638+
fprintf(sendmail, "%s", converted_message);
639+
efree(converted_message);
640+
}
641+
} else {
642+
fprintf(sendmail, "%s", message);
643+
}
644+
645+
fprintf(sendmail, "%s", line_sep);
590646
#ifdef PHP_WIN32
591647
ret = pclose(sendmail);
592648

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
GH-19188: new INI mail.cr_lf_mode
3+
--INI--
4+
sendmail_path={MAIL:gh19188_cr_lf_mode.out}
5+
mail.cr_lf_mode=crlf
6+
--FILE--
7+
<?php
8+
9+
var_dump(mail('[email protected]', 'Test Subject', 'A Message', 'X-Test: crlf'));
10+
$mail = file_get_contents('gh19188_cr_lf_mode.out');
11+
echo "CRLF mode:\n";
12+
var_dump(preg_match_all('/\r\n/', $mail));
13+
var_dump(preg_match_all('/(?<!\r)\n/', $mail));
14+
?>
15+
--CLEAN--
16+
<?php
17+
@unlink('gh19188_cr_lf_mode.out');
18+
?>
19+
--EXPECT--
20+
bool(true)
21+
CRLF mode:
22+
int(5)
23+
int(0)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
GH-19188: mail.cr_lf_mode runtime changes should fail
3+
--FILE--
4+
<?php
5+
6+
var_dump(ini_set('mail.cr_lf_mode', 'lf'));
7+
var_dump(ini_get('mail.cr_lf_mode'));
8+
9+
var_dump(ini_set('mail.cr_lf_mode', 'mixed'));
10+
var_dump(ini_get('mail.cr_lf_mode'));
11+
12+
var_dump(ini_set('mail.cr_lf_mode', 'os'));
13+
var_dump(ini_get('mail.cr_lf_mode'));
14+
15+
var_dump(ini_set('mail.cr_lf_mode', 'invalid'));
16+
var_dump(ini_get('mail.cr_lf_mode'));
17+
?>
18+
--EXPECT--
19+
bool(false)
20+
string(4) "crlf"
21+
bool(false)
22+
string(4) "crlf"
23+
bool(false)
24+
string(4) "crlf"
25+
bool(false)
26+
string(4) "crlf"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
GH-19188: mail.cr_lf_mode=lf
3+
--INI--
4+
sendmail_path={MAIL:gh19188_lf_mode.out}
5+
mail.cr_lf_mode=lf
6+
--FILE--
7+
<?php
8+
9+
var_dump(mail('[email protected]', 'Test Subject', "A Message\r\nWith CRLF", 'X-Test: lf'));
10+
$mail = file_get_contents('gh19188_lf_mode.out');
11+
echo "LF mode:\n";
12+
var_dump(preg_match_all('/\r\n/', $mail));
13+
var_dump(preg_match_all('/(?<!\r)\n/', $mail));
14+
?>
15+
--CLEAN--
16+
<?php
17+
@unlink('gh19188_lf_mode.out');
18+
?>
19+
--EXPECT--
20+
bool(true)
21+
LF mode:
22+
int(0)
23+
int(6)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
GH-19188: mail.cr_lf_mode=mixed
3+
--INI--
4+
sendmail_path={MAIL:gh19188_mixed_mode.out}
5+
mail.cr_lf_mode=mixed
6+
--FILE--
7+
<?php
8+
var_dump(mail('[email protected]', 'Test Subject', 'A Message', 'X-Test: mixed'));
9+
$mail = file_get_contents('gh19188_mixed_mode.out');
10+
echo "Mixed mode:\n";
11+
var_dump(preg_match_all('/(?<!\r)\n/', $mail));
12+
?>
13+
--CLEAN--
14+
<?php
15+
@unlink('gh19188_mixed_mode.out');
16+
?>
17+
--EXPECT--
18+
bool(true)
19+
Mixed mode:
20+
int(5)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
GH-19188: mail.cr_lf_mode=os (Unix)
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY === 'Windows') die("skip Non-Windows only");
6+
?>
7+
--INI--
8+
sendmail_path={MAIL:gh19188_os_mode.out}
9+
mail.cr_lf_mode=os
10+
--FILE--
11+
<?php
12+
var_dump(mail('[email protected]', 'Test Subject', 'A Message', 'X-Test: os'));
13+
$mail = file_get_contents('gh19188_os_mode.out');
14+
echo "OS mode:\n";
15+
$crlf_count = preg_match_all('/\r\n/', $mail);
16+
$lf_only_count = preg_match_all('/(?<!\r)\n/', $mail);
17+
echo "CRLF count: ";
18+
var_dump($crlf_count);
19+
echo "LF-only count: ";
20+
var_dump($lf_only_count);
21+
?>
22+
--CLEAN--
23+
<?php
24+
@unlink('gh19188_os_mode.out');
25+
?>
26+
--EXPECT--
27+
bool(true)
28+
OS mode:
29+
CRLF count: int(0)
30+
LF-only count: int(5)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
GH-19188: mail.cr_lf_mode=os (Windows)
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY !== 'Windows') die("skip Windows only");
6+
?>
7+
--INI--
8+
sendmail_path={MAIL:gh19188_os_mode.out}
9+
mail.cr_lf_mode=os
10+
--FILE--
11+
<?php
12+
13+
var_dump(mail('[email protected]', 'Test Subject', 'A Message', 'X-Test: os'));
14+
$mail = file_get_contents('gh19188_os_mode.out');
15+
echo "OS mode:\n";
16+
$crlf_count = preg_match_all('/\r\n/', $mail);
17+
$lf_only_count = preg_match_all('/(?<!\r)\n/', $mail);
18+
echo "CRLF count: ";
19+
var_dump($crlf_count);
20+
echo "LF-only count: ";
21+
var_dump($lf_only_count);
22+
?>
23+
--CLEAN--
24+
<?php
25+
@unlink('gh19188_os_mode.out');
26+
?>
27+
--EXPECT--
28+
bool(true)
29+
OS mode:
30+
CRLF count: int(5)
31+
LF-only count: int(0)

main/main.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,36 @@ static PHP_INI_MH(OnUpdateMailLog)
721721
}
722722
/* }}} */
723723

724+
/* {{{ PHP_INI_MH */
725+
static PHP_INI_MH(OnUpdateMailCrLfMode)
726+
{
727+
if (new_value) {
728+
const char *val = ZSTR_VAL(new_value);
729+
if (ZSTR_LEN(new_value) > 0 &&
730+
strcmp(val, "crlf") != 0 &&
731+
strcmp(val, "lf") != 0 &&
732+
strcmp(val, "mixed") != 0 &&
733+
strcmp(val, "os") != 0) {
734+
int err_type;
735+
736+
if (stage == ZEND_INI_STAGE_RUNTIME) {
737+
err_type = E_WARNING;
738+
} else {
739+
err_type = E_ERROR;
740+
}
741+
742+
if (stage != ZEND_INI_STAGE_DEACTIVATE) {
743+
php_error_docref(NULL, err_type, "Invalid value \"%s\" for mail.cr_lf_mode. Must be one of: \"crlf\", \"lf\", \"mixed\", \"os\"", val);
744+
}
745+
746+
return FAILURE;
747+
}
748+
}
749+
OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
750+
return SUCCESS;
751+
}
752+
/* }}} */
753+
724754
/* {{{ PHP_INI_MH */
725755
static PHP_INI_MH(OnChangeMailForceExtra)
726756
{
@@ -826,6 +856,7 @@ PHP_INI_BEGIN()
826856
PHP_INI_ENTRY("smtp_port", "25", PHP_INI_ALL, NULL)
827857
STD_PHP_INI_BOOLEAN("mail.add_x_header", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_x_header, php_core_globals, core_globals)
828858
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)
859+
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)
829860
STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals)
830861
PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap)
831862

main/php_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ struct _php_core_globals {
154154
char *mail_log;
155155
bool mail_x_header;
156156
bool mail_mixed_lf_and_crlf;
157+
char *mail_cr_lf_mode;
157158

158159
bool in_error_log;
159160

0 commit comments

Comments
 (0)