From 36fcdaf6213a34a8be7a982621a6b565eb6279b9 Mon Sep 17 00:00:00 2001 From: niklavsE Date: Tue, 23 Sep 2025 17:57:33 +0300 Subject: [PATCH 1/5] improve CIDR validation logic --- src/ClientIp.php | 32 ++++++++++++++++++++++++++------ tests/ClientIpTest.php | 9 +++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/ClientIp.php b/src/ClientIp.php index 9dff01c..8af4f43 100644 --- a/src/ClientIp.php +++ b/src/ClientIp.php @@ -107,16 +107,36 @@ private function isInProxiedIps(string $ip): bool private static function isInCIDR(string $ip, string $cidr): bool { - $tokens = explode('/', $cidr); - if (count($tokens) !== 2 || !self::isValid($ip) || !self::isValid($tokens[0]) || !is_numeric($tokens[1])) { + if (false === str_contains($cidr, '/')) { return false; } - $cidr_base = ip2long($tokens[0]); - $ip_long = ip2long($ip); - $mask = (0xffffffff << intval($tokens[1])) & 0xffffffff; + [$address, $netmask] = explode('/', $cidr, 2); - return ($cidr_base & $mask) === ($ip_long & $mask); + if ('0' === $netmask) { + return false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32 || false === is_numeric($netmask)) { + return false; + } + + if (false === ip2long($address)) { + return false; + } + + return 0 === substr_compare( + sprintf( + '%032b', + ip2long($ip) + ), + sprintf( + '%032b', + ip2long($address) + ), + 0, + (int) $netmask + ); } /** diff --git a/tests/ClientIpTest.php b/tests/ClientIpTest.php index 84076e1..d8cdf82 100644 --- a/tests/ClientIpTest.php +++ b/tests/ClientIpTest.php @@ -179,6 +179,15 @@ function ($request) { ], $request); $this->assertEquals('1.1.1.1', (string) $response->getBody()); + + $response = Dispatcher::run([ + (new ClientIp())->proxy(['1.1.1.1', '2.0.0.0/8']), + function ($request) { + echo $request->getAttribute('client-ip'); + }, + ], $request); + + $this->assertEquals('3.3.3.3', (string) $response->getBody()); } public function testNoRemoteAddr(): void From f4e0ef91fe3ba40a46a8a14d3e85a51088c74908 Mon Sep 17 00:00:00 2001 From: niklavsE Date: Wed, 24 Sep 2025 09:57:53 +0300 Subject: [PATCH 2/5] refine CIDR validation logic for improved accuracy --- src/ClientIp.php | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/ClientIp.php b/src/ClientIp.php index 8af4f43..4a12d15 100644 --- a/src/ClientIp.php +++ b/src/ClientIp.php @@ -107,36 +107,32 @@ private function isInProxiedIps(string $ip): bool private static function isInCIDR(string $ip, string $cidr): bool { - if (false === str_contains($cidr, '/')) { + $tokens = explode('/', $cidr); + if (count($tokens) !== 2) { return false; } - [$address, $netmask] = explode('/', $cidr, 2); + if (!self::isValid($ip) && !self::isValid($tokens[0])) { + return false; + } - if ('0' === $netmask) { - return false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); + if (!is_numeric($tokens[1]) || $tokens[1] < 0 || $tokens[1] > 32) { + return false; } - if ($netmask < 0 || $netmask > 32 || false === is_numeric($netmask)) { + if (false === ip2long($ip)) { return false; } - if (false === ip2long($address)) { + if (false === ip2long($tokens[0])) { return false; } - return 0 === substr_compare( - sprintf( - '%032b', - ip2long($ip) - ), - sprintf( - '%032b', - ip2long($address) - ), - 0, - (int) $netmask - ); + $ip_long = ip2long($ip); + $cidr_base = ip2long($tokens[0]); + $mask = (int) $tokens[1]; + + return 0 === substr_compare(sprintf('%032b', $ip_long), sprintf('%032b', $cidr_base), 0, $mask); } /** From d18c938864c404981d3c01b85d811e2c7eff7843 Mon Sep 17 00:00:00 2001 From: niklavsE Date: Wed, 24 Sep 2025 09:59:37 +0300 Subject: [PATCH 3/5] align with the existing code style --- src/ClientIp.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClientIp.php b/src/ClientIp.php index 4a12d15..a4e4e91 100644 --- a/src/ClientIp.php +++ b/src/ClientIp.php @@ -120,11 +120,11 @@ private static function isInCIDR(string $ip, string $cidr): bool return false; } - if (false === ip2long($ip)) { + if (!ip2long($ip)) { return false; } - if (false === ip2long($tokens[0])) { + if (!ip2long($tokens[0])) { return false; } From 89fa125fe3a0d7b4bc8a86a7f2a80aa4b99fb4da Mon Sep 17 00:00:00 2001 From: niklavsE Date: Thu, 30 Oct 2025 14:51:09 +0200 Subject: [PATCH 4/5] add configuration for CI testing with PHP version matrix --- .circleci/config.yml | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..14df922 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,99 @@ +version: 2.1 + +orbs: + kasko-ci: kasko/ci-private@6 + +jobs: + test-using: + parameters: + php-version: + type: string + stability: + type: enum + enum: + - stable + - lowest + executor: + name: kasko-ci/php-test-executor + php-version: << parameters.php-version >> + steps: + - kasko-ci/php-version + - checkout + - restore_cache: + keys: + - v1-composer-cache-<< parameters.stability >>-{{ checksum "composer.json" }} + # Fall back to using the latest cache if no exact match is found. + - v1-composer-cache-<< parameters.stability >> + - when: + condition: + equal: [ "stable", << parameters.stability >> ] + steps: + - run: + name: Validate composer files + command: composer validate --strict + - run: + name: Lint PHP files + command: | + find src -name "*.php" -print0 | xargs -0 -n1 -P8 php -l + - run: + name: Configure composer + command: composer config -g github-oauth.github.com $GITHUB_COMPOSER_AUTH + - run: + name: Install composer packages (<< parameters.stability >>) + command: | + composer update \ + --no-interaction \ + --no-plugins \ + --no-progress \ + --no-scripts \ + --optimize-autoloader \ + --prefer-<< parameters.stability >> \ + --prefer-dist \ + --prefer-stable + - save_cache: + key: v1-composer-cache-<< parameters.stability >>-{{ checksum "composer.json" }} + paths: + - ./vendor + - run: + name: List installed packages + command: composer show + - run: + name: Check for unused composer packages + command: unused-scanner + - when: + condition: + equal: [ "stable", << parameters.stability >> ] + steps: + - run: + name: Run PHP Code Sniffer + command: ./vendor/bin/phpcs + - run: + name: Run PHPStan static analysis + command: php -d memory_limit=-1 ./vendor/bin/phpstan analyse --no-progress + - store_artifacts: + path: build/ + + test: + docker: + - image: alpine + steps: + - run: + name: Build done + command: /bin/true + +workflows: + version: 2 + tests: + jobs: + - test-using: + matrix: + parameters: + php-version: + - php83 + stability: + - stable + - lowest + - test: + requires: + - test-using-php83-stable + - test-using-php83-lowest From 4a189e4c7133699b61b9e59d635f25bc48783df1 Mon Sep 17 00:00:00 2001 From: Niklavs <34540352+NiklavsE@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:10:45 +0200 Subject: [PATCH 5/5] Delete .circleci/config.yml --- .circleci/config.yml | 99 -------------------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 14df922..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,99 +0,0 @@ -version: 2.1 - -orbs: - kasko-ci: kasko/ci-private@6 - -jobs: - test-using: - parameters: - php-version: - type: string - stability: - type: enum - enum: - - stable - - lowest - executor: - name: kasko-ci/php-test-executor - php-version: << parameters.php-version >> - steps: - - kasko-ci/php-version - - checkout - - restore_cache: - keys: - - v1-composer-cache-<< parameters.stability >>-{{ checksum "composer.json" }} - # Fall back to using the latest cache if no exact match is found. - - v1-composer-cache-<< parameters.stability >> - - when: - condition: - equal: [ "stable", << parameters.stability >> ] - steps: - - run: - name: Validate composer files - command: composer validate --strict - - run: - name: Lint PHP files - command: | - find src -name "*.php" -print0 | xargs -0 -n1 -P8 php -l - - run: - name: Configure composer - command: composer config -g github-oauth.github.com $GITHUB_COMPOSER_AUTH - - run: - name: Install composer packages (<< parameters.stability >>) - command: | - composer update \ - --no-interaction \ - --no-plugins \ - --no-progress \ - --no-scripts \ - --optimize-autoloader \ - --prefer-<< parameters.stability >> \ - --prefer-dist \ - --prefer-stable - - save_cache: - key: v1-composer-cache-<< parameters.stability >>-{{ checksum "composer.json" }} - paths: - - ./vendor - - run: - name: List installed packages - command: composer show - - run: - name: Check for unused composer packages - command: unused-scanner - - when: - condition: - equal: [ "stable", << parameters.stability >> ] - steps: - - run: - name: Run PHP Code Sniffer - command: ./vendor/bin/phpcs - - run: - name: Run PHPStan static analysis - command: php -d memory_limit=-1 ./vendor/bin/phpstan analyse --no-progress - - store_artifacts: - path: build/ - - test: - docker: - - image: alpine - steps: - - run: - name: Build done - command: /bin/true - -workflows: - version: 2 - tests: - jobs: - - test-using: - matrix: - parameters: - php-version: - - php83 - stability: - - stable - - lowest - - test: - requires: - - test-using-php83-stable - - test-using-php83-lowest