You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

116 lines
5.3 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
  1. <?php
  2. namespace p3k\HTTP\Tests;
  3. use p3k\HTTP\Guard;
  4. use PHPUnit\Framework\TestCase;
  5. class GuardTest extends TestCase {
  6. private static function guard(array $dns = [], array $allow = []) {
  7. return new Guard($allow, function($host) use($dns) { return $dns[$host] ?? []; });
  8. }
  9. public static function publicAddresses() {
  10. return [['8.8.8.8'], ['93.184.216.34'], ['100.128.0.1'], ['198.20.0.1'], ['223.255.255.255'], ['2606:4700::1'], ['2001:4860:4860::8888'], ['64:ff9b::808:808'], ['2002:808:808::']];
  11. }
  12. public static function nonPublicAddresses() {
  13. return [
  14. ['127.0.0.1'], ['127.8.9.10'], ['10.11.11.80'], ['172.16.0.1'], ['192.168.1.1'], ['169.254.169.254'],
  15. ['100.64.0.1'], ['0.0.0.0'], ['::'], ['::1'], ['fe80::1'], ['fc00::1'], ['fd12:3456::1'],
  16. ['::ffff:127.0.0.1'], ['::ffff:10.0.0.1'], ['64:ff9b::7f00:1'], ['64:ff9b::a00:1'], ['2002:7f00:1::1'], ['2002:a9fe:a9fe::'],
  17. ['192.0.0.8'], ['192.0.2.1'], ['198.18.0.1'], ['198.19.255.255'], ['198.51.100.7'], ['203.0.113.5'], ['224.0.0.1'],
  18. ['239.255.255.250'], ['255.255.255.255'], ['100.127.255.255'], ['::ffff:8.8.8.8'], ['64:ff9b:1::1'], ['100::1'],
  19. ['2001::1'], ['2001:db8::1'], ['3fff::1'], ['fec0::1'], ['ff02::1'], ['not an address'], [''],
  20. ];
  21. }
  22. /** @dataProvider publicAddresses */
  23. #[\PHPUnit\Framework\Attributes\DataProvider('publicAddresses')]
  24. public function testPublicAddresses($address) {
  25. $this->assertTrue(Guard::is_public($address));
  26. }
  27. /** @dataProvider nonPublicAddresses */
  28. #[\PHPUnit\Framework\Attributes\DataProvider('nonPublicAddresses')]
  29. public function testNonPublicAddresses($address) {
  30. $this->assertFalse(Guard::is_public($address));
  31. }
  32. public static function blockedUrls() {
  33. return [
  34. ['gopher://127.0.0.1:6379/_SET%20x%201'], ['dict://127.0.0.1:6379/info'], ['file:///etc/passwd'],
  35. ['ftp://example.com/'], ['ldap://example.com/'], ['javascript:alert(1)'], ['/relative'], ['https://'],
  36. ['http://127.0.0.1/'], ['http://localhost.example/'], ['http://[::1]/'], ['http://[::ffff:127.0.0.1]/'],
  37. ['http://2130706433/'], ['http://0x7f000001/'], ['http://0177.0.0.1/'], ['http://127.1/'], ['http://0/'],
  38. ['http://169.254.169.254/latest/meta-data/'], ['http://mixed.example/'],
  39. ];
  40. }
  41. /** @dataProvider blockedUrls */
  42. #[\PHPUnit\Framework\Attributes\DataProvider('blockedUrls')]
  43. public function testBlockedUrls($url) {
  44. $result = self::guard(['localhost.example' => ['127.0.0.1'], 'mixed.example' => ['93.184.216.34', '10.0.0.1']])->check($url);
  45. $this->assertSame('blocked_url', $result['error'] ?? null, $url);
  46. }
  47. public function testPublicHostIsAllowedWithItsAddresses() {
  48. $result = self::guard(['example.com' => ['93.184.216.34', '2606:2800:220:1::']])->check('https://Example.com/path');
  49. $this->assertSame(['host' => 'example.com', 'port' => 443, 'addresses' => ['93.184.216.34', '2606:2800:220:1::']], $result);
  50. }
  51. public function testExplicitPort() {
  52. $result = self::guard(['example.com' => ['93.184.216.34']])->check('http://example.com:8080/');
  53. $this->assertSame(8080, $result['port']);
  54. }
  55. // Uses the real system resolver: localhost is in every hosts file, and the
  56. // IPv6 entry is only found through getaddrinfo, not a DNS AAAA query.
  57. public function testSystemResolverReadsTheHostsFile() {
  58. if(!function_exists('socket_addrinfo_lookup'))
  59. $this->markTestSkipped('ext-sockets is not available');
  60. $expected = [];
  61. foreach(socket_addrinfo_lookup('localhost', null, ['ai_socktype' => SOCK_STREAM]) as $info) {
  62. $address = socket_addrinfo_explain($info)['ai_addr'];
  63. $expected[] = $address['sin6_addr'] ?? $address['sin_addr'];
  64. }
  65. $addresses = Guard::resolve('localhost');
  66. $this->assertContains('127.0.0.1', $addresses);
  67. foreach(array_unique($expected) as $address)
  68. $this->assertContains($address, $addresses);
  69. $this->assertSame([], Guard::resolve('nonexistent.invalid'));
  70. }
  71. public function testUnresolvableHost() {
  72. $this->assertSame('dns_error', self::guard()->check('https://nowhere.example/')['error']);
  73. }
  74. public function testAllowedHost() {
  75. $guard = self::guard(['dev.example' => ['10.11.11.80']], ['dev.example']);
  76. $this->assertSame(['10.11.11.80'], $guard->check('https://dev.example/')['addresses']);
  77. $this->assertArrayHasKey('error', $guard->check('https://other.example/'));
  78. }
  79. public function testAllowedRange() {
  80. $guard = self::guard(['dev.example' => ['10.11.11.80'], 'db.example' => ['10.11.12.5']], ['10.11.11.0/24']);
  81. $this->assertArrayNotHasKey('error', $guard->check('https://dev.example/'));
  82. $this->assertArrayHasKey('error', $guard->check('https://db.example/'));
  83. }
  84. public function testAllowedSingleAddress() {
  85. $guard = self::guard([], ['127.0.0.1', '::1']);
  86. $this->assertArrayNotHasKey('error', $guard->check('http://127.0.0.1:8000/'));
  87. $this->assertArrayNotHasKey('error', $guard->check('http://[::1]:8000/'));
  88. $this->assertArrayHasKey('error', $guard->check('http://127.0.0.2/'));
  89. }
  90. public function testInCidr() {
  91. $this->assertTrue(Guard::in_cidr('10.11.11.80', '10.11.11.0/24'));
  92. $this->assertFalse(Guard::in_cidr('10.11.12.80', '10.11.11.0/24'));
  93. $this->assertTrue(Guard::in_cidr('10.11.11.80', '10.8.0.0/13'));
  94. $this->assertTrue(Guard::in_cidr('fd00::1', 'fc00::/7'));
  95. $this->assertFalse(Guard::in_cidr('10.0.0.1', 'fc00::/7'));
  96. }
  97. }