Skip to content

Commit ec31fc1

Browse files
Fix GH-19188: Add support for new INI mail.cr_lf_mode
1 parent 7c1e461 commit ec31fc1

12 files changed

+250
-2
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ PHP NEWS
1111
. Add support for CURLINFO_CONN_ID in curl_getinfo() (thecaliskan)
1212
. Add support for CURLINFO_QUEUE_TIME_T in curl_getinfo() (thecaliskan)
1313

14+
- Mail:
15+
. Add support for new INI mail.cr_lf_mode (alexandre-daubois)
16+
1417
- OpenSSL:
1518
. Add $digest_algo parameter to openssl_public_encrypt() and
1619
openssl_private_decrypt() functions. (Jakub Zelenka)

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+
line_sep = "\r\n";
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,24 @@ static PHP_INI_MH(OnUpdateMailLog)
703703
}
704704
/* }}} */
705705

706+
/* {{{ PHP_INI_MH */
707+
static PHP_INI_MH(OnUpdateMailCrLfMode)
708+
{
709+
if (new_value) {
710+
const char *val = ZSTR_VAL(new_value);
711+
if (ZSTR_LEN(new_value) > 0 &&
712+
strcmp(val, "crlf") != 0 &&
713+
strcmp(val, "lf") != 0 &&
714+
strcmp(val, "mixed") != 0 &&
715+
strcmp(val, "os") != 0) {
716+
return FAILURE;
717+
}
718+
}
719+
OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
720+
return SUCCESS;
721+
}
722+
/* }}} */
723+
706724
/* {{{ PHP_INI_MH */
707725
static PHP_INI_MH(OnChangeMailForceExtra)
708726
{
@@ -808,6 +826,7 @@ PHP_INI_BEGIN()
808826
PHP_INI_ENTRY("smtp_port", "25", PHP_INI_ALL, NULL)
809827
STD_PHP_INI_BOOLEAN("mail.add_x_header", "0", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, mail_x_header, php_core_globals, core_globals)
810828
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)
829+
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)
811830
STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals)
812831
PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap)
813832
PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit)

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)