| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 |
- <?php
- declare(strict_types=1);
- namespace app\miniapi\protection;
- use Webman\Http\Request;
- use app\miniapi\http\ClientIp;
- use app\miniapi\http\Pagination;
- /** Only registered routes create endpoint buckets; IDs and query filters share quotas. */
- final class RequestProfile
- {
- private const PAGED = [
- '/api/mini/v1/packages',
- '/api/mini/v1/enterprises/{id}/ledger',
- '/api/mini/v1/enterprises/{id}/orders',
- '/api/mini/v1/enterprises/{id}/devices',
- '/api/mini/v1/drivers/{id}/tasks',
- '/api/mini/v1/agents/{id}/customers',
- '/api/mini/v1/agents/{id}/orders',
- ];
- public static function endpoint(Request $request): string
- {
- $route = $request->route?->getPath();
- if (!$route || str_contains($route, '{path:')) {
- return 'unknown';
- }
- $method = $request->method() === 'HEAD' ? 'GET' : $request->method();
- // Route matching has already constrained non-ID segments to registered literals.
- return $method . ' ' . preg_replace('#/\d+(?=/|$)#', '/{id}', $request->path());
- }
- public static function listCost(Request $request): int
- {
- if ($request->method() !== 'GET') {
- return 0;
- }
- $path = preg_replace('#/\d+(?=/|$)#', '/{id}', $request->path());
- if (!in_array($path, self::PAGED, true)) {
- return 0;
- }
- return (new Pagination($request->get()))->pageSize;
- }
- public static function ip(Request $request): string
- {
- // Resolved through the proxy allowlist so a deployment behind Nginx keeps per-caller
- // buckets; an untrusted peer's own header is still ignored.
- $ip = ClientIp::resolve($request);
- $packed = @inet_pton($ip);
- if ($packed === false) {
- return 'unknown';
- }
- if (strlen($packed) === 16 && substr($packed, 0, 12) === str_repeat("\0", 10) . "\xff\xff") {
- $packed = substr($packed, 12);
- }
- return (string) inet_ntop($packed);
- }
- }
|