RequestProfile.php 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. <?php
  2. declare(strict_types=1);
  3. namespace app\miniapi\protection;
  4. use Webman\Http\Request;
  5. use app\miniapi\http\Pagination;
  6. /** Only registered routes create endpoint buckets; IDs and query filters share quotas. */
  7. final class RequestProfile
  8. {
  9. private const PAGED = [
  10. '/api/mini/v1/packages',
  11. '/api/mini/v1/enterprises/{id}/ledger',
  12. '/api/mini/v1/enterprises/{id}/orders',
  13. '/api/mini/v1/enterprises/{id}/devices',
  14. '/api/mini/v1/drivers/{id}/tasks',
  15. '/api/mini/v1/agents/{id}/customers',
  16. '/api/mini/v1/agents/{id}/orders',
  17. ];
  18. public static function endpoint(Request $request): string
  19. {
  20. $route = $request->route?->getPath();
  21. if (!$route || str_contains($route, '{path:')) {
  22. return 'unknown';
  23. }
  24. $method = $request->method() === 'HEAD' ? 'GET' : $request->method();
  25. // Route matching has already constrained non-ID segments to registered literals.
  26. return $method . ' ' . preg_replace('#/\d+(?=/|$)#', '/{id}', $request->path());
  27. }
  28. public static function listCost(Request $request): int
  29. {
  30. if ($request->method() !== 'GET') {
  31. return 0;
  32. }
  33. $path = preg_replace('#/\d+(?=/|$)#', '/{id}', $request->path());
  34. if (!in_array($path, self::PAGED, true)) {
  35. return 0;
  36. }
  37. return (new Pagination($request->get()))->pageSize;
  38. }
  39. public static function ip(Request $request): string
  40. {
  41. // Never trust caller-supplied X-Forwarded-For or X-Real-IP.
  42. $ip = $request->getRemoteIp();
  43. $packed = @inet_pton($ip);
  44. if ($packed === false) {
  45. return 'unknown';
  46. }
  47. if (strlen($packed) === 16 && substr($packed, 0, 12) === str_repeat("\0", 10) . "\xff\xff") {
  48. $packed = substr($packed, 12);
  49. }
  50. return (string) inet_ntop($packed);
  51. }
  52. }