From cc931af35ddc0a07c0a669eb4da9dc66e1f73af4 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 30 Dec 2022 15:09:48 +0000 Subject: [PATCH] Fix GH-8086: Introduce mail.mixed_lf_and_crlf INI When this INI option is enabled, it reverts the line separator for headers and message to LF which was a non conformant behavior in PHP 7. It is done because some non conformant MTAs fail to parse CRLF line separator for headers and body. This is used for mail and mb_send_mail functions. --- NEWS | 3 +++ UPGRADING | 2 ++ ext/mbstring/mbstring.c | 16 ++++++++------ ext/mbstring/tests/gh8086.phpt | 33 +++++++++++++++++++++++++++++ ext/standard/mail.c | 12 ++++++----- ext/standard/tests/mail/gh8086.phpt | 18 ++++++++++++++++ main/main.c | 1 + main/php_globals.h | 1 + php.ini-development | 4 ++++ php.ini-production | 4 ++++ 10 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 ext/mbstring/tests/gh8086.phpt create mode 100644 ext/standard/tests/mail/gh8086.phpt diff --git a/NEWS b/NEWS index 1046769880e..8c56c818dea 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ PHP NEWS - GMP: . Properly implement GMP::__construct(). (nielsdos) +- Standard: + - Fixed bug GH-8086 (Introduce mail.mixed_lf_and_crlf INI). (Jakub Zelenka) + 02 Feb 2023, PHP 8.2.2 - Core: diff --git a/UPGRADING b/UPGRADING index e67d692d8cc..744fa57c1c8 100644 --- a/UPGRADING +++ b/UPGRADING @@ -230,6 +230,8 @@ PHP 8.2 UPGRADE NOTES - Standard . unserialize() now performs a stricter validation of the structure of serialized objects. + . mail() function reverts back to the mixed LF and CRLF new lines (behavior + before PHP 8.0) if mail.mixed_lf_and_crlf INI is on. - XML . xml_parser_set_option() now actually returns false when attempting to set a diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 34678ea3d0f..7bcb771dae8 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -4128,7 +4128,9 @@ PHP_FUNCTION(mb_send_mail) || orig_str.encoding->no_encoding == mbfl_no_encoding_pass) { orig_str.encoding = mbfl_identify_encoding(&orig_str, MBSTRG(current_detect_order_list), MBSTRG(current_detect_order_list_size), MBSTRG(strict_detection)); } - pstr = mbfl_mime_header_encode(&orig_str, &conv_str, tran_cs, head_enc, CRLF, sizeof("Subject: [PHP-jp nnnnnnnn]" CRLF) - 1); + const char *line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : CRLF; + size_t line_sep_len = strlen(line_sep); + pstr = mbfl_mime_header_encode(&orig_str, &conv_str, tran_cs, head_enc, line_sep, strlen("Subject: [PHP-jp nnnnnnnn]") + line_sep_len); if (pstr != NULL) { subject_buf = subject = (char *)pstr->val; } @@ -4167,14 +4169,14 @@ PHP_FUNCTION(mb_send_mail) n = ZSTR_LEN(str_headers); mbfl_memory_device_strncat(&device, p, n); if (n > 0 && p[n - 1] != '\n') { - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } zend_string_release_ex(str_headers, 0); } if (!zend_hash_str_exists(&ht_headers, "mime-version", sizeof("mime-version") - 1)) { mbfl_memory_device_strncat(&device, PHP_MBSTR_MAIL_MIME_HEADER1, sizeof(PHP_MBSTR_MAIL_MIME_HEADER1) - 1); - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } if (!suppressed_hdrs.cnt_type) { @@ -4185,7 +4187,7 @@ PHP_FUNCTION(mb_send_mail) mbfl_memory_device_strncat(&device, PHP_MBSTR_MAIL_MIME_HEADER3, sizeof(PHP_MBSTR_MAIL_MIME_HEADER3) - 1); mbfl_memory_device_strcat(&device, p); } - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } if (!suppressed_hdrs.cnt_trans_enc) { mbfl_memory_device_strncat(&device, PHP_MBSTR_MAIL_MIME_HEADER4, sizeof(PHP_MBSTR_MAIL_MIME_HEADER4) - 1); @@ -4194,10 +4196,12 @@ PHP_FUNCTION(mb_send_mail) p = "7bit"; } mbfl_memory_device_strcat(&device, p); - mbfl_memory_device_strncat(&device, CRLF, sizeof(CRLF)-1); + mbfl_memory_device_strncat(&device, line_sep, line_sep_len); } - mbfl_memory_device_unput(&device); + if (!PG(mail_mixed_lf_and_crlf)) { + mbfl_memory_device_unput(&device); + } mbfl_memory_device_unput(&device); mbfl_memory_device_output('\0', &device); str_headers = zend_string_init((char *)device.buffer, strlen((char *)device.buffer), 0); diff --git a/ext/mbstring/tests/gh8086.phpt b/ext/mbstring/tests/gh8086.phpt new file mode 100644 index 00000000000..2eeed67e5b4 --- /dev/null +++ b/ext/mbstring/tests/gh8086.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-8086 (mb_send_mail() function not working correctly in PHP 8.x) +--SKIPIF-- + +--INI-- +sendmail_path={MAIL:{PWD}/gh8086.eml} +mail.mixed_lf_and_crlf=on +--FILE-- + +--CLEAN-- + +--EXPECT-- +int(6) diff --git a/ext/standard/mail.c b/ext/standard/mail.c index 55790e6100f..ef4c8c60a87 100644 --- a/ext/standard/mail.c +++ b/ext/standard/mail.c @@ -429,6 +429,8 @@ PHPAPI int php_mail(const char *to, const char *subject, const char *message, co MAIL_RET(0); } + char *line_sep = PG(mail_mixed_lf_and_crlf) ? "\n" : "\r\n"; + if (PG(mail_x_header)) { const char *tmp = zend_get_executed_filename(); zend_string *f; @@ -436,7 +438,7 @@ PHPAPI int php_mail(const char *to, const char *subject, const char *message, co f = php_basename(tmp, strlen(tmp), NULL, 0); if (headers != NULL && *headers) { - spprintf(&ahdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s\r\n%s", php_getuid(), ZSTR_VAL(f), headers); + spprintf(&ahdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s%s%s", php_getuid(), ZSTR_VAL(f), line_sep, headers); } else { spprintf(&ahdr, 0, "X-PHP-Originating-Script: " ZEND_LONG_FMT ":%s", php_getuid(), ZSTR_VAL(f)); } @@ -510,12 +512,12 @@ PHPAPI int php_mail(const char *to, const char *subject, const char *message, co MAIL_RET(0); } #endif - fprintf(sendmail, "To: %s\r\n", to); - fprintf(sendmail, "Subject: %s\r\n", subject); + fprintf(sendmail, "To: %s%s", to, line_sep); + fprintf(sendmail, "Subject: %s%s", subject, line_sep); if (hdr != NULL) { - fprintf(sendmail, "%s\r\n", hdr); + fprintf(sendmail, "%s%s", hdr, line_sep); } - fprintf(sendmail, "\r\n%s\r\n", message); + fprintf(sendmail, "%s%s%s", line_sep, message, line_sep); ret = pclose(sendmail); #if PHP_SIGCHILD diff --git a/ext/standard/tests/mail/gh8086.phpt b/ext/standard/tests/mail/gh8086.phpt new file mode 100644 index 00000000000..fba5cc56bb4 --- /dev/null +++ b/ext/standard/tests/mail/gh8086.phpt @@ -0,0 +1,18 @@ +--TEST-- +GH-8086 (Mail() function not working correctly in PHP 8.x) +--INI-- +sendmail_path={MAIL:gh8086.out} +mail.mixed_lf_and_crlf=on +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +int(5) diff --git a/main/main.c b/main/main.c index 8be52d316a1..6b8a9e6bd19 100644 --- a/main/main.c +++ b/main/main.c @@ -737,6 +737,7 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("SMTP", "localhost",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.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.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("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit) diff --git a/main/php_globals.h b/main/php_globals.h index cbf0271c7b7..0e004bd0111 100644 --- a/main/php_globals.h +++ b/main/php_globals.h @@ -153,6 +153,7 @@ struct _php_core_globals { char *request_order; bool mail_x_header; + bool mail_mixed_lf_and_crlf; char *mail_log; bool in_error_log; diff --git a/php.ini-development b/php.ini-development index 70c6b005558..ba75e4933ba 100644 --- a/php.ini-development +++ b/php.ini-development @@ -1095,6 +1095,10 @@ smtp_port = 25 ; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename mail.add_x_header = Off +; Use mixed LF and CRLF line separators to keep compatibility with some +; RFC 2822 non conformant MTA. +mail.mixed_lf_and_crlf = Off + ; 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. ;mail.log = diff --git a/php.ini-production b/php.ini-production index 21627c91424..9f9ff22586d 100644 --- a/php.ini-production +++ b/php.ini-production @@ -1097,6 +1097,10 @@ smtp_port = 25 ; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename mail.add_x_header = Off +; Use mixed LF and CRLF line separators to keep compatibility with some +; RFC 2822 non conformant MTA. +mail.mixed_lf_and_crlf = Off + ; 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. ;mail.log =