Make WordPress Core


Ignore:
Timestamp:
08/03/2022 02:34:58 PM (4 years ago)
Author:
SergeyBiryukov
Message:

Cache API: Validate cache key in WP_Object_Cache methods.

Some plugins may call the wp_cache_*() functions with an empty string, false, or null as cache key, usually as a result of not checking the return value of another function that's used as the key.

Previously, this was silently failing, leading to odd behavior at best and often breakage due to key collisions.

A valid cache key must be either an integer number or a non-empty string.

This commit introduces a quick type check on the given cache keys and adds a _doing_it_wrong() message that should help plugin developers to notice these issues quicker.

Includes:

  • A check in update_user_caches() and clean_user_cache() to make sure user email is not empty before being cached or removed from cache, as it is technically possible to create a user with empty email via wp_insert_user().
  • Some minor cleanup in unit tests where the email was passed to wp_insert_user() incorrectly or was unintentionally reset.

Props tillkruess, malthert, dd32, spacedmonkey, flixos90, peterwilsoncc, SergeyBiryukov.
Fixes #56198.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-object-cache.php

    r53767 r53818  
    131131
    132132        /**
     133         * Serves as a utility function to determine whether a key is valid.
     134         *
     135         * @since 6.1.0
     136         *
     137         * @param int|string $key Cache key to check for validity.
     138         * @return bool Whether the key is valid.
     139         */
     140        protected function is_valid_key( $key ) {
     141                if ( is_int( $key ) ) {
     142                        return true;
     143                }
     144
     145                if ( is_string( $key ) && trim( $key ) !== '' ) {
     146                        return true;
     147                }
     148
     149                $type = gettype( $key );
     150
     151                if ( ! function_exists( '__' ) ) {
     152                        wp_load_translations_early();
     153                }
     154
     155                $message = is_string( $key )
     156                        ? __( 'Cache key must not be an empty string.' )
     157                        /* translators: %s: The type of the given cache key. */
     158                        : sprintf( __( 'Cache key must be integer or non-empty string, %s given.' ), $type );
     159
     160                _doing_it_wrong(
     161                        sprintf( '%s::%s', __CLASS__, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['function'] ),
     162                        $message,
     163                        '6.1.0'
     164                );
     165
     166                return false;
     167        }
     168
     169        /**
    133170         * Serves as a utility function to determine whether a key exists in the cache.
    134171         *
     
    164201                }
    165202
     203                if ( ! $this->is_valid_key( $key ) ) {
     204                        return false;
     205                }
     206
    166207                if ( empty( $group ) ) {
    167208                        $group = 'default';
     
    217258         */
    218259        public function replace( $key, $data, $group = 'default', $expire = 0 ) {
     260                if ( ! $this->is_valid_key( $key ) ) {
     261                        return false;
     262                }
     263
    219264                if ( empty( $group ) ) {
    220265                        $group = 'default';
     
    246291         *
    247292         * @since 2.0.0
     293         * @since 6.1.0 Returns false if cache key is invalid.
    248294         *
    249295         * @param int|string $key    What to call the contents in the cache.
     
    251297         * @param string     $group  Optional. Where to group the cache contents. Default 'default'.
    252298         * @param int        $expire Optional. Not used.
    253          * @return true Always returns true.
     299         * @return bool True if contents were set, false if key is invalid.
    254300         */
    255301        public function set( $key, $data, $group = 'default', $expire = 0 ) {
     302                if ( ! $this->is_valid_key( $key ) ) {
     303                        return false;
     304                }
     305
    256306                if ( empty( $group ) ) {
    257307                        $group = 'default';
     
    311361         */
    312362        public function get( $key, $group = 'default', $force = false, &$found = null ) {
     363                if ( ! $this->is_valid_key( $key ) ) {
     364                        return false;
     365                }
     366
    313367                if ( empty( $group ) ) {
    314368                        $group = 'default';
     
    369423         */
    370424        public function delete( $key, $group = 'default', $deprecated = false ) {
     425                if ( ! $this->is_valid_key( $key ) ) {
     426                        return false;
     427                }
     428
    371429                if ( empty( $group ) ) {
    372430                        $group = 'default';
     
    417475         */
    418476        public function incr( $key, $offset = 1, $group = 'default' ) {
     477                if ( ! $this->is_valid_key( $key ) ) {
     478                        return false;
     479                }
     480
    419481                if ( empty( $group ) ) {
    420482                        $group = 'default';
     
    456518         */
    457519        public function decr( $key, $offset = 1, $group = 'default' ) {
     520                if ( ! $this->is_valid_key( $key ) ) {
     521                        return false;
     522                }
     523
    458524                if ( empty( $group ) ) {
    459525                        $group = 'default';
Note: See TracChangeset for help on using the changeset viewer.