main_class = $main_class; } public function check_request_rate() { if (empty($this->main_class->options['rate_limiting_enable']) || (defined('WP_CLI') && WP_CLI) || current_user_can('manage_options')) { return; } if (method_exists($this->main_class, 'is_request_uri_excluded') && $this->main_class->is_request_uri_excluded()) { return; } $ip = $this->main_class->get_client_ip(); if ($this->main_class->is_ip_actively_blocked($ip)) { return; } if ($this->main_class->is_whitelisted($ip)) { return; } if (!empty($this->main_class->request_is_asn_whitelisted) || !empty($this->main_class->is_advanced_rule_allowed)) { return; } $options = $this->main_class->options; $current_uri = $this->main_class->get_current_request_uri(); $advanced_rules_json = $options['rate_limiting_advanced_rules'] ?? '[]'; $advanced_rules = json_decode($advanced_rules_json, true); $matched_rule = null; if (is_array($advanced_rules)) { foreach ($advanced_rules as $rule) { if (empty($rule['endpoint'])) { continue; } if (strpos($current_uri, $rule['endpoint']) !== false) { $matched_rule = $rule; break; } } } if ($matched_rule) { $limit = (int) $matched_rule['limit']; $window = (int) $matched_rule['window']; $action = $matched_rule['action'] ?? '429'; $cache_prefix = 'advaipbl_rl_' . md5($matched_rule['endpoint']) . '_'; $rule_name = $matched_rule['endpoint']; } else { $limit = (int) ($options['rate_limiting_limit'] ?? 120); $window = (int) ($options['rate_limiting_window'] ?? 60); $action = '403'; $cache_prefix = 'advaipbl_rl_global_'; $rule_name = 'Global'; } if ($limit <= 0 || $window <= 0) { return; } $cache_key = $cache_prefix . $ip; $rate_data = $this->main_class->get_from_custom_cache($cache_key); $current_time = time(); $request_count = 1; $first_request_time = $current_time; if (is_array($rate_data) && isset($rate_data['first_request_time']) && ($current_time <= ($rate_data['first_request_time'] + $window))) { $request_count = (int)$rate_data['count'] + 1; $first_request_time = (int)$rate_data['first_request_time']; } $this->main_class->set_in_custom_cache($cache_key, ['count' => $request_count, 'first_request_time' => $first_request_time], $window + 10); if ($request_count > $limit) { /* translators: %s is a placeholder */ $reason = sprintf(__('Rate limit exceeded (%3$s): %1$d requests in %2$d seconds', 'advanced-ip-blocker'), $request_count, $window, $rule_name); $log_data = [ 'limit' => $limit, 'window' => $window, 'count' => $request_count, 'uri' => $current_uri, 'action' => $action ]; if ($action === '403') { // block_ip_instantly handles atomic DB locking and logs the event (including duration_seconds) as 'critical'. $this->main_class->block_ip_instantly($ip, 'rate_limit', $reason, $log_data); } elseif (strpos($action, 'challenge') === 0) { if (isset($this->main_class->js_challenge_manager)) { if (!$this->main_class->js_challenge_manager->is_vip_pass_valid()) { $mode = str_replace('challenge_', '', $action); if ($mode === 'managed') { $mode = 'js_managed'; } if ($mode === 'automatic') { $mode = 'js_automatic'; } $log_data['engine'] = $mode; $log_data['panic_trigger'] = ($rule_name === 'Global') ? 'automatic' : 'advanced_rule'; $this->main_class->log_specific_error('rate_limit_challenge', $ip, $log_data, 'info'); $this->main_class->js_challenge_manager->serve_challenge('rate_limit', $mode); exit; } } else { $this->main_class->log_specific_error('rate_limit', $ip, $log_data, 'warning'); $this->serve_429_response(max(1, ($first_request_time + $window) - $current_time)); } } else { // Default is 429 - Log as warning $this->main_class->log_specific_error('rate_limit', $ip, $log_data, 'warning'); $this->serve_429_response(max(1, ($first_request_time + $window) - $current_time)); } } } public function check_advanced_rate_limit($ip, $rule_id, $limit, $window) { $transient_name = 'advaipbl_rl_' . md5($ip . '_' . $rule_id); $current_hits = (int) get_transient($transient_name); if ($current_hits >= $limit) { return true; } // Increment hit counter if ($current_hits === 0) { set_transient($transient_name, 1, $window); } else { set_transient($transient_name, $current_hits + 1, $window); } return false; } public function serve_429_response($retry_after = 60) { if (!headers_sent()) { header('HTTP/1.1 429 Too Many Requests', true, 429); header('Retry-After: ' . (int)$retry_after); header('Content-Type: text/html; charset=utf-8'); } echo '429 Too Many Requests'; echo '

429 Too Many Requests

'; echo '

' . esc_html__('You have sent too many requests in a given amount of time. Please slow down.', 'advanced-ip-blocker') . '

'; echo ''; exit; } }