Skip to content

Commit 8b5dd7a

Browse files
committed
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. git-svn-id: https://develop.svn.wordpress.org/trunk@61172 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 653e935 commit 8b5dd7a

File tree

3 files changed

+249
-5
lines changed

3 files changed

+249
-5
lines changed

src/wp-includes/functions.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7397,6 +7397,11 @@ function wp_is_stream( $path ) {
73977397
* @return bool True if valid date, false if not valid date.
73987398
*/
73997399
function 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.
74027407
*
@@ -7405,7 +7410,7 @@ function wp_checkdate( $month, $day, $year, $source_date ) {
74057410
* @param bool $checkdate Whether the given date is valid.
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

74117416
/**

src/wp-includes/post.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5431,11 +5431,13 @@ function wp_resolve_post_date( $post_date = '', $post_date_gmt = '' ) {
54315431
}
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 );
5434+
preg_match( '/^(\d{4})-(\d{1,2})-(\d{1,2})/', $post_date, $matches );
54375435

5438-
$valid_date = wp_checkdate( $month, $day, $year, $post_date );
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 ) {
54415443
return false;

tests/phpunit/tests/post.php

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,4 +814,241 @@ public function test_use_block_editor_for_post() {
814814
$this->assertTrue( use_block_editor_for_post( $restless_post_id ) );
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
}

0 commit comments

Comments
 (0)