Make WordPress Core

Changeset 61172


Ignore:
Timestamp:
11/06/2025 11:35:52 PM (9 months 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.