Repository navigation
Options: Validate transient names (#58903) #14865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1371,6 +1371,50 @@ function wp_filter_default_autoload_value_via_option_size( $autoload, $option, $ | |
| return $autoload; | ||
| } | ||
|
|
||
| /** | ||
| * Determines whether a transient name is a non-empty string. | ||
| * | ||
| * Names consisting solely of whitespace are considered empty. | ||
| * | ||
| * @since 7.3.0 | ||
| * @access private | ||
| * | ||
| * @param mixed $transient Transient name to validate. | ||
| * @return bool True if the transient name is a non-empty string, false otherwise. | ||
| */ | ||
| function _wp_is_valid_transient_name( $transient ) { | ||
| return is_string( $transient ) && '' !== trim( $transient ); | ||
| } | ||
|
|
||
| /** | ||
| * Determines whether a transient name fits within a maximum length. | ||
| * | ||
| * Transients are stored in `option_name`, a VARCHAR(191) column. Site transients | ||
| * share that limit, since they are stored there on single site installations. | ||
| * The longest key written is the timeout, so the name must leave room for the | ||
| * `_transient_timeout_` (19 characters) or `_site_transient_timeout_` (24 characters) | ||
| * prefix: 191 - 19 = 172 and 191 - 24 = 167. | ||
| * | ||
| * @since 7.3.0 | ||
| * @access private | ||
| * | ||
| * @param string $transient Transient name. | ||
| * @param int $max_length Maximum number of characters: 172 for transients, | ||
| * 167 for site transients. | ||
| * @return bool True if the transient name is within the limit, false otherwise. | ||
| */ | ||
| function _wp_is_valid_transient_name_length( $transient, $max_length ) { | ||
| /* | ||
| * VARCHAR lengths count characters, not bytes. strlen() can only overcount a | ||
| * multibyte string, so the slower mb_strlen() is only needed when it exceeds the limit. | ||
| */ | ||
| if ( strlen( $transient ) <= $max_length ) { | ||
| return true; | ||
| } | ||
|
|
||
| return mb_strlen( $transient, 'UTF-8' ) <= $max_length; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function doesn’t do much despite adding a lot of documentation and some indirection. it’s a thin wrapper around if transients are stored in options, then what we want is |
||
| } | ||
|
|
||
| /** | ||
| * Deletes a transient. | ||
| * | ||
|
|
@@ -1426,12 +1470,18 @@ function delete_transient( $transient ) { | |
| * then the return value will be false. | ||
| * | ||
| * @since 2.8.0 | ||
| * @since 7.3.0 Returns false if the transient name is not a non-empty string. | ||
| * | ||
| * @param string $transient Transient name. Expected to not be SQL-escaped. | ||
| * @return mixed Value of transient. | ||
| */ | ||
| function get_transient( $transient ) { | ||
|
|
||
| if ( ! _wp_is_valid_transient_name( $transient ) ) { | ||
| _doing_it_wrong( __FUNCTION__, __( 'Transient names must be non-empty strings.' ), '7.3.0' ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a read operation may not warrant in any case, the error message contradicts the code. |
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Filters the value of an existing transient before it is retrieved. | ||
| * | ||
|
|
@@ -1500,6 +1550,7 @@ function get_transient( $transient ) { | |
| * then it will be serialized before it is set. | ||
| * | ||
| * @since 2.8.0 | ||
| * @since 7.3.0 Returns false if the transient name is invalid. | ||
| * | ||
| * @param string $transient Transient name. Expected to not be SQL-escaped. | ||
| * Must be 172 characters or fewer in length. | ||
|
|
@@ -1510,6 +1561,24 @@ function get_transient( $transient ) { | |
| */ | ||
| function set_transient( $transient, $value, $expiration = 0 ) { | ||
|
|
||
| if ( ! _wp_is_valid_transient_name( $transient ) ) { | ||
| _doing_it_wrong( __FUNCTION__, __( 'Transient names must be non-empty strings.' ), '7.3.0' ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( ! _wp_is_valid_transient_name_length( $transient, 172 ) ) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if ( '' === trim( $transient ) || ! wp_is_valid_option_name( "_transient_{$transient}" ) )this is where the specifics about function wp_parse_transient_name( $name ) {
$trimmed = trim( $name );
return (
'' !== $trimmed &&
! str_starts_with( $trimmed, 'timeout_' ) &&
wp_is_valid_option_name( "_transient_timeout_{$trimmed}" ) &&
wp_is_valid_option_name( "_transient_{$trimmed}" )
)
? $trimmed
: null;
} |
||
| _doing_it_wrong( | ||
| __FUNCTION__, | ||
| sprintf( | ||
| /* translators: %d: Maximum number of characters in a transient name. */ | ||
| __( 'Transient names must be %d characters or fewer.' ), | ||
| 172 | ||
| ), | ||
| '7.3.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| $expiration = (int) $expiration; | ||
|
|
||
| /** | ||
|
|
@@ -2559,6 +2628,7 @@ function delete_site_transient( $transient ) { | |
| * then the return value will be false. | ||
| * | ||
| * @since 2.9.0 | ||
| * @since 7.3.0 Returns false if the transient name is not a non-empty string. | ||
| * | ||
| * @see get_transient() | ||
| * | ||
|
|
@@ -2567,6 +2637,11 @@ function delete_site_transient( $transient ) { | |
| */ | ||
| function get_site_transient( $transient ) { | ||
|
|
||
| if ( ! _wp_is_valid_transient_name( $transient ) ) { | ||
| _doing_it_wrong( __FUNCTION__, __( 'Transient names must be non-empty strings.' ), '7.3.0' ); | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Filters the value of an existing site transient before it is retrieved. | ||
| * | ||
|
|
@@ -2633,6 +2708,7 @@ function get_site_transient( $transient ) { | |
| * then it will be serialized before it is set. | ||
| * | ||
| * @since 2.9.0 | ||
| * @since 7.3.0 Returns false if the transient name is invalid. | ||
| * | ||
| * @see set_transient() | ||
| * | ||
|
|
@@ -2644,6 +2720,24 @@ function get_site_transient( $transient ) { | |
| */ | ||
| function set_site_transient( $transient, $value, $expiration = 0 ) { | ||
|
|
||
| if ( ! _wp_is_valid_transient_name( $transient ) ) { | ||
| _doing_it_wrong( __FUNCTION__, __( 'Transient names must be non-empty strings.' ), '7.3.0' ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( ! _wp_is_valid_transient_name_length( $transient, 167 ) ) { | ||
| _doing_it_wrong( | ||
| __FUNCTION__, | ||
| sprintf( | ||
| /* translators: %d: Maximum number of characters in a transient name. */ | ||
| __( 'Transient names must be %d characters or fewer.' ), | ||
| 167 | ||
| ), | ||
| '7.3.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Filters the value of a specific site transient before it is set. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is some complicated exposition, and yet I wonder if it helps or harms comprehension.
for one, it’s overly-coupled to the later code. the comment mentions the option prefixes, but those don’t appear in this function. that’s a distraction that will lead someone hunting for an explanation of why.
additionally, this highlights an abstraction mismatch in this function, which I think shouldn’t exist. I’ll share more in the next comment.