diff --git a/composer.json b/composer.json index f68ecdd..3be7fb0 100755 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "require": { "php": ">=8.0", "ext-swoole": "*", - "utopia-php/framework": "0.33.*" + "utopia-php/framework": "0.33.35" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/composer.lock b/composer.lock index e004e5a..20fbf3d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f72fec6df1ca8c4970751a9d55f200e2", + "content-hash": "0e587eb21ddbe26cb064a7e9662753fc", "packages": [ { "name": "brick/math", @@ -1942,28 +1942,29 @@ }, { "name": "utopia-php/framework", - "version": "0.33.x-dev", + "version": "0.33.35", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "70ecf88aa4035229482385efc5f8c2f354a29acd" + "reference": "82b139fb04f30045db51b0d322224f222da32313" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/70ecf88aa4035229482385efc5f8c2f354a29acd", - "reference": "70ecf88aa4035229482385efc5f8c2f354a29acd", + "url": "https://api.github.com/repos/utopia-php/http/zipball/82b139fb04f30045db51b0d322224f222da32313", + "reference": "82b139fb04f30045db51b0d322224f222da32313", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.3", "utopia-php/compression": "0.1.*", - "utopia-php/telemetry": "0.1.*" + "utopia-php/telemetry": "0.1.*", + "utopia-php/validators": "0.1.*" }, "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" + "laravel/pint": "1.*", + "phpbench/phpbench": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "9.*" }, "type": "library", "autoload": { @@ -1983,9 +1984,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.33.25" + "source": "https://github.com/utopia-php/http/tree/0.33.35" }, - "time": "2025-09-07T09:03:11+00:00" + "time": "2025-12-12T08:33:52+00:00" }, { "name": "utopia-php/telemetry", @@ -2036,6 +2037,51 @@ "source": "https://github.com/utopia-php/telemetry/tree/0.1.1" }, "time": "2025-03-17T11:57:52+00:00" + }, + { + "name": "utopia-php/validators", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/validators.git", + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/validators/zipball/5c57d5b6cf964f8981807c1d3ea8df620c869080", + "reference": "5c57d5b6cf964f8981807c1d3ea8df620c869080", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "require-dev": { + "laravel/pint": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "11.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A lightweight collection of reusable validators for Utopia projects", + "keywords": [ + "php", + "utopia", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/utopia-php/validators/issues", + "source": "https://github.com/utopia-php/validators/tree/0.1.0" + }, + "time": "2025-11-18T11:05:46+00:00" } ], "packages-dev": [ @@ -3987,13 +4033,13 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.0", "ext-swoole": "*" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/docker-compose.yml b/docker-compose.yml index c1d8732..05eec30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: php: image: swoole-dev diff --git a/src/Swoole/Request.php b/src/Swoole/Request.php index e50cfee..5d197a9 100644 --- a/src/Swoole/Request.php +++ b/src/Swoole/Request.php @@ -14,6 +14,13 @@ class Request extends UtopiaRequest */ protected SwooleRequest $swoole; + /** + * List of trusted proxy header names to check for client IP address + * + * @var array + */ + protected array $trustedIpHeaders = []; + /** * Request constructor. */ @@ -64,18 +71,55 @@ public function setServer(string $key, string $value): static return $this; } + /** + * Set trusted ip headers + * + * WARNING: Only set these headers if your application is behind a trusted proxy. + * Trusting these headers when accepting direct client connections is a security risk. + * + * @param array $headers List of header names to trust (e.g., ['x-forwarded-for', 'x-real-ip']) + * @return static + */ + public function setTrustedIpHeaders(array $headers): static + { + $normalized = array_map('strtolower', $headers); + $trimmed = array_map('trim', $normalized); + $this->trustedIpHeaders = array_filter($trimmed); + + return $this; + } + /** * Get IP * - * Returns users IP address. - * Support HTTP_X_FORWARDED_FOR header usually return - * from different proxy servers or PHP default REMOTE_ADDR + * Extracts the client's IP address from trusted headers or falls back to the remote address. + * Prioritizes headers like X-Forwarded-For when behind proxies or load balancers, + * defaulting to REMOTE_ADDR when trusted headers are unavailable. + * + * @return string The validated client IP address or '0.0.0.0' if unavailable */ public function getIP(): string { - $ips = explode(',', $this->getHeader('x-forwarded-for', $this->getServer('remote_addr') ?? '0.0.0.0')); + $remoteAddr = $this->getServer('REMOTE_ADDR') ?? '0.0.0.0'; + + foreach ($this->trustedIpHeaders as $header) { + $headerValue = $this->getHeader($header); + + if (empty($headerValue)) { + continue; + } + + // Leftmost IP address is the address of the originating client + $ips = explode(',', $headerValue); + $ip = trim($ips[0]); + + // Validate IP format (supports both IPv4 and IPv6) + if (filter_var($ip, FILTER_VALIDATE_IP)) { + return $ip; + } + } - return trim($ips[0] ?? ''); + return $remoteAddr; } /** diff --git a/tests/e2e/server.php b/tests/e2e/server.php index c4eb151..ab86507 100644 --- a/tests/e2e/server.php +++ b/tests/e2e/server.php @@ -71,8 +71,8 @@ ->inject('request') ->inject('response') ->action(function (Request $request, Response $response) { - $response->addHeader('Set-Cookie', 'key1=value1'); - $response->addHeader('Set-Cookie', 'key2=value2'); + $response->addHeader('Set-Cookie', 'key1=value1', override: false); + $response->addHeader('Set-Cookie', 'key2=value2', override: false); $response->send('OK'); });