Repository navigation
XML-RPC: Convert client-supplied dates before calling IXR_Date methods. - #13517
josephscott wants to merge 18 commits into
Conversation
Six methods called getIso() or getTimestamp() on client-supplied date values, assuming they were IXR_Date objects. A client sending a date as a string, array, or other type caused a fatal error. A new wp_xmlrpc_server::_convert_client_date() helper passes an IXR_Date through, converts a string the same way wp.newPost already did, and returns a 400 IXR_Error for anything else. It is used in _insert_post(), wp.editPost, wp.editComment, metaWeblog.newPost, and metaWeblog.editPost. See #66107, #42995. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
westonruter
left a comment
There was a problem hiding this comment.
What may make sense is to start adopting JSON Schema for the XML-RPC methods. It could re-use REST API functions to validate and sanitize the incoming data. This would eliminate a lot of ad hoc type checking. Maybe this is out of scope here.
Check to make sure we got a struct in the places they are expected
|
I haven't looked at what would be involved in re-using the REST API functions to validate and sanitize the incoming data. Not it scope for this ticket, but it could be an interesting idea to explore. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Invalid date strings can still propagate false from mysql2date() into IXR_Date::parseIso() and trigger PHP fatals, so the new conversion path needs additional validation/guarding.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
This PR hardens XML-RPC date handling by converting client-supplied date values before calling IXR_Date methods, and by validating that “content struct” arguments are associative arrays to avoid fatals on unexpected input.
Changes:
- Add
_convert_client_date()and use it in post/comment insert/edit paths to normalizeIXR_Datevs string dates (and reject non-date types). - Add
_is_content_struct_array()and use it across XML-RPC endpoints to reject non-associative/non-array content structs with a 400 error. - Expand PHPUnit coverage for invalid structs and non-string cached date values, plus add
IXR_Datereturn-type annotations.
| File | Description |
|---|---|
| tests/phpunit/tests/xmlrpc/wp/newPost.php | Adds regression tests for invalid post_date types and non-array content structs in wp.newPost. |
| tests/phpunit/tests/xmlrpc/wp/getPost.php | Adds test ensuring non-string cached dates return empty IXR dates instead of fatals. |
| tests/phpunit/tests/xmlrpc/wp/editPost.php | Adds coverage for string date acceptance, struct validation, and cached non-string modified dates. |
| tests/phpunit/tests/xmlrpc/wp/editComment.php | Adds coverage for string date_created_gmt acceptance and struct validation. |
| tests/phpunit/tests/xmlrpc/mw/newPost.php | Adds coverage for string dateCreated acceptance and struct validation in mw.newPost. |
| tests/phpunit/tests/xmlrpc/mw/editPost.php | Adds coverage for string dateCreated acceptance and struct validation in mw.editPost. |
| src/wp-includes/IXR/class-IXR-date.php | Adds return types/docblocks for IXR_Date::getIso() and ::getXml(). |
| src/wp-includes/class-wp-xmlrpc-server.php | Introduces _is_content_struct_array() and _convert_client_date(), and updates date conversion behavior in multiple XML-RPC methods. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
I think fixing |
A subclass overriding either method without a return type would fatal on class load with an incompatible declaration error. The phpdoc return types are retained for static analysis. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
westonruter
left a comment
There was a problem hiding this comment.
🤖 Comment by Claude Opus 5.5
Review findings on the current head (f3f15b8):
wp.newPostnow rejects a non-stringpost_date_gmteven whenpost_dateis supplied (inline below).- An unparseable date string yields an empty
IXR_Daterather than an error (inline below). This is being punted to a follow-up ticket, as noted in the Copilot thread, but it is flagged here since the consequence on edit methods is more significant than a malformed response. - Native
: stringreturn types onIXR_Date::getIso()andgetXml()would fatal on class load for any subclass that overrides either method without a return type. This has been addressed in f3f15b8, which removes the native return types while keeping the@return/@phpstan-returntags for static analysis.
westonruter
left a comment
There was a problem hiding this comment.
🤖 Comment by Claude Opus 5.5
Follow-up review on b352645. The two findings from the previous review are resolved by 0cad4e3 (a non-date post_date_gmt is again ignored when post_date is supplied) and b352645 (unparseable date strings are rejected).
Three further findings, all low severity. None is a regression relative to trunk: in each case the input either already behaved this way or previously caused a fatal error.
- A malformed
dateTime.iso8601value bypasses the new validation (inline below). - An explicit timezone offset in a string date is dropped (inline below). This likely belongs in the follow-up ticket along with the other date-parsing policy questions.
- A zero
post_date_gmtstring onwp.editPostresults in aninvalid_dateerror (inline below), whereaswp.newPosttreats it as empty.
| * @phpstan-return ( $date is IXR_Date|string ? IXR_Date : IXR_Error ) | ||
| */ | ||
| protected function _convert_client_date( $date ) { | ||
| if ( $date instanceof IXR_Date ) { |
There was a problem hiding this comment.
🤖 Comment by Claude Opus 5.5
The new format check only applies to strings. An IXR_Date is returned as-is, but IXR parses any <dateTime.iso8601> contents into one, so <dateTime.iso8601>garbage</dateTime.iso8601> yields a junk getIso(). In _insert_post(), iso8601_to_datetime() then returns false while edit_date is true, so an edited post's date is silently reset to the current time — the same outcome the string check now prevents.
This is pre-existing on trunk, but since the check now exists, it could apply to both input types for consistency, for example:
if ( is_string( $date ) ) {
$date = $this->_convert_date( $date );
}
if ( ! ( $date instanceof IXR_Date ) ) {
return new IXR_Error( 400, __( 'Dates must be a dateTime.iso8601 value or a string.' ) );
}
// A value that could not be parsed as a date produces an IXR_Date with empty or malformed components.
if ( ! preg_match( '/^\d{8}T\d{2}:\d{2}:\d{2}/', $date->getIso() ) ) {
return new IXR_Error( 400, __( 'Invalid date.' ) );
}
return $date;There was a problem hiding this comment.
Updated to reject any client date that cannot be parsed
| } | ||
|
|
||
| if ( is_string( $date ) ) { | ||
| $date = $this->_convert_date( $date ); |
There was a problem hiding this comment.
🤖 Comment by Claude Opus 5.5
_convert_date() formats with mysql2date( 'Ymd\TH:i:s', … ), which keeps the wall-clock time but discards any offset in the input. For example, 2020-01-01T05:00:00+02:00 becomes 20200101T05:00:00, whereas the correct UTC time is 03:00. For post_date_gmt / date_created_gmt the Z is then appended, storing 05:00 GMT; a local post_date / dateCreated of …05:00:00Z is likewise stored as 05:00 in the site timezone.
This was already the behavior for wp.newPost on trunk, but wp.editPost, metaWeblog.newPost, metaWeblog.editPost, and wp.editComment now accept strings (previously a fatal error), so they now store a shifted date rather than failing. _convert_date() was designed for WordPress's own offset-less MySQL-format dates, so deciding how offsets should be interpreted for local vs. GMT fields seems suited to the follow-up ticket.
There was a problem hiding this comment.
Updated to honor timezone offsets in client date strings
| @@ -1577,10 +1639,20 @@ protected function _insert_post( $user, $content_struct ) { | |||
|
|
|||
| // Do some timestamp voodoo. | |||
| if ( ! empty( $post_data['post_date_gmt'] ) ) { | |||
There was a problem hiding this comment.
🤖 Comment by Claude Opus 5.5
wp.newPost unsets a post_date_gmt of '0000-00-00 00:00:00' as "no GMT date", but wp.editPost passes the struct straight to _insert_post(). There the zero string is non-empty, _convert_client_date() returns IXR_Date( '00000000T00:00:00Z' ) (which passes the format check), and iso8601_to_datetime() produces -0001-11-30 00:00:00 with edit_date = true, so wp_update_post() fails with invalid_date.
On trunk this input was a fatal error, so an error is an improvement, but a client sending the zero GMT date to keep a draft's date unset may reasonably expect the same "empty" handling as wp.newPost. Treating '0000-00-00 00:00:00' as empty here (before the ! empty() checks) would make the two consistent.
There was a problem hiding this comment.
Update to treat an empty date as no date in _insert_post()

https://core.trac.wordpress.org/ticket/66107
AI assistance: Yes
Tool(s): Claude Code
Model(s): Fable 5.1
Used for: Research and experimentation. Guided directions on writing the code changes. It wrote the tests.
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.