toBeFalse(); expect(cacti_trusted_proxy_match('10.0.0.1', []))->toBeFalse(); }); test('trusted match skips empty entries', function () : void { expect(cacti_trusted_proxy_match('10.0.0.1', ['', '10.0.0.1']))->toBeTrue(); expect(cacti_trusted_proxy_match('10.0.0.1', ['']))->toBeFalse(); }); test('trusted match normalises equivalent IP spellings', function () : void { expect(cacti_trusted_proxy_match('::1', ['0:0:0:0:0:0:0:1']))->toBeTrue(); expect(cacti_trusted_proxy_match('127.0.0.1', ['127.0.0.1']))->toBeTrue(); }); test('trusted match does not let an IPv4-mapped entry match bare IPv4', function () : void { expect(cacti_trusted_proxy_match('127.0.0.1', ['::ffff:127.0.0.1']))->toBeFalse(); }); test('trusted match supports CIDR', function () : void { expect(cacti_trusted_proxy_match('10.1.2.3', ['10.0.0.0/8']))->toBeTrue(); expect(cacti_trusted_proxy_match('10.1.2.3', ['192.168.0.0/16']))->toBeFalse(); expect(cacti_trusted_proxy_match('2001:db8::1', ['2001:db8::/32']))->toBeTrue(); }); test('trusted match falls back to exact string equality for a non IP entry', function () : void { expect(cacti_trusted_proxy_match('proxy.local', ['proxy.local']))->toBeTrue(); expect(cacti_trusted_proxy_match('proxy.local', ['other.local']))->toBeFalse(); }); test('header keys map wire names onto CGI names', function () : void { expect(cacti_server_header_key('REMOTE_ADDR'))->toBe('REMOTE_ADDR'); expect(cacti_server_header_key('HTTP_X_FORWARDED_FOR'))->toBe('HTTP_X_FORWARDED_FOR'); expect(cacti_server_header_key('X-Forwarded-For'))->toBe('HTTP_X_FORWARDED_FOR'); expect(cacti_server_header_key(' cf-connecting-ip '))->toBe('HTTP_CF_CONNECTING_IP'); }); test('resolution needs a usable REMOTE_ADDR', function () : void { expect(cacti_resolve_client_addr([], [], ['HTTP_X_FORWARDED_FOR']))->toBeFalse(); expect(cacti_resolve_client_addr(['REMOTE_ADDR' => ''], [], ['HTTP_X_FORWARDED_FOR']))->toBeFalse(); expect(cacti_resolve_client_addr(['REMOTE_ADDR' => 'garbage'], [], ['HTTP_X_FORWARDED_FOR']))->toBeFalse(); }); test('an untrusted peer never has its forwarded header honoured', function () : void { $server = ['REMOTE_ADDR' => '198.51.100.1', 'HTTP_X_FORWARDED_FOR' => '10.0.0.9']; expect(cacti_resolve_client_addr($server, [], ['HTTP_X_FORWARDED_FOR']))->toBe('198.51.100.1'); expect(cacti_resolve_client_addr($server, ['10.0.0.5'], ['HTTP_X_FORWARDED_FOR']))->toBe('198.51.100.1'); }); test('a trusted peer yields the hop it observed', function () : void { $server = ['REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 198.51.100.7']; expect(cacti_resolve_client_addr($server, ['10.0.0.5'], ['HTTP_X_FORWARDED_FOR']))->toBe('198.51.100.7'); }); test('trusted hops inside the chain are walked past', function () : void { $server = ['REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_FOR' => '198.51.100.7, 10.0.0.6, 10.0.0.7']; expect(cacti_resolve_client_addr($server, ['10.0.0.0/8'], ['HTTP_X_FORWARDED_FOR']))->toBe('198.51.100.7'); }); test('a chain of nothing but trusted hops falls back to REMOTE_ADDR', function () : void { $server = ['REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_FOR' => '10.0.0.6, 10.0.0.7']; expect(cacti_resolve_client_addr($server, ['10.0.0.0/8'], ['HTTP_X_FORWARDED_FOR']))->toBe('10.0.0.5'); }); test('a malformed entry abandons that header', function () : void { $server = ['REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_FOR' => '198.51.100.7, not-an-ip']; expect(cacti_resolve_client_addr($server, ['10.0.0.5'], ['HTTP_X_FORWARDED_FOR']))->toBe('10.0.0.5'); }); test('empty chain segments are skipped', function () : void { $server = ['REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_FOR' => '198.51.100.7, ']; expect(cacti_resolve_client_addr($server, ['10.0.0.5'], ['HTTP_X_FORWARDED_FOR']))->toBe('198.51.100.7'); }); test('REMOTE_ADDR and absent headers in the permitted list are skipped', function () : void { $server = ['REMOTE_ADDR' => '10.0.0.5', 'HTTP_X_FORWARDED_FOR' => '198.51.100.7']; $headers = ['REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR']; expect(cacti_resolve_client_addr($server, ['10.0.0.5'], $headers))->toBe('198.51.100.7'); });