From 39b1b3a7cccf2cbebad406c60421992b6242fded Mon Sep 17 00:00:00 2001 From: PurHur Date: Mon, 20 Jul 2026 11:41:42 +0000 Subject: [PATCH] Stdlib: mail.add_x_header emits X-PHP-Originating-Script (#21433) Honor CLI/PHPT mail.add_x_header and prepend X-PHP-Originating-Script:{uid}:{basename} before piping to sendmail, matching php-src php_mail(). Co-authored-by: Cursor --- ext/standard/VmIni.php | 19 +++++++++++++-- ext/standard/VmMail.php | 24 +++++++++++++++++++ .../cases/stdlib/mail_add_x_header.phpt | 24 +++++++++++++++++++ .../cases/stdlib/mail_add_x_header_off.phpt | 19 +++++++++++++++ test/repro/issue_21433_mail_add_x_header.php | 23 ++++++++++++++++++ 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 test/compliance/cases/stdlib/mail_add_x_header.phpt create mode 100644 test/compliance/cases/stdlib/mail_add_x_header_off.phpt create mode 100644 test/repro/issue_21433_mail_add_x_header.php diff --git a/ext/standard/VmIni.php b/ext/standard/VmIni.php index 8ba632c1b24..3508a3bc6fd 100644 --- a/ext/standard/VmIni.php +++ b/ext/standard/VmIni.php @@ -97,7 +97,6 @@ final class VmIni 'disable_functions', 'disable_classes', 'open_basedir', - 'mail.add_x_header', 'error_append_string', 'error_prepend_string', 'upload_tmp_dir', @@ -118,7 +117,6 @@ final class VmIni 'user_dir', 'disable_functions', 'disable_classes', - 'mail.add_x_header', ]; /** @var list */ @@ -245,6 +243,9 @@ public static function get(Context $ctx, string $option) { return ''; } + if ('mail.add_x_header' === $key) { + return self::formatRegisterArgcArgvIniGet(self::$mailAddXHeader); + } if (in_array($key, self::EMPTY_STRING_INI_KEYS, true)) { return ''; } @@ -409,6 +410,9 @@ public static function getUnserializeMaxDepth(): int private static string $maxExecutionTime = self::CFG_MAX_EXECUTION_TIME; + /** php-src PG(mail_x_header) — mail.add_x_header (#21433). */ + private static bool $mailAddXHeader = false; + /** php-src PG(register_argc_argv) — startup/-d only; runtime ini_set() returns false (#4515). */ private static bool $registerArgcArgv = true; @@ -443,6 +447,11 @@ public static function applyStartupIniOverride(string $option, string $value): b return true; } + if ('mail.add_x_header' === $key) { + self::$mailAddXHeader = self::parseBoolIni($value); + + return true; + } if ('phar.readonly' === $key) { \PHPCompiler\ext\phar\VmPhar::setStartupReadonly(self::parseBoolIni($value)); @@ -452,6 +461,12 @@ public static function applyStartupIniOverride(string $option, string $value): b return false; } + /** php-src PG(mail_x_header) for mail() X-PHP-Originating-Script (#21433). */ + public static function mailAddXHeaderEnabled(): bool + { + return self::$mailAddXHeader; + } + /** Observable ini_get('max_execution_time') after set_time_limit / ini_set (#12481). */ public static function syncMaxExecutionTime(int $seconds): void { diff --git a/ext/standard/VmMail.php b/ext/standard/VmMail.php index a6963e79a55..25bade707ac 100644 --- a/ext/standard/VmMail.php +++ b/ext/standard/VmMail.php @@ -265,6 +265,9 @@ public static function send( $headers = null; } } + if (VmIni::mailAddXHeaderEnabled()) { + $headers = self::prependOriginatingScriptHeader($frame, $headers); + } $sendmailPath = VmIniIntrospection::mirroredHostIniGet('sendmail_path'); if (null === $sendmailPath || '' === $sendmailPath) { @@ -302,6 +305,27 @@ public static function send( return true; } + /** + * php-src php_mail() PG(mail_x_header) — X-PHP-Originating-Script (#21433). + */ + private static function prependOriginatingScriptHeader(Frame $frame, ?string $headers): string + { + $sep = "\r\n"; + $uid = VmProcessIdentityNative::getuid() ?? 0; + $script = $frame->scriptPath; + if ('' === $script || '-' === $script || str_starts_with($script, 'Command line')) { + $base = 'Command line code'; + } else { + $base = VmString::basename($script); + } + $origin = 'X-PHP-Originating-Script: '.$uid.':'.$base; + if (null !== $headers && '' !== $headers) { + return $origin.$sep.$headers; + } + + return $origin; + } + /** * php-src mail.c — trim trailing whitespace; replace bare control chars with space * while preserving RFC822 long-header CRLF+WSP folding. diff --git a/test/compliance/cases/stdlib/mail_add_x_header.phpt b/test/compliance/cases/stdlib/mail_add_x_header.phpt new file mode 100644 index 00000000000..44279fa9a8d --- /dev/null +++ b/test/compliance/cases/stdlib/mail_add_x_header.phpt @@ -0,0 +1,24 @@ +--TEST-- +stdlib mail.add_x_header X-PHP-Originating-Script (#21433, ext/standard/mail.c) +--INI-- +sendmail_path={PWD}/mail_fixtures/mock_sendmail.sh +mail.add_x_header=1 +--FILE-- + 'noreply@example.com']); +var_export($ok); +echo "\n"; +$raw = is_file($out) ? file_get_contents($out) : ''; +echo (str_contains($raw, 'X-PHP-Originating-Script:') ? 'has_x' : 'no_x'), "\n"; +echo (str_contains($raw, 'From: noreply@example.com') ? 'has_from' : 'no_from'), "\n"; +@unlink($out); +--EXPECT-- +'1' +true +has_x +has_from diff --git a/test/compliance/cases/stdlib/mail_add_x_header_off.phpt b/test/compliance/cases/stdlib/mail_add_x_header_off.phpt new file mode 100644 index 00000000000..a489f19f839 --- /dev/null +++ b/test/compliance/cases/stdlib/mail_add_x_header_off.phpt @@ -0,0 +1,19 @@ +--TEST-- +stdlib mail.add_x_header=0 omits X-PHP-Originating-Script (#21433) +--INI-- +sendmail_path={PWD}/mail_fixtures/mock_sendmail.sh +mail.add_x_header=0 +--FILE-- +