|
Server : Apache System : Linux s1230 5.15.0-139-generic #149~20.04.1 SMP Tue Jul 14 11:21:49 UTC 2026 x86_64 User : p141464 ( 418825) PHP Version : 7.4.33.12 Disable Function : NONE Directory : /html/relaunch-kmu/wp-content/plugins/real-cookie-banner/inc/rest/ |
Upload File : |
<?php
namespace DevOwl\RealCookieBanner\rest;
use DevOwl\RealCookieBanner\Vendor\DevOwl\CookieConsentManagement\frontend\SavingConsentViaRestApiEndpointChecker;
use DevOwl\RealCookieBanner\Vendor\MatthiasWeb\Utils\Service;
use DevOwl\RealCookieBanner\base\UtilsProvider;
use DevOwl\RealCookieBanner\Core;
use DevOwl\RealCookieBanner\scanner\AutomaticScanStarter;
use DevOwl\RealCookieBanner\scanner\Query;
use DevOwl\RealCookieBanner\view\Notices;
use DevOwl\RealCookieBanner\view\Scanner as ViewScanner;
use DevOwl\RealCookieBanner\Vendor\DevOwl\ServiceCloudConsumer\templates\AbstractTemplate;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
// @codeCoverageIgnoreStart
\defined('ABSPATH') or die('No script kiddies please!');
// Avoid direct file request
// @codeCoverageIgnoreEnd
/**
* Scanner API
* @internal
*/
class Scanner
{
use UtilsProvider;
/**
* C'tor.
*/
private function __construct()
{
// Silence is golden.
}
/**
* Register endpoints.
*/
public function rest_api_init()
{
$namespace = Service::getNamespace($this);
\register_rest_route($namespace, '/scanner/queue', ['methods' => 'POST', 'callback' => [$this, 'routeAddQueue'], 'permission_callback' => [$this, 'permission_callback'], 'args' => ['purgeUnused' => ['type' => 'boolean', 'default' => \false]]]);
\register_rest_route($namespace, '/scanner/result/templates', ['methods' => 'GET', 'callback' => [$this, 'routeResultTemplates'], 'permission_callback' => [$this, 'permission_callback']]);
\register_rest_route($namespace, '/scanner/result/externals', ['methods' => 'GET', 'callback' => [$this, 'routeResultExternalUrls'], 'permission_callback' => [$this, 'permission_callback']]);
\register_rest_route($namespace, '/scanner/result/externals/host/(?P<host>[a-zA-Z0-9\\._-]+)', ['methods' => 'GET', 'callback' => [$this, 'routeResultAllExternalUrlsByHost'], 'permission_callback' => [$this, 'permission_callback']]);
\register_rest_route($namespace, '/scanner/result/externals/template/(?P<template>[a-zA-Z0-9_-]+)', ['methods' => 'GET', 'callback' => [$this, 'routeResultAllExternalUrlsByTemplate'], 'permission_callback' => [$this, 'permission_callback']]);
\register_rest_route($namespace, '/scanner/result/ignore', ['methods' => 'POST', 'callback' => [$this, 'routeResultIgnorePost'], 'permission_callback' => [$this, 'permission_callback'], 'args' => ['type' => ['type' => 'string', 'enum' => ['template', 'host'], 'required' => \true], 'value' => ['type' => 'string', 'required' => \true], 'ignored' => ['type' => 'boolean', 'required' => \true]]]);
\register_rest_route($namespace, '/scanner/result/markup/(?P<id>\\d+)', ['methods' => 'GET', 'callback' => [$this, 'routeResultMarkupById'], 'permission_callback' => [$this, 'permission_callback']]);
\register_rest_route($namespace, '/scanner/scan-without-login', [
'methods' => 'GET',
'callback' => [$this, 'routeScanWithoutLogin'],
// Same gate as real-queue workers: editors already run non-loopback client scans.
'permission_callback' => [$this, 'permission_callback_queue'],
'args' => ['url' => ['type' => 'string', 'required' => \true], 'jobId' => ['type' => 'number', 'required' => \true]],
]);
}
/**
* Check if user is allowed to call this service requests.
*/
public function permission_callback()
{
return \current_user_can(Core::MANAGE_MIN_CAPABILITY);
}
/**
* Same capability gate as real-queue (`edit_posts` ∪ registered caps).
*/
public function permission_callback_queue()
{
return Core::getInstance()->getRealQueue()->currentUserAllowedToQuery();
}
/**
* See API docs.
*
* @param WP_REST_Request $request
*
* @api {post} /real-cookie-banner/v1/scanner/queue Add URLs to the scanner queue
* @apiHeader {string} X-WP-Nonce
* @apiParam {string[]} urls
* @apiParam {boolean} [purgeUnused] If `true`, the difference of the previous scanned URLs gets
* automatically purged if they do no longer exist in the URLs (pass only if you have the complete sitemap!)
* @apiName AddQueue
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeAddQueue($request)
{
$urls = $request->get_param('urls');
if (!\is_array($urls)) {
return new WP_Error('rest_arg_not_string_array', null, ['status' => 422]);
}
// Disable automatic scanning
\update_option(AutomaticScanStarter::OPTION_NAME, AutomaticScanStarter::STATUS_STARTED);
$added = Core::getInstance()->getScanner()->addUrlsToQueue(\array_unique($urls), $request->get_param('purgeUnused'));
// Remove explicit notices of external URLs which require a manual scan
Core::getInstance()->getNotices()->dismissScannerExplicitExternalUrlCoverageNotice(Notices::SCANNER_EXPLICIT_EXTERNAL_URL_COVERAGE_STATE_MANUAL_SCAN_REQUIRED);
return new WP_REST_Response(['added' => $added]);
}
/**
* See API docs.
*
* @api {get} /real-cookie-banner/v1/scanner/result/templates Get predefined templates for blocker which got scanned through our scanner
* @apiHeader {string} X-WP-Nonce
* @apiName TemplatesResult
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeResultTemplates()
{
return new WP_REST_Response(['items' => (object) AbstractTemplate::toArrays(Core::getInstance()->getScanner()->getQuery()->getScannedTemplates())]);
}
/**
* See API docs.
*
* @api {get} /real-cookie-banner/v1/scanner/result/externals Get external URLs which got scanned through our scanner
* @apiHeader {string} X-WP-Nonce
* @apiName ExternalUrlResults
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeResultExternalUrls()
{
$results = Core::getInstance()->getScanner()->getQuery()->getScannedExternalUrls();
return new WP_REST_Response(['items' => (object) $results]);
}
/**
* See API docs.
*
* @param WP_REST_Request $request
*
* @api {get} /real-cookie-banner/v1/scanner/result/externals/host/:host Get all blocked URLs for a given host
* @apiHeader {string} X-WP-Nonce
* @apiParam {string} host Replace dots with underscores as some security plugins do not allow hosts in URL path
* @apiName AllExternalUrlsByHost
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeResultAllExternalUrlsByHost($request)
{
$host = \str_replace('_', '.', $request->get_param('host'));
$result = Core::getInstance()->getScanner()->getQuery()->getAllScannedExternalUrlsBy('host', $host);
return \count($result) > 0 ? new WP_REST_Response(['items' => $result]) : new WP_Error('rest_not_found', 'Host not found. Did you forgot to replace dots with underscores?');
}
/**
* See API docs.
*
* @param WP_REST_Request $request
*
* @api {get} /real-cookie-banner/v1/scanner/result/externals/template/:template Get all blocked URLs for a given template identifier
* @apiHeader {string} X-WP-Nonce
* @apiParam {string} template
* @apiName AllExternalUrlsByTemplate
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeResultAllExternalUrlsByTemplate($request)
{
$result = Core::getInstance()->getScanner()->getQuery()->getAllScannedExternalUrlsBy('template', $request->get_param('template'));
return \count($result) > 0 ? new WP_REST_Response(['items' => $result]) : new WP_Error('rest_not_found', 'Template not found');
}
/**
* See API docs.
*
* @param WP_REST_Request $request
*
* @api {put} /real-cookie-banner/v1/scanner/result/ignore Set a template or external host as ignored or unignored
* @apiHeader {string} X-WP-Nonce
* @apiParam {string} type Can be `template` or `host`
* @apiParam {string} value The host or template identifier
* @apiParam {boolean} ignored
* @apiName ScanResultIgnore
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeResultIgnorePost($request)
{
$type = $request->get_param('type');
$value = $request->get_param('value');
$ignored = $request->get_param('ignored');
\delete_transient(ViewScanner::TRANSIENT_SERVICES_FOR_NOTICE);
\delete_transient(Query::TRANSIENT_SCANNED_EXTERNAL_URLS);
return new WP_REST_Response(['updated' => Core::getInstance()->getNotices()->setScannerIgnored($type, $value, $ignored)]);
}
/**
* See API docs.
*
* @param WP_REST_Request $request
*
* @api {get} /real-cookie-banner/v1/scanner/result/markup/:id Get markup by scan entry ID
* @apiHeader {string} X-WP-Nonce
* @apiParam {number} id
* @apiName GetMarkup
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission manage_options
*/
public function routeResultMarkupById($request)
{
$id = \intval($request->get_param('id'));
$result = Core::getInstance()->getScanner()->getQuery()->getMarkup($id);
return $result !== null ? new WP_REST_Response($result) : new WP_Error('rest_not_found', 'Scan entry not found');
}
/**
* See API docs.
*
* @param WP_REST_Request $request
*
* @api {get} /real-cookie-banner/v1/scanner/scan-without-login Scan an URL without login (loopback request)
* @apiHeader {string} X-WP-Nonce
* @apiParam {string} url
* @apiParam {number} jobId
* @apiName ScanWithoutLogin
* @apiGroup Scanner
* @apiVersion 1.0.0
* @apiPermission edit_posts (real-queue query capability)
*/
public function routeScanWithoutLogin($request)
{
$url = $request->get_param('url');
$jobId = $request->get_param('jobId');
// Check if the job ID is valid
$job = Core::getInstance()->getRealQueue()->getQuery()->fetchById($jobId);
if ($job === null || !$job->data->isLoopback) {
return new WP_Error('rest_not_found', 'Job not found');
}
// Validate that the URL belongs to the jobs' domain to prevent SSRF attacks
$jobDataUrl = $job->data->url;
$parsed_url = \wp_parse_url($url);
if (!$parsed_url || !isset($parsed_url['host'])) {
return new WP_Error('rest_invalid_url', 'Invalid URL provided');
}
$current_domain = \wp_parse_url($jobDataUrl, \PHP_URL_HOST);
if ($parsed_url['host'] !== $current_domain) {
return new WP_Error('rest_invalid_domain', 'URL must belong to the jobs\' domain');
}
$checker = new SavingConsentViaRestApiEndpointChecker();
// See https://github.com/WordPress/WordPress/blob/8fbd2fc6f40ea1f2ad746758b7111a66ab134e19/wp-admin/includes/class-wp-site-health.php#L2136-L2137
$checker->setRequestArgument('sslverify', \apply_filters('https_local_ssl_verify', \false));
//$isNonBlockingRequestStarted = Core::getInstance()->getNotices()->isNonBlockingRequestStarted(); // Not needed here in REST
if ($checker->start($url, \false)) {
$requestArguments = $checker->getRequestArguments();
$result = \wp_remote_get($url, ['redirection' => 0, 'cookies' => $requestArguments['cookies'], 'headers' => $requestArguments['headers'], 'timeout' => $requestArguments['timeout'], 'sslverify' => $requestArguments['sslverify']]);
if (\is_wp_error($result)) {
return new WP_Error('rest_scan_without_login_error', $result->get_error_message());
}
$status = \wp_remote_retrieve_response_code($result);
/**
* > The ok read-only property of the Response interface contains a Boolean stating whether
* > the response was successful (status in the range 200-299) or not.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
*/
$ok = $status >= 200 && $status < 300;
// Check for a `Location` redirect
$location = \wp_remote_retrieve_header($result, 'Location');
return new WP_REST_Response(['status' => $status, 'statusText' => \wp_remote_retrieve_response_message($result), 'ok' => $ok, 'headers' => (object) \wp_remote_retrieve_headers($result), 'redirected' => !empty($location), 'responseUrl' => $location, 'body' => \base64_encode(\wp_remote_retrieve_body($result))]);
} else {
return new WP_Error('rest_scan_without_login_error', 'Loopback request could not be started.');
}
}
/**
* Add live results for the scanner tab.
*
* @param mixed $data
*/
public function real_queue_additional_data_list($data)
{
$data['templates'] = ['items' => AbstractTemplate::toArrays(Core::getInstance()->getScanner()->getQuery()->getScannedTemplates())];
$data['externalUrls'] = $this->routeResultExternalUrls()->get_data();
// Remove explicit notices of external URLs which do not require a manual scan
Core::getInstance()->getNotices()->dismissScannerExplicitExternalUrlCoverageNotice(Notices::SCANNER_EXPLICIT_EXTERNAL_URL_COVERAGE_STATE_SCANNED);
return $data;
}
/**
* Add live results for the scanner results in admin bar.
*
* @param mixed $data
*/
public function real_queue_additional_data_notice($data)
{
$viewScanner = ViewScanner::instance();
list($services, $countAll) = $viewScanner->getServicesForNotice(ViewScanner::MAX_FOUND_SERVICES_LIST_ITEMS);
return ['countAll' => $countAll, 'text' => \count($services) === 0 ? null : $viewScanner->generateNoticeTextFromServices($services, $countAll)];
}
/**
* New instance.
*/
public static function instance()
{
return new \DevOwl\RealCookieBanner\rest\Scanner();
}
}