Make WordPress Core

Changeset 61201


Ignore:
Timestamp:
11/11/2025 02:34:32 AM (5 weeks ago)
Author:
westonruter
Message:

Mail: Improve multipart message handling in wp_mail().

This improves how wp_mail() handles Content-Type headers for multipart messages, preventing cases where the header could be duplicated.

Developed in https://github.com/WordPress/wordpress-develop/pull/9500

Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67.
Fixes #15448.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/pluggable.php

    r61188 r61201  
    175175     *              instead of PHPMailer's default validator.
    176176     * @since 6.9.0 Added $embeds parameter.
     177     * @since 6.9.0 Improved Content-Type header handling for multipart messages.
    177178     *
    178179     * @global PHPMailer\PHPMailer\PHPMailer $phpmailer
     
    352353                                    $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
    353354                                    $charset  = '';
     355                                    if ( preg_match( '~^multipart/(\S+)~', $content_type, $matches ) ) {
     356                                        $content_type = 'multipart/' . strtolower( $matches[1] ) . '; boundary="' . $boundary . '"';
     357                                    }
    354358                                }
    355359
     
    547551                    }
    548552                }
    549             }
    550 
    551             if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) {
    552                 $phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) );
    553553            }
    554554        }
  • trunk/tests/phpunit/tests/pluggable/wpMail.php

    r61131 r61201  
    8484        // We need some better assertions here but these catch the failure for now.
    8585        $this->assertSameIgnoreEOL( $body, $mailer->get_sent()->body );
    86         $this->assertStringContainsString( 'boundary="----=_Part_4892_25692638.1192452070893"', iconv_mime_decode_headers( ( $mailer->get_sent()->header ) )['Content-Type'][0] );
    87         $this->assertStringContainsString( 'charset=', $mailer->get_sent()->header );
     86        $headers = iconv_mime_decode_headers( $mailer->get_sent()->header );
     87        $this->assertArrayHasKey( 'Content-Type', $headers, 'Expected Content-Type header to be sent.' );
     88        $content_type_headers = (array) $headers['Content-Type'];
     89        $this->assertCount( 1, $content_type_headers, "Expected only one Content-Type header to be sent. Saw:\n" . implode( "\n", $content_type_headers ) );
     90        $this->assertSame( 'multipart/mixed; boundary="----=_Part_4892_25692638.1192452070893"; charset=', $content_type_headers[0], 'Expected Content-Type to match.' );
    8891    }
    8992
     
    670673        $this->assertEquals( '7bit', $mailer->Encoding );
    671674    }
     675
     676    /**
     677     * Test that wp_mail() can send a multipart/alternative email with plain text and html versions.
     678     *
     679     * @ticket 15448
     680     */
     681    public function test_wp_mail_plain_and_html() {
     682        $headers = 'Content-Type: multipart/alternative; boundary="TestBoundary"';
     683        $to      = '[email protected]';
     684        $subject = 'Test email with plain text and html versions';
     685        $message = <<<EOT
     686--TestBoundary
     687Content-Type: text/plain; charset=us-ascii
     688
     689Here is some plain text.
     690--TestBoundary
     691Content-Type: text/html; charset=UTF-8
     692Content-Transfer-Encoding: 8bit
     693
     694<html><head></head><body>Here is the HTML with UTF-8 γειά σου Κόσμε;-)<body></html>
     695--TestBoundary--
     696EOT;
     697
     698        wp_mail( $to, $subject, $message, $headers );
     699        $mailer = tests_retrieve_phpmailer_instance();
     700
     701        $this->assertSame( 1, preg_match( '/boundary="(.*)"/', $mailer->get_sent()->header, $matches ), 'Expected to match boundary directive in header.' );
     702        $boundary = $matches[1];
     703        $body     = '--' . $boundary . "\n";
     704        $body    .= 'Content-Type: text/plain; charset=us-ascii' . "\n";
     705        $body    .= "\n";
     706        $body    .= 'Here is some plain text.' . "\n";
     707        $body    .= '--' . $boundary . "\n";
     708        $body    .= 'Content-Type: text/html; charset=UTF-8' . "\n";
     709        $body    .= 'Content-Transfer-Encoding: 8bit' . "\n";
     710        $body    .= "\n";
     711        $body    .= '<html><head></head><body>Here is the HTML with UTF-8 γειά σου Κόσμε;-)<body></html>' . "\n";
     712        $body    .= '--' . $boundary . '--' . "\n";
     713
     714        $this->assertSameIgnoreEOL( $body, $mailer->get_sent()->body, 'The body is not as expected.' );
     715        $this->assertStringContainsString(
     716            'Content-Type: multipart/alternative;',
     717            $mailer->get_sent()->header,
     718            'The multipart/alternative header is not present.'
     719        );
     720    }
     721
     722    /**
     723     * Check workarounds using phpmailer_init still work around.
     724     *
     725     * @ticket 15448
     726     */
     727    public function test_wp_mail_plain_and_html_workaround() {
     728        $to      = '[email protected]';
     729        $subject = 'Test email with plain text derived from html version';
     730        $message = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body><p>Hello World! γειά σου Κόσμε</p></body></html>';
     731
     732        $set_alt_body = static function ( WP_PHPMailer $mailer ) {
     733            $mailer->AltBody = strip_tags( $mailer->Body );
     734        };
     735        add_action( 'phpmailer_init', $set_alt_body );
     736        wp_mail( $to, $subject, $message );
     737        remove_action( 'phpmailer_init', $set_alt_body );
     738
     739        $mailer = tests_retrieve_phpmailer_instance();
     740
     741        $this->assertStringContainsString(
     742            'Content-Type: multipart/alternative;',
     743            $mailer->get_sent()->header,
     744            'The multipart/alternative header is not present.'
     745        );
     746        $this->assertStringContainsString(
     747            'Content-Type: text/plain; charset=UTF-8',
     748            $mailer->get_sent()->body,
     749            'The text/plain Content-Type header is not present.'
     750        );
     751        $this->assertStringContainsString(
     752            'Content-Type: text/html; charset=UTF-8',
     753            $mailer->get_sent()->body,
     754            'The text/html Content-Type header is not present.'
     755        );
     756    }
    672757}
Note: See TracChangeset for help on using the changeset viewer.