diff --git a/Puc/v5p0/DebugBar/Extension.php b/Puc/v5p0/DebugBar/Extension.php index d085068..b7b27d1 100644 --- a/Puc/v5p0/DebugBar/Extension.php +++ b/Puc/v5p0/DebugBar/Extension.php @@ -66,14 +66,16 @@ if ( !class_exists(Extension::class, false) ): * the update checking process works as expected. */ public function ajaxCheckNow() { - if ( $_POST['uid'] !== $this->updateChecker->getUniqueName('uid') ) { + //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is checked in preAjaxRequest(). + if ( !isset($_POST['uid']) || ($_POST['uid'] !== $this->updateChecker->getUniqueName('uid')) ) { return; } $this->preAjaxRequest(); $update = $this->updateChecker->checkForUpdates(); if ( $update !== null ) { echo "An update is available:"; - echo '
', htmlentities(print_r($update, true)), '
'; + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- For debugging output. + echo '
', esc_html(print_r($update, true)), '
'; } else { echo 'No updates found.'; } @@ -85,7 +87,7 @@ if ( !class_exists(Extension::class, false) ): foreach (array_values($errors) as $num => $item) { $wpError = $item['error']; /** @var \WP_Error $wpError */ - printf('

%d) %s

', $num + 1, esc_html($wpError->get_error_message())); + printf('

%d) %s

', intval($num + 1), esc_html($wpError->get_error_message())); echo '
'; printf('
Error code:
%s
', esc_html($wpError->get_error_code())); @@ -107,8 +109,8 @@ if ( !class_exists(Extension::class, false) ): //Status code. printf( '
HTTP status:
%d %s
', - wp_remote_retrieve_response_code($item['httpResponse']), - wp_remote_retrieve_response_message($item['httpResponse']) + esc_html(wp_remote_retrieve_response_code($item['httpResponse'])), + esc_html(wp_remote_retrieve_response_message($item['httpResponse'])) ); //Headers. @@ -147,7 +149,9 @@ if ( !class_exists(Extension::class, false) ): } check_ajax_referer('puc-ajax'); + //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting -- Part of a debugging feature. error_reporting(E_ALL); + //phpcs:ignore WordPress.PHP.IniSet.display_errors_Blacklisted @ini_set('display_errors', 'On'); } diff --git a/Puc/v5p0/DebugBar/PluginExtension.php b/Puc/v5p0/DebugBar/PluginExtension.php index 975a02f..feaf2ff 100644 --- a/Puc/v5p0/DebugBar/PluginExtension.php +++ b/Puc/v5p0/DebugBar/PluginExtension.php @@ -20,14 +20,16 @@ if ( !class_exists(PluginExtension::class, false) ): * Request plugin info and output it. */ public function ajaxRequestInfo() { - if ( $_POST['uid'] !== $this->updateChecker->getUniqueName('uid') ) { + //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is checked in preAjaxRequest(). + if ( !isset($_POST['uid']) || ($_POST['uid'] !== $this->updateChecker->getUniqueName('uid')) ) { return; } $this->preAjaxRequest(); $info = $this->updateChecker->requestInfo(); if ( $info !== null ) { echo 'Successfully retrieved plugin info from the metadata URL:'; - echo '
', htmlentities(print_r($info, true)), '
'; + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- For debugging output. + echo '
', esc_html(print_r($info, true)), '
'; } else { echo 'Failed to retrieve plugin info from the metadata URL.'; } diff --git a/Puc/v5p0/Metadata.php b/Puc/v5p0/Metadata.php index eab684b..92563fb 100644 --- a/Puc/v5p0/Metadata.php +++ b/Puc/v5p0/Metadata.php @@ -36,16 +36,18 @@ if ( !class_exists(Metadata::class, false) ): /** @var \StdClass $apiResponse */ $apiResponse = json_decode($json); if ( empty($apiResponse) || !is_object($apiResponse) ){ - $errorMessage = "Failed to parse update metadata. Try validating your .json file with http://jsonlint.com/"; + $errorMessage = "Failed to parse update metadata. Try validating your .json file with https://jsonlint.com/"; do_action('puc_api_error', new WP_Error('puc-invalid-json', $errorMessage)); - trigger_error($errorMessage, E_USER_NOTICE); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. + trigger_error(esc_html($errorMessage), E_USER_NOTICE); return false; } $valid = $target->validateMetadata($apiResponse); if ( is_wp_error($valid) ){ do_action('puc_api_error', $valid); - trigger_error($valid->get_error_message(), E_USER_NOTICE); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. + trigger_error(esc_html($valid->get_error_message()), E_USER_NOTICE); return false; } diff --git a/Puc/v5p0/OAuthSignature.php b/Puc/v5p0/OAuthSignature.php index 79b0d1e..9942709 100644 --- a/Puc/v5p0/OAuthSignature.php +++ b/Puc/v5p0/OAuthSignature.php @@ -26,10 +26,10 @@ if ( !class_exists(OAuthSignature::class, false) ): $parameters = array(); //Parse query parameters. - $query = parse_url($url, PHP_URL_QUERY); + $query = wp_parse_url($url, PHP_URL_QUERY); if ( !empty($query) ) { parse_str($query, $parsedParams); - if ( is_array($parameters) ) { + if ( is_array($parsedParams) ) { $parameters = $parsedParams; } //Remove the query string from the URL. We'll replace it later. @@ -91,7 +91,8 @@ if ( !class_exists(OAuthSignature::class, false) ): } } if ( $rand === null ) { - $rand = mt_rand(); + //phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand + $rand = function_exists('wp_rand') ? wp_rand() : mt_rand(); } return md5($mt . '_' . $rand); diff --git a/Puc/v5p0/Plugin/Ui.php b/Puc/v5p0/Plugin/Ui.php index fce1750..880e31b 100644 --- a/Puc/v5p0/Plugin/Ui.php +++ b/Puc/v5p0/Plugin/Ui.php @@ -206,8 +206,9 @@ if ( !class_exists('Ui', false) ): * You can change the result message by using the "puc_manual_check_message-$slug" filter. */ public function displayManualCheckResult() { + //phpcs:disable WordPress.Security.NonceVerification.Recommended -- Just displaying a message. if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->updateChecker->slug) ) { - $status = strval($_GET['puc_update_check_result']); + $status = sanitize_key($_GET['puc_update_check_result']); $title = $this->updateChecker->getInstalledPackage()->getPluginTitle(); $noticeClass = 'updated notice-success'; $details = ''; @@ -223,16 +224,29 @@ if ( !class_exists('Ui', false) ): $details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient)); delete_site_transient($this->manualCheckErrorTransient); } else { - $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status)); + $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), $status); $noticeClass = 'error notice-error'; } + + $message = esc_html($message); + + //Plugins can replace the message with their own, including adding HTML. + $message = apply_filters( + $this->updateChecker->getUniqueName('manual_check_message'), + $message, + $status + ); + printf( '

%s

%s
', - $noticeClass, - apply_filters($this->updateChecker->getUniqueName('manual_check_message'), $message, $status), + esc_attr($noticeClass), + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Was escaped above, and plugins can add HTML. + $message, + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Contains HTML. Content should already be escaped. $details ); } + //phpcs:enable } /** @@ -259,8 +273,8 @@ if ( !class_exists('Ui', false) ): /** @var \WP_Error $wpError */ $output .= sprintf( $formatString, - $wpError->get_error_message(), - $wpError->get_error_code() + esc_html($wpError->get_error_message()), + esc_html($wpError->get_error_code()) ); } if ( $showAsList ) { diff --git a/Puc/v5p0/Plugin/UpdateChecker.php b/Puc/v5p0/Plugin/UpdateChecker.php index 5f61f25..568f091 100644 --- a/Puc/v5p0/Plugin/UpdateChecker.php +++ b/Puc/v5p0/Plugin/UpdateChecker.php @@ -57,8 +57,8 @@ if ( !class_exists(UpdateChecker::class, false) ): if ( $slugUsedBy ) { $this->triggerError(sprintf( 'Plugin slug "%s" is already in use by %s. Slugs must be unique.', - htmlentities($slug), - htmlentities($slugUsedBy) + $slug, + $slugUsedBy ), E_USER_ERROR); } add_filter($slugCheckFilter, array($this, 'getAbsolutePath')); diff --git a/Puc/v5p0/PucFactory.php b/Puc/v5p0/PucFactory.php index 4b52a95..a02cc1f 100644 --- a/Puc/v5p0/PucFactory.php +++ b/Puc/v5p0/PucFactory.php @@ -105,13 +105,14 @@ if ( !class_exists(PucFactory::class, false) ): $checkerClass = self::getCompatibleClassVersion($checkerClass); if ( $checkerClass === null ) { + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error trigger_error( - sprintf( + esc_html(sprintf( 'PUC %s does not support updates for %ss %s', - htmlentities(self::$latestCompatibleVersion), + self::$latestCompatibleVersion, strtolower($type), - $service ? ('hosted on ' . htmlentities($service)) : 'using JSON metadata' - ), + $service ? ('hosted on ' . $service) : 'using JSON metadata' + )), E_USER_ERROR ); } @@ -123,11 +124,12 @@ if ( !class_exists(PucFactory::class, false) ): //VCS checker + an API client. $apiClass = self::getCompatibleClassVersion($apiClass); if ( $apiClass === null ) { - trigger_error(sprintf( + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + trigger_error(esc_html(sprintf( 'PUC %s does not support %s', - htmlentities(self::$latestCompatibleVersion), - htmlentities($service) - ), E_USER_ERROR); + self::$latestCompatibleVersion, + $service + )), E_USER_ERROR); } return new $checkerClass( @@ -251,8 +253,8 @@ if ( !class_exists(PucFactory::class, false) ): $service = null; //Which hosting service does the URL point to? - $host = (string)(parse_url($metadataUrl, PHP_URL_HOST)); - $path = (string)(parse_url($metadataUrl, PHP_URL_PATH)); + $host = (string)(wp_parse_url($metadataUrl, PHP_URL_HOST)); + $path = (string)(wp_parse_url($metadataUrl, PHP_URL_PATH)); //Check if the path looks like "/user-name/repository". //For GitLab.com it can also be "/user/group1/group2/.../repository". diff --git a/Puc/v5p0/Scheduler.php b/Puc/v5p0/Scheduler.php index 58c43ae..aef5914 100644 --- a/Puc/v5p0/Scheduler.php +++ b/Puc/v5p0/Scheduler.php @@ -57,7 +57,15 @@ if ( !class_exists(Scheduler::class, false) ): if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) { //Randomly offset the schedule to help prevent update server traffic spikes. Without this //most checks may happen during times of day when people are most likely to install new plugins. - $firstCheckTime = time() - rand(0, max($this->checkPeriod * 3600 - 15 * 60, 1)); + $upperLimit = max($this->checkPeriod * 3600 - 15 * 60, 1); + if ( function_exists('wp_rand') ) { + $randomOffset = wp_rand(0, $upperLimit); + } else { + //This constructor may be called before wp_rand() is available. + //phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand + $randomOffset = rand(0, $upperLimit); + } + $firstCheckTime = time() - $randomOffset; $firstCheckTime = apply_filters( $this->updateChecker->getUniqueName('first_check_time'), $firstCheckTime diff --git a/Puc/v5p0/UpdateChecker.php b/Puc/v5p0/UpdateChecker.php index 3be594b..0da5db5 100644 --- a/Puc/v5p0/UpdateChecker.php +++ b/Puc/v5p0/UpdateChecker.php @@ -210,7 +210,7 @@ if ( !class_exists(UpdateChecker::class, false) ): */ public function allowMetadataHost($allow, $host) { if ( $this->cachedMetadataHost === 0 ) { - $this->cachedMetadataHost = parse_url($this->metadataUrl, PHP_URL_HOST); + $this->cachedMetadataHost = wp_parse_url($this->metadataUrl, PHP_URL_HOST); } if ( is_string($this->cachedMetadataHost) && (strtolower($host) === strtolower($this->cachedMetadataHost)) ) { @@ -432,7 +432,8 @@ if ( !class_exists(UpdateChecker::class, false) ): */ public function triggerError($message, $errorType) { if ( $this->isDebugModeEnabled() ) { - trigger_error($message, $errorType); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- Only happens in debug mode. + trigger_error(esc_html($message), $errorType); } } diff --git a/Puc/v5p0/Vcs/BitBucketApi.php b/Puc/v5p0/Vcs/BitBucketApi.php index e73ccf3..13677db 100644 --- a/Puc/v5p0/Vcs/BitBucketApi.php +++ b/Puc/v5p0/Vcs/BitBucketApi.php @@ -24,7 +24,7 @@ if ( !class_exists(BitBucketApi::class, false) ): private $repository; public function __construct($repositoryUrl, $credentials = array()) { - $path = parse_url($repositoryUrl, PHP_URL_PATH); + $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); if ( preg_match('@^/?(?P[^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { $this->username = $matches['username']; $this->repository = $matches['repository']; diff --git a/Puc/v5p0/Vcs/GitHubApi.php b/Puc/v5p0/Vcs/GitHubApi.php index fc6208a..8682c8b 100644 --- a/Puc/v5p0/Vcs/GitHubApi.php +++ b/Puc/v5p0/Vcs/GitHubApi.php @@ -46,7 +46,7 @@ if ( !class_exists(GitHubApi::class, false) ): private $downloadFilterAdded = false; public function __construct($repositoryUrl, $accessToken = null) { - $path = parse_url($repositoryUrl, PHP_URL_PATH); + $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); if ( preg_match('@^/?(?P[^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { $this->userName = $matches['username']; $this->repositoryName = $matches['repository']; @@ -371,6 +371,7 @@ if ( !class_exists(GitHubApi::class, false) ): */ public function addHttpRequestFilter($result) { if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { + //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args -- The callback doesn't change the timeout. add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2); add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4); $this->downloadFilterAdded = true; diff --git a/Puc/v5p0/Vcs/GitLabApi.php b/Puc/v5p0/Vcs/GitLabApi.php index 5de56a5..b43b4ce 100644 --- a/Puc/v5p0/Vcs/GitLabApi.php +++ b/Puc/v5p0/Vcs/GitLabApi.php @@ -41,18 +41,18 @@ if ( !class_exists(GitLabApi::class, false) ): public function __construct($repositoryUrl, $accessToken = null, $subgroup = null) { //Parse the repository host to support custom hosts. - $port = parse_url($repositoryUrl, PHP_URL_PORT); + $port = wp_parse_url($repositoryUrl, PHP_URL_PORT); if ( !empty($port) ) { $port = ':' . $port; } - $this->repositoryHost = parse_url($repositoryUrl, PHP_URL_HOST) . $port; + $this->repositoryHost = wp_parse_url($repositoryUrl, PHP_URL_HOST) . $port; if ( $this->repositoryHost !== 'gitlab.com' ) { - $this->repositoryProtocol = parse_url($repositoryUrl, PHP_URL_SCHEME); + $this->repositoryProtocol = wp_parse_url($repositoryUrl, PHP_URL_SCHEME); } //Find the repository information - $path = parse_url($repositoryUrl, PHP_URL_PATH); + $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); if ( preg_match('@^/?(?P[^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { $this->userName = $matches['username']; $this->repositoryName = $matches['repository'];