Files
2026-04-28 15:13:50 +02:00

147 lines
4.8 KiB
PHP

<?php
namespace RSSSL\Security\Includes\Check404;
class Rsssl_Simple_404_Interceptor {
private $attempts = 10; // Default attempts threshold
private $time_span = 5; // Time span in seconds (5 seconds)
private $option_name = 'rsssl_404_cache';
private $notice_option = 'rsssl_404_notice_shown';
public function __construct() {
// Load the 404 test class only if the firewall has been enabled
if ( rsssl_get_option('enable_firewall') == '1' ) {
add_action( 'admin_init', array( $this, 'maybe_load_class_404_test' ), 20, 4 );
}
add_filter( 'rsssl_notices', array( $this, 'show_help_notices' ) );
if ( defined( 'rsssl_pro' ) ) {
return;
}
add_action( 'template_redirect', array( $this, 'detect_404' ) );
}
/**
* Detect and handle 404 errors.
*/
public function detect_404(): void {
if (is_404()) {
if ( get_option( $this->notice_option ) ) {
return;
}
$ip_address = $this->get_ip_address();
$current_time = time();
// Prevent the option from becoming too large
$cache = get_option($this->option_name, []);
if (!isset($cache[$ip_address])) {
$cache[$ip_address] = [];
}
$cache[$ip_address][] = $current_time;
$cache[$ip_address] = $this->clean_up_old_entries($cache[$ip_address]);
if (count($cache[$ip_address]) > $this->attempts && !get_option($this->notice_option)) {
update_option($this->notice_option, true, false);
return;
}
update_option($this->option_name, $cache, false);
}
}
/**
* Cleans up old entries based on the given timestamps.
*
* This method filters the given timestamps array and only keeps the entries where the difference between the current time
* and the timestamp is less than the specified time span.
*
* @param array $timestamps An array of timestamps.
*
* @return array The cleaned up timestamps array.
*/
private function clean_up_old_entries($timestamps): array {
$current_time = time();
return array_filter($timestamps, function($timestamp) use ($current_time) {
return ($current_time - $timestamp) < $this->time_span;
});
}
/**
* Retrieves the IP address of the client.
*
* This method checks for the IP address in the following order:
* 1. HTTP_CLIENT_IP: Represents the IP address of the client if the client is a shared internet device.
* 2. HTTP_X_FORWARDED_FOR: Represents the IP address of the client if the client is accessing the server through a proxy server.
* 3. REMOTE_ADDR: Represents the IP address of the client if the client is accessing the server directly.
*
* @return string The IP address of the client.
*/
private function get_ip_address(): string {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
return 'UNKNOWN';
}
/**
* Add a help notice for 404 detection warning.
*
* @param array $notices The existing notices array.
*
* @return array Updated notices array with 404 detection warning notice.
*/
public function show_help_notices(array $notices): array {
if (get_option($this->notice_option)) {
$message = __('We detected suspected bots triggering large numbers of 404 errors on your site.', 'really-simple-ssl');
$notice = [
'callback' => '_true_',
'score' => 1,
'show_with_options' => ['enable_404_detection'],
'output' => [
'true' => [
'msg' => $message,
'icon' => 'warning',
'type' => 'warning',
'dismissible' => true,
'admin_notice' => false,
'highlight_field_id' => 'enable_firewall',
'plusone' => true,
'url' => 'https://really-simple-ssl.com/suspected-bots-causing-404-errors/',
]
]
];
$notices['404_detection_warning'] = $notice;
}
return $notices;
}
/**
* @param $field
* @param $value
* @param $old_value
* @param $option_name
*
* @return void
*/
public function maybe_load_class_404_test() {
if ( ! get_option( 'rsssl_homepage_contains_404_resources' ) ) {
Rsssl_Test_404::get_instance();
}
}
}
new Rsssl_Simple_404_Interceptor();