Make WordPress Core

Changeset 61172


Ignore:
Timestamp:
11/06/2025 11:35:52 PM (4 weeks ago)
Author:
johnjamesjacoby
Message:

Date/Time: Prevent a PHP exception when inserting posts with a partially malformed post_date.

This commit updates wp_resolve_post_date() to use a regular expression for parsing the date string into year, month, and day matches. This approach handles missing leading zeros more reliably than substr() while remaining performant (see #57683).

It also adds checks and type-casting to wp_checkdate() variables before passing them into PHP's checkdate() function to avoid the potential for a TypeError in PHP 8 and newer (see #54186).

Lastly, it includes 2 new unit tests (with data providers) to cover an array of valid and invalid date formats (YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, ISO 8601, RSS, leap years, malformed inputs, etc...)

Props alordiel, desrosj, johnbillion, johnjamesjacoby, johnregan3, modius5150, nacin, pbearne.

Fixes #26798.

Location:
trunk
Files:
3 edited

Legend:

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

    r60922 r61172  
    73987398 */
    73997399function wp_checkdate( $month, $day, $year, $source_date ) {
     7400    $checkdate = false;
     7401    if ( is_numeric( $month ) && is_numeric( $day ) && is_numeric( $year ) ) {
     7402        $checkdate = checkdate( (int) $month, (int) $day, (int) $year );
     7403    }
     7404
    74007405    /**
    74017406     * Filters whether the given date is valid for the Gregorian calendar.
     
    74067411     * @param string $source_date Date to check.
    74077412     */
    7408     return apply_filters( 'wp_checkdate', checkdate( $month, $day, $year ), $source_date );
     7413    return apply_filters( 'wp_checkdate', $checkdate, $source_date );
    74097414}
    74107415
  • trunk/src/wp-includes/post.php

    r61171 r61172  
    54325432
    54335433    // Validate the date.
    5434     $month = (int) substr( $post_date, 5, 2 );
    5435     $day   = (int) substr( $post_date, 8, 2 );
    5436     $year  = (int) substr( $post_date, 0, 4 );
    5437 
    5438     $valid_date = wp_checkdate( $month, $day, $year, $post_date );
     5434    preg_match( '/^(\d{4})-(\d{1,2})-(\d{1,2})/', $post_date, $matches );
     5435
     5436    if ( empty( $matches ) || ! is_array( $matches ) || count( $matches ) < 4 ) {
     5437        return false;
     5438    }
     5439
     5440    $valid_date = wp_checkdate( $matches[2], $matches[3], $matches[1], $post_date );
    54395441
    54405442    if ( ! $valid_date ) {
  • trunk/tests/phpunit/tests/post.php

    r60906 r61172  
    815815        remove_filter( 'use_block_editor_for_post', '__return_true' );
    816816    }
     817
     818    /**
     819     * @ticket 26798
     820     *
     821     * @dataProvider data_wp_insert_post_handle_malformed_post_date
     822     *
     823     * The purpose of this test is to ensure that invalid dates do not
     824     * cause PHP errors when wp_insert_post() is called, and that the
     825     * posts are not actually "inserted" (created).
     826     */
     827    public function test_wp_insert_post_handle_malformed_post_date( $input, $expected ) {
     828        $post = array(
     829            'post_author'  => self::$editor_id,
     830            'post_status'  => 'publish',
     831            'post_content' => 'content',
     832            'post_title'   => 'title',
     833            'post_date'    => $input,
     834        );
     835
     836        // Inserting the post should fail gracefully.
     837        $id     = wp_insert_post( $post );
     838        $actual = ! empty( $id );
     839
     840        // Compare if post was (or was not) inserted.
     841        $this->assertSame( $actual, $expected );
     842    }
     843
     844    /**
     845     * @ticket 26798
     846     */
     847    public function data_wp_insert_post_handle_malformed_post_date() {
     848        return array(
     849            array(
     850                '2012-01-01',
     851                true,
     852            ),
     853            // 24-hour time format.
     854            array(
     855                '2012-01-01 13:00:00',
     856                true,
     857            ),
     858            // ISO8601 date with timezone.
     859            array(
     860                '2016-01-16T00:00:00Z',
     861                true,
     862            ),
     863            // ISO8601 date with timezone offset.
     864            array(
     865                '2016-01-16T00:00:00+0100',
     866                true,
     867            ),
     868            // RFC3339 Format.
     869            array(
     870                '1970-01-01T01:00:00+01:00',
     871                true,
     872            ),
     873            // RSS Format
     874            array(
     875                '1970-01-01T01:00:00+0100',
     876                true,
     877            ),
     878            // Leap year.
     879            array(
     880                '2012-02-29',
     881                true,
     882            ),
     883            // Strange formats.
     884            array(
     885                '2012-01-01 0',
     886                true,
     887            ),
     888            array(
     889                '2012-01-01 25:00:00',
     890                true,
     891            ),
     892            array(
     893                '2012-01-01 00:60:00',
     894                true,
     895            ),
     896            // Failures.
     897            array(
     898                '2012-08-0z',
     899                false,
     900            ),
     901            array(
     902                '2012-08-1',
     903                false,
     904            ),
     905            array(
     906                '201-01-08 00:00:00',
     907                false,
     908            ),
     909            array(
     910                '201-01-08 00:60:00',
     911                false,
     912            ),
     913            array(
     914                '201a-01-08 00:00:00',
     915                false,
     916            ),
     917            array(
     918                '2012-1-08 00:00:00',
     919                false,
     920            ),
     921            array(
     922                '2012-31-08 00:00:00',
     923                false,
     924            ),
     925            array(
     926                '2012-01-8 00:00:00',
     927                false,
     928            ),
     929            array(
     930                '2012-01-48 00:00:00',
     931                false,
     932            ),
     933            // Not a leap year.
     934            array(
     935                '2011-02-29',
     936                false,
     937            ),
     938        );
     939    }
     940
     941    /**
     942     * @ticket 26798
     943     *
     944     * @dataProvider data_wp_resolve_post_date_regex
     945     *
     946     * Tests the regex inside of wp_resolve_post_date(), with
     947     * the emphasis on the date format (not the time).
     948     */
     949    public function test_wp_resolve_post_date_regex( $date, $expected ) {
     950        // Attempt to resolve post date.
     951        $actual = wp_resolve_post_date( $date );
     952
     953        // Compare if resolved post date is (or is not) valid.
     954        $this->assertSame( $actual, $expected );
     955    }
     956
     957    /**
     958     * @ticket 26798
     959     */
     960    public function data_wp_resolve_post_date_regex() {
     961        return array(
     962            array(
     963                '2012-01-01',
     964                '2012-01-01',
     965            ),
     966            array(
     967                '2012-01-01 00:00:00',
     968                '2012-01-01 00:00:00',
     969            ),
     970            // ISO8601 date with timezone.
     971            array(
     972                '2016-01-16T00:00:00Z',
     973                '2016-01-16T00:00:00Z',
     974            ),
     975            // ISO8601 date with timezone offset.
     976            array(
     977                '2016-01-16T00:00:00+0100',
     978                '2016-01-16T00:00:00+0100',
     979            ),
     980            // RFC3339 Format.
     981            array(
     982                '1970-01-01T01:00:00+01:00',
     983                '1970-01-01T01:00:00+01:00',
     984            ),
     985            // RSS Format
     986            array(
     987                '1970-01-01T01:00:00+0100',
     988                '1970-01-01T01:00:00+0100',
     989            ),
     990            // 24-hour time format.
     991            array(
     992                '2012-01-01 13:00:00',
     993                '2012-01-01 13:00:00',
     994            ),
     995            array(
     996                '2016-01-16T00:0',
     997                '2016-01-16T00:0',
     998            ),
     999            array(
     1000                '2012-01-01 0',
     1001                '2012-01-01 0',
     1002            ),
     1003            array(
     1004                '2012-01-01 00:00',
     1005                '2012-01-01 00:00',
     1006            ),
     1007            array(
     1008                '2012-01-01 25:00:00',
     1009                '2012-01-01 25:00:00',
     1010            ),
     1011            array(
     1012                '2012-01-01 00:60:00',
     1013                '2012-01-01 00:60:00',
     1014            ),
     1015            array(
     1016                '2012-01-01 00:00:60',
     1017                '2012-01-01 00:00:60',
     1018            ),
     1019            array(
     1020                '201-01-08',
     1021                false,
     1022            ),
     1023            array(
     1024                '201a-01-08',
     1025                false,
     1026            ),
     1027            array(
     1028                '2012-1-08',
     1029                false,
     1030            ),
     1031            array(
     1032                '2012-31-08',
     1033                false,
     1034            ),
     1035            array(
     1036                '2012-01-8',
     1037                false,
     1038            ),
     1039            array(
     1040                '2012-01-48 00:00:00',
     1041                false,
     1042            ),
     1043            // Leap year.
     1044            array(
     1045                '2012-02-29',
     1046                '2012-02-29',
     1047            ),
     1048            array(
     1049                '2011-02-29',
     1050                false,
     1051            ),
     1052        );
     1053    }
    8171054}
Note: See TracChangeset for help on using the changeset viewer.