wishthis/vendor/wp-coding-standards/wpcs/WordPress/Helpers/SanitizationHelperTrait.php

418 lines
15 KiB
PHP
Raw Normal View History

2023-09-19 17:26:46 +00:00
<?php
/**
* WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/
namespace WordPressCS\WordPress\Helpers;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\Context;
use PHPCSUtils\Utils\TextStrings;
use WordPressCS\WordPress\Helpers\ArrayWalkingFunctionsHelper;
use WordPressCS\WordPress\Helpers\ContextHelper;
use WordPressCS\WordPress\Helpers\RulesetPropertyHelper;
use WordPressCS\WordPress\Helpers\UnslashingFunctionsHelper;
/**
* Helper functions and function lists for checking whether a sanitizing function is being used.
*
* Any sniff class which incorporates this trait will automatically support the
* following `public` properties which can be changed from within a custom ruleset:
* - `customSanitizingFunctions`.
* - `customUnslashingSanitizingFunctions`
*
* ---------------------------------------------------------------------------------------------
* This trait is only intended for internal use by WordPressCS and is not part of the public API.
* This also means that it has no promise of backward compatibility. Use at your own risk.
* ---------------------------------------------------------------------------------------------
*
* @internal
*
* @since 3.0.0 The properties in this trait were previously contained partially in the
* `WordPressCS\WordPress\Sniff` class and partially in the `NonceVerificationSniff`
* and the `ValidatedSanitizedInputSniff` classes and have been moved here.
* The pre-existing methods in this trait were previously contained in the
* `WordPressCS\WordPress\Sniff` class and have been moved here.
*/
trait SanitizationHelperTrait {
/**
* Custom list of functions that sanitize the values passed to them.
*
* @since 0.5.0
* @since 3.0.0 Moved from the NonceVerification and the ValidatedSanitizedInput sniff classes to this trait.
*
* @var string[]
*/
public $customSanitizingFunctions = array();
/**
* Custom sanitizing functions that implicitly unslash the values passed to them.
*
* @since 0.5.0
* @since 3.0.0 Moved from the NonceVerification and the ValidatedSanitizedInput sniff classes to this trait.
*
* @var string[]
*/
public $customUnslashingSanitizingFunctions = array();
/**
* Functions that sanitize values.
*
* This list is complementary to the `$unslashingSanitizingFunctions`
* list.
* Sanitizing functions should be added to this list if they do *not*
* implicitly unslash data and to the `$unslashingsanitizingFunctions`
* list if they do.
*
* @since 0.5.0
* @since 0.11.0 Changed from public static to protected non-static.
* @since 3.0.0 - Moved from the Sniff class to this trait.
* - Visibility changed from protected to private.
*
* @var array<string, true>
*/
private $sanitizingFunctions = array(
'_wp_handle_upload' => true,
'esc_url_raw' => true,
'filter_input' => true,
'filter_var' => true,
'hash_equals' => true,
'is_email' => true,
'number_format' => true,
'sanitize_bookmark_field' => true,
'sanitize_bookmark' => true,
'sanitize_email' => true,
'sanitize_file_name' => true,
'sanitize_hex_color_no_hash' => true,
'sanitize_hex_color' => true,
'sanitize_html_class' => true,
'sanitize_meta' => true,
'sanitize_mime_type' => true,
'sanitize_option' => true,
'sanitize_sql_orderby' => true,
'sanitize_term_field' => true,
'sanitize_term' => true,
'sanitize_text_field' => true,
'sanitize_textarea_field' => true,
'sanitize_title_for_query' => true,
'sanitize_title_with_dashes' => true,
'sanitize_title' => true,
'sanitize_url' => true,
'sanitize_user_field' => true,
'sanitize_user' => true,
'validate_file' => true,
'wp_handle_sideload' => true,
'wp_handle_upload' => true,
'wp_kses_allowed_html' => true,
'wp_kses_data' => true,
'wp_kses_one_attr' => true,
'wp_kses_post' => true,
'wp_kses' => true,
'wp_parse_id_list' => true,
'wp_redirect' => true,
'wp_safe_redirect' => true,
'wp_sanitize_redirect' => true,
'wp_strip_all_tags' => true,
);
/**
* Sanitizing functions that implicitly unslash the data passed to them.
*
* This list is complementary to the `$sanitizingFunctions` list.
* Sanitizing functions should be added to this list if they also
* implicitely unslash data and to the `$sanitizingFunctions` list
* if they don't.
*
* @since 0.5.0
* @since 0.11.0 Changed from public static to protected non-static.
* @since 3.0.0 - Moved from the Sniff class to this trait.
* - Visibility changed from protected to private.
*
* @var array<string, bool>
*/
private $unslashingSanitizingFunctions = array(
'absint' => true,
'boolval' => true,
'count' => true,
'doubleval' => true,
'floatval' => true,
'intval' => true,
'sanitize_key' => true,
'sizeof' => true,
);
/**
* Cache of previously added custom functions.
*
* Prevents having to do the same merges over and over again.
*
* @since 0.4.0
* @since 0.11.0 - Changed from public static to protected non-static.
* - Changed the format from simple bool to array.
* @since 3.0.0 - Moved from the NonceVerification and the ValidatedSanitizedInput sniff classes to this class.
* - Visibility changed from protected to private.
*
* @var array<string, string[]>
*/
private $addedCustomSanitizingFunctions = array(
'sanitize' => array(),
'unslashsanitize' => array(),
);
/**
* Combined list of WP/PHP native and custom sanitizing functions.
*
* @since 3.0.0
*
* @var array<string, bool>
*/
private $allSanitizingFunctions = array();
/**
* Combined list of WP/PHP native and custom sanitizing and unslashing functions.
*
* @since 3.0.0
*
* @var array<string, bool>
*/
private $allUnslashingSanitizingFunctions = array();
/**
* Retrieve a list of all known sanitizing functions.
*
* @since 3.0.0
*
* @return array<string, bool>
*/
final public function get_sanitizing_functions() {
if ( array() === $this->allSanitizingFunctions
|| $this->customSanitizingFunctions !== $this->addedCustomSanitizingFunctions['sanitize']
) {
$this->allSanitizingFunctions = RulesetPropertyHelper::merge_custom_array(
$this->customSanitizingFunctions,
$this->sanitizingFunctions
);
$this->addedCustomSanitizingFunctions['sanitize'] = $this->customSanitizingFunctions;
}
return $this->allSanitizingFunctions;
}
/**
* Retrieve a list of all known sanitizing and unslashing functions.
*
* @since 3.0.0
*
* @return array<string, bool>
*/
final public function get_sanitizing_and_unslashing_functions() {
if ( array() === $this->allUnslashingSanitizingFunctions
|| $this->customUnslashingSanitizingFunctions !== $this->addedCustomSanitizingFunctions['unslashsanitize']
) {
$this->allUnslashingSanitizingFunctions = RulesetPropertyHelper::merge_custom_array(
$this->customUnslashingSanitizingFunctions,
$this->unslashingSanitizingFunctions
);
$this->addedCustomSanitizingFunctions['unslashsanitize'] = $this->customUnslashingSanitizingFunctions;
}
return $this->allUnslashingSanitizingFunctions;
}
/**
* Check if a particular function is regarded as a sanitizing function.
*
* @since 3.0.0
*
* @param string $functionName The name of the function to check.
*
* @return bool
*/
final public function is_sanitizing_function( $functionName ) {
return isset( $this->get_sanitizing_functions()[ strtolower( $functionName ) ] );
}
/**
* Check if a particular function is regarded as a sanitizing and unslashing function.
*
* @since 3.0.0
*
* @param string $functionName The name of the function to check.
*
* @return bool
*/
final public function is_sanitizing_and_unslashing_function( $functionName ) {
return isset( $this->get_sanitizing_and_unslashing_functions()[ strtolower( $functionName ) ] );
}
/**
* Check if something is only being sanitized.
*
* @since 0.5.0
* @since 3.0.0 - Moved from the Sniff class to this class.
* - The method visibility was changed from `protected` to `public`.
* - The `$phpcsFile` parameter was added.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The index of the token in the stack.
*
* @return bool Whether the token is only within a sanitization.
*/
final public function is_only_sanitized( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
// If it isn't being sanitized at all.
if ( ! $this->is_sanitized( $phpcsFile, $stackPtr ) ) {
return false;
}
// If the token isn't in parentheses, we know the value must have only been casted, because
// is_sanitized() would have returned `false` otherwise.
if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
return true;
}
// At this point we're expecting the value to have not been casted. If it
// was, it wasn't *only* casted, because it's also in a function.
if ( ContextHelper::is_safe_casted( $phpcsFile, $stackPtr ) ) {
return false;
}
// The only parentheses should belong to the sanitizing function. If there's
// more than one set, this isn't *only* sanitization.
return ( \count( $tokens[ $stackPtr ]['nested_parenthesis'] ) === 1 );
}
/**
* Check if something is being sanitized.
*
* @since 0.5.0
* @since 3.0.0 - Moved from the Sniff class to this class.
* - The method visibility was changed from `protected` to `public`.
* - The `$phpcsFile` parameter was added.
* - The $require_unslash parameter has been changed from
* a boolean toggle to a ?callable $unslash_callback parameter to
* allow a sniff calling this method to handle their "unslashing"
* related messaging itself.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The index of the token in the stack.
* @param callable|null $unslash_callback Optional. When passed, this method will check if
* an unslashing function is used on the variable before
* sanitization and if not, the callback will be called
* to handle the missing unslashing.
* The callback will receive the $phpcsFile object and
* the $stackPtr.
* When not passed or `null`, this method will **not**
* check for unslashing issues.
* Defaults to `null` (skip unslashing checks).
*
* @return bool Whether the token is being sanitized.
*/
final public function is_sanitized( File $phpcsFile, $stackPtr, $unslash_callback = null ) {
$tokens = $phpcsFile->getTokens();
$require_unslash = is_callable( $unslash_callback );
if ( isset( $tokens[ $stackPtr ] ) === false ) {
return false;
}
// If the variable is just being unset, the value isn't used at all, so it's safe.
if ( Context::inUnset( $phpcsFile, $stackPtr ) ) {
return true;
}
// First we check if it is being casted to a safe value.
if ( ContextHelper::is_safe_casted( $phpcsFile, $stackPtr ) ) {
return true;
}
// If this isn't within a function call, we know already that it's not safe.
if ( ! isset( $tokens[ $stackPtr ]['nested_parenthesis'] ) ) {
if ( $require_unslash ) {
call_user_func( $unslash_callback, $phpcsFile, $stackPtr );
}
return false;
}
$sanitizing_functions = $this->get_sanitizing_functions();
$sanitizing_functions += $this->get_sanitizing_and_unslashing_functions();
$sanitizing_functions += ArrayWalkingFunctionsHelper::get_functions();
$valid_functions = $sanitizing_functions + UnslashingFunctionsHelper::get_functions();
// Get the function that it's in.
$functionPtr = ContextHelper::is_in_function_call( $phpcsFile, $stackPtr, $valid_functions );
// If this isn't a call to one of the valid functions, it sure isn't a sanitizing function.
if ( false === $functionPtr ) {
if ( true === $require_unslash ) {
call_user_func( $unslash_callback, $phpcsFile, $stackPtr );
}
return false;
}
$functionName = $tokens[ $functionPtr ]['content'];
// Check if an unslashing function is being used.
$is_unslashed = false;
if ( UnslashingFunctionsHelper::is_unslashing_function( $functionName ) ) {
$is_unslashed = true;
// Check whether this function call is wrapped within a sanitizing function.
$higherFunctionPtr = ContextHelper::is_in_function_call( $phpcsFile, $functionPtr, $sanitizing_functions );
// If there is no other valid function being used, this value is unsanitized.
if ( false === $higherFunctionPtr ) {
return false;
}
$functionPtr = $higherFunctionPtr;
$functionName = $tokens[ $functionPtr ]['content'];
}
// Arrays might be sanitized via an array walking function using a callback.
if ( ArrayWalkingFunctionsHelper::is_array_walking_function( $functionName ) ) {
// Get the callback parameter.
$callback = ArrayWalkingFunctionsHelper::get_callback_parameter( $phpcsFile, $functionPtr );
if ( ! empty( $callback ) ) {
/*
* If this is a function callback (not a method callback array) and we're able
* to resolve the function name, do so.
*/
$first_non_empty = $phpcsFile->findNext(
Tokens::$emptyTokens,
$callback['start'],
( $callback['end'] + 1 ),
true
);
if ( false !== $first_non_empty && \T_CONSTANT_ENCAPSED_STRING === $tokens[ $first_non_empty ]['code'] ) {
$functionName = TextStrings::stripQuotes( $tokens[ $first_non_empty ]['content'] );
}
}
}
// If slashing is required, give an error.
if ( false === $is_unslashed
&& true === $require_unslash
&& ! $this->is_sanitizing_and_unslashing_function( $functionName )
) {
call_user_func( $unslash_callback, $phpcsFile, $stackPtr );
}
// Check if this is a sanitizing function.
return ( $this->is_sanitizing_function( $functionName ) || $this->is_sanitizing_and_unslashing_function( $functionName ) );
}
}