| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- <?php
- declare(strict_types=1);
- namespace App\Security;
- use App\App\Bootstrap;
- final class RateLimiter
- {
- private string $storageDir;
- private bool $enabled;
- public function __construct()
- {
- $app = Bootstrap::config('app');
- $this->enabled = (bool) ($app['rate_limit']['enabled'] ?? true);
- $this->storageDir = (string) ($app['storage']['rate_limit'] ?? Bootstrap::rootPath() . '/storage/rate_limit');
- if ($this->enabled && !is_dir($this->storageDir)) {
- mkdir($this->storageDir, 0775, true);
- }
- }
- public function allow(string $key, int $limit, int $windowSeconds): bool
- {
- if (!$this->enabled) {
- return true;
- }
- $hash = hash('sha256', $key);
- $path = $this->storageDir . '/' . $hash . '.json';
- $now = time();
- $handle = fopen($path, 'c+');
- if ($handle === false) {
- return true;
- }
- try {
- if (!flock($handle, LOCK_EX)) {
- return true;
- }
- $contents = stream_get_contents($handle);
- $timestamps = [];
- if (is_string($contents) && $contents !== '') {
- $decoded = json_decode($contents, true);
- if (is_array($decoded)) {
- $timestamps = array_values(array_filter($decoded, static fn ($ts): bool => is_int($ts)));
- }
- }
- $threshold = $now - $windowSeconds;
- $timestamps = array_values(array_filter($timestamps, static fn (int $ts): bool => $ts >= $threshold));
- if (count($timestamps) >= $limit) {
- return false;
- }
- $timestamps[] = $now;
- ftruncate($handle, 0);
- rewind($handle);
- fwrite($handle, json_encode($timestamps));
- return true;
- } finally {
- flock($handle, LOCK_UN);
- fclose($handle);
- }
- }
- }
|