*/ public static function blocked(): array { $ips = [ '0.0.0.0', '10.1.2.3', '100.64.0.1', '100.127.255.254', '127.0.0.1', '169.254.169.254', '172.16.0.1', '172.31.255.255', '192.0.0.8', '192.0.2.1', '192.168.1.1', '198.18.0.1', '198.51.100.7', '203.0.113.9', '224.0.0.1', '240.0.0.1', '255.255.255.255', '::', '::1', 'fc00::1', 'fd12:3456::1', 'fe80::1', 'febf::1', 'ff02::1', '2001:db8::1', '::ffff:127.0.0.1', '::ffff:10.0.0.1', '::ffff:169.254.169.254', '::ffff:a9fe:a9fe', '64:ff9b::a9fe:a9fe', '::127.0.0.1', ]; return array_combine($ips, array_map(static fn (string $ip): array => [$ip], $ips)); } /** @dataProvider blocked */ public function testPrivateAndReservedAddressesAreNotPublic(string $ip): void { self::assertFalse(PublicAddress::isPublicIp($ip)); } public function testPublicAddressesPass(): void { foreach (['93.184.216.34', '100.63.255.255', '100.128.0.1', '172.32.0.1', '8.8.8.8', '2606:4700::1111', '::ffff:8.8.8.8'] as $ip) { self::assertTrue(PublicAddress::isPublicIp($ip), $ip); } self::assertFalse(PublicAddress::isPublicIp('not-an-ip')); } public function testHostsAreResolvedAndEveryAddressMustBePublic(): void { $resolve = static fn (string $host): array => [ 'cdn.example.com' => ['93.184.216.34'], 'mixed.example.com' => ['93.184.216.34', '169.254.169.254'], 'metadata.example.com' => ['169.254.169.254'], ][$host] ?? []; self::assertTrue(PublicAddress::hostIsPublic('cdn.example.com', $resolve)); self::assertFalse(PublicAddress::hostIsPublic('mixed.example.com', $resolve)); self::assertFalse(PublicAddress::hostIsPublic('metadata.example.com', $resolve)); self::assertFalse(PublicAddress::hostIsPublic('unresolvable.example.com', $resolve)); self::assertFalse(PublicAddress::hostIsPublic('169.254.169.254', $resolve), 'IP literal, no lookup'); self::assertFalse(PublicAddress::hostIsPublic('[fe80::1]', $resolve), 'IPv6 literal in brackets'); self::assertTrue(PublicAddress::hostIsPublic('8.8.8.8', $resolve)); self::assertFalse(PublicAddress::hostIsPublic('', $resolve)); } }