@@ -37,6 +37,30 @@ class WP_HTTP_Polling_Sync_Server {
3737 */
3838 const COMPACTION_THRESHOLD = 50 ;
3939
40+ /**
41+ * Maximum total size (in bytes) of the request body.
42+ *
43+ * @since 7.0.0
44+ * @var int
45+ */
46+ const MAX_BODY_SIZE = 16 * MB_IN_BYTES ;
47+
48+ /**
49+ * Maximum number of rooms allowed per request.
50+ *
51+ * @since 7.0.0
52+ * @var int
53+ */
54+ const MAX_ROOMS_PER_REQUEST = 50 ;
55+
56+ /**
57+ * Maximum length of a single update data string.
58+ *
59+ * @since 7.0.0
60+ * @var int
61+ */
62+ const MAX_UPDATE_DATA_SIZE = MB_IN_BYTES ;
63+
4064 /**
4165 * Sync update type: compaction.
4266 *
@@ -96,8 +120,9 @@ public function register_routes(): void {
96120 $ typed_update_args = array (
97121 'properties ' => array (
98122 'data ' => array (
99- 'type ' => 'string ' ,
100- 'required ' => true ,
123+ 'type ' => 'string ' ,
124+ 'required ' => true ,
125+ 'maxLength ' => self ::MAX_UPDATE_DATA_SIZE ,
101126 ),
102127 'type ' => array (
103128 'type ' => 'string ' ,
@@ -149,12 +174,14 @@ public function register_routes(): void {
149174 'methods ' => array ( WP_REST_Server::CREATABLE ),
150175 'callback ' => array ( $ this , 'handle_request ' ),
151176 'permission_callback ' => array ( $ this , 'check_permissions ' ),
177+ 'validate_callback ' => array ( $ this , 'validate_request ' ),
152178 'args ' => array (
153179 'rooms ' => array (
154180 'items ' => array (
155181 'properties ' => $ room_args ,
156182 'type ' => 'object ' ,
157183 ),
184+ 'maxItems ' => self ::MAX_ROOMS_PER_REQUEST ,
158185 'required ' => true ,
159186 'type ' => 'array ' ,
160187 ),
@@ -223,6 +250,30 @@ public function check_permissions( WP_REST_Request $request ) {
223250 return true ;
224251 }
225252
253+ /**
254+ * Validates that the request body does not exceed the maximum allowed size.
255+ *
256+ * Runs as the route-level validate_callback, after per-arg schema
257+ * validation has already passed.
258+ *
259+ * @since 7.0.0
260+ *
261+ * @param WP_REST_Request $request The REST request.
262+ * @return true|WP_Error True if valid, WP_Error if the body is too large.
263+ */
264+ public function validate_request ( WP_REST_Request $ request ) {
265+ $ body = $ request ->get_body ();
266+ if ( is_string ( $ body ) && strlen ( $ body ) > self ::MAX_BODY_SIZE ) {
267+ return new WP_Error (
268+ 'rest_sync_body_too_large ' ,
269+ __ ( 'Request body is too large. ' ),
270+ array ( 'status ' => 413 )
271+ );
272+ }
273+
274+ return true ;
275+ }
276+
226277 /**
227278 * Handles request: stores sync updates and awareness data, and returns
228279 * updates the client is missing.
@@ -278,24 +329,47 @@ public function handle_request( WP_REST_Request $request ) {
278329 *
279330 * @param string $entity_kind The entity kind, e.g. 'postType', 'taxonomy', 'root'.
280331 * @param string $entity_name The entity name, e.g. 'post', 'category', 'site'.
281- * @param string|null $object_id The object ID / entity key for single entities, null for collections.
332+ * @param string|null $object_id The numeric object ID / entity key for single entities, null for collections.
282333 * @return bool True if user has permission, otherwise false.
283334 */
284335 private function can_user_sync_entity_type ( string $ entity_kind , string $ entity_name , ?string $ object_id ): bool {
285- // Handle single post type entities with a defined object ID.
286- if ( 'postType ' === $ entity_kind && is_numeric ( $ object_id ) ) {
287- return current_user_can ( 'edit_post ' , (int ) $ object_id );
336+ if ( is_string ( $ object_id ) ) {
337+ if ( ! ctype_digit ( $ object_id ) ) {
338+ return false ;
339+ }
340+ $ object_id = (int ) $ object_id ;
288341 }
289-
290- // Handle single taxonomy term entities with a defined object ID.
291- if ( 'taxonomy ' === $ entity_kind && is_numeric ( $ object_id ) ) {
292- $ taxonomy = get_taxonomy ( $ entity_name );
293- return isset ( $ taxonomy ->cap ->assign_terms ) && current_user_can ( $ taxonomy ->cap ->assign_terms );
342+ if ( null !== $ object_id && $ object_id <= 0 ) {
343+ // Object ID must be numeric if provided.
344+ return false ;
294345 }
295346
296- // Handle single comment entities with a defined object ID.
297- if ( 'root ' === $ entity_kind && 'comment ' === $ entity_name && is_numeric ( $ object_id ) ) {
298- return current_user_can ( 'edit_comment ' , (int ) $ object_id );
347+ // Validate permissions for the provided object ID.
348+ if ( is_int ( $ object_id ) ) {
349+ // Handle single post type entities with a defined object ID.
350+ if ( 'postType ' === $ entity_kind ) {
351+ if ( get_post_type ( $ object_id ) !== $ entity_name ) {
352+ // Post is not of the specified post type.
353+ return false ;
354+ }
355+ return current_user_can ( 'edit_post ' , $ object_id );
356+ }
357+
358+ // Handle single taxonomy term entities with a defined object ID.
359+ if ( 'taxonomy ' === $ entity_kind ) {
360+ $ term_exists = term_exists ( $ object_id , $ entity_name );
361+ if ( ! is_array ( $ term_exists ) || ! isset ( $ term_exists ['term_id ' ] ) ) {
362+ // Either term doesn't exist OR term is not in specified taxonomy.
363+ return false ;
364+ }
365+
366+ return current_user_can ( 'edit_term ' , $ object_id );
367+ }
368+
369+ // Handle single comment entities with a defined object ID.
370+ if ( 'root ' === $ entity_kind && 'comment ' === $ entity_name ) {
371+ return current_user_can ( 'edit_comment ' , $ object_id );
372+ }
299373 }
300374
301375 // All the remaining checks are for collections. If an object ID is provided,
0 commit comments