initial commit

This commit is contained in:
rodolfomartinez 2026-01-30 22:33:40 -05:00
parent d066133bd4
commit e6f71e3706
55 changed files with 11928 additions and 0 deletions

View file

@ -0,0 +1,188 @@
<?php
/**
* Rate Limiter for Maple Local Fonts.
*
* @package Maple_Local_Fonts
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Class MLF_Rate_Limiter
*
* Provides rate limiting functionality to prevent abuse of AJAX endpoints.
*/
class MLF_Rate_Limiter {
/**
* Default rate limit (requests per window).
*
* @var int
*/
private $limit = 10;
/**
* Default time window in seconds.
*
* @var int
*/
private $window = 60;
/**
* Constructor.
*
* @param int $limit Maximum requests allowed per window.
* @param int $window Time window in seconds.
*/
public function __construct($limit = 10, $window = 60) {
$this->limit = absint($limit);
$this->window = absint($window);
}
/**
* Check if the current user/IP is rate limited.
*
* @param string $action The action being rate limited.
* @return bool True if rate limited (should block), false if allowed.
*/
public function is_limited($action) {
$key = $this->get_rate_limit_key($action);
$data = get_transient($key);
if ($data === false) {
// First request, not limited
return false;
}
return $data['count'] >= $this->limit;
}
/**
* Record a request for rate limiting purposes.
*
* @param string $action The action being recorded.
* @return void
*/
public function record_request($action) {
$key = $this->get_rate_limit_key($action);
$data = get_transient($key);
if ($data === false) {
// First request in this window
$data = [
'count' => 1,
'start' => time(),
];
} else {
$data['count']++;
}
// Set/update transient with remaining window time
$elapsed = time() - $data['start'];
$remaining = max(1, $this->window - $elapsed);
set_transient($key, $data, $remaining);
}
/**
* Check rate limit and record request in one call.
*
* @param string $action The action being checked.
* @return bool True if request is allowed, false if rate limited.
*/
public function check_and_record($action) {
if ($this->is_limited($action)) {
return false;
}
$this->record_request($action);
return true;
}
/**
* Get the number of remaining requests in the current window.
*
* @param string $action The action to check.
* @return int Number of remaining requests.
*/
public function get_remaining($action) {
$key = $this->get_rate_limit_key($action);
$data = get_transient($key);
if ($data === false) {
return $this->limit;
}
return max(0, $this->limit - $data['count']);
}
/**
* Get the rate limit key for the current user/IP.
*
* @param string $action The action being rate limited.
* @return string Transient key.
*/
private function get_rate_limit_key($action) {
// Use user ID if logged in, otherwise IP
$user_id = get_current_user_id();
if ($user_id > 0) {
$identifier = 'user_' . $user_id;
} else {
// Sanitize and hash IP for privacy
$ip = $this->get_client_ip();
$identifier = 'ip_' . md5($ip);
}
return 'mlf_rate_' . sanitize_key($action) . '_' . $identifier;
}
/**
* Get the client IP address.
*
* @return string Client IP address.
*/
private function get_client_ip() {
$ip = '';
// Check for various headers (in order of reliability)
$headers = [
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR',
];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
// X-Forwarded-For can contain multiple IPs, get the first one
$ips = explode(',', sanitize_text_field(wp_unslash($_SERVER[$header])));
$ip = trim($ips[0]);
// Validate IP
if (filter_var($ip, FILTER_VALIDATE_IP)) {
break;
}
}
}
// Fallback to localhost if no valid IP found
return $ip ?: '127.0.0.1';
}
/**
* Clear rate limit for a specific action and user/IP.
*
* @param string $action The action to clear.
* @return void
*/
public function clear($action) {
$key = $this->get_rate_limit_key($action);
delete_transient($key);
}
}