1<?php 2 3namespace dokuwiki\test; 4 5use dokuwiki\Input\Input; 6use dokuwiki\Ip; 7 8class IpTest extends \DokuWikiTest { 9 10 /** 11 * The data provider for ipToNumber() tests. 12 * 13 * @return mixed[][] Returns an array of test cases. 14 */ 15 public function ip_to_number_provider() : array 16 { 17 $tests = [ 18 ['127.0.0.1', 4, 0x00000000, 0x7f000001], 19 ['::127.0.0.1', 6, 0x00000000, 0x7f000001], 20 ['::1', 6, 0x00000000, 0x00000001], 21 ['38AF:3033:AA39:CDE3:1A46:094C:44ED:5300', 6, 0x38AF3033AA39CDE3, 0x1A46094C44ED5300], 22 ['0000:0000:0000:0000:0000:0000:0000:0000', 6, 0, 0], 23 ['193.53.125.7', 4, 0x00000000, 0xC1357D07], 24 // NOTE: wrap around! 0xFFFFFFFFFFFFFFFE seen as -2 25 ['7FFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFE', 6, 0x7FFFFFFFFFFFFFFF, -2], 26 ]; 27 28 // float loses percision and give confusing test results! 29 // sprintf("%.0f",0x7FFFFFFFFFFFFFFF) == sprintf("%.0f",0x7FFFFFFFFFFFFF00) 30 // using string of decimal values instead 31 if(PHP_INT_SIZE == 4) { 32 $tests = [ 33 ['127.0.0.1', 4, '0', '2130706433'], 34 ['::127.0.0.1', 6, '0','2130706433'], 35 ['::1', 6, '0', '1'], 36 ['0000:0000:0000:0000:0000:0000:0000:0000', 6, '0', '0'], 37 ['193.53.125.7', 4, '0', '3241508103'], 38 ['7FFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFE', 6, '9223372036854775807', '18446744073709551614'], 39 ['38AF:3033:AA39:CDE3:1A46:094C:44ED:5300', 6, '4084536385505709539', '1893210916534440704'] 40 ]; 41 } 42 return $tests; 43 } 44 45 /** 46 * Test ipToNumber(). 47 * 48 * @dataProvider ip_to_number_provider 49 * 50 * @param string $ip The IP address to convert. 51 * @param int $version The IP version, either 4 or 6. 52 * @param int $upper The upper 64 bits of the IP. 53 * @param int $lower The lower 64 bits of the IP. 54 * 55 * @return void 56 * 57 * Note: $upper and $lower are likley 'float' instead of 'int' for large values. 58 * The more accurate hint 'int|float' is not supported in php7.4. 59 */ 60 public function test_ip_to_number(string $ip, int $version, $upper, $lower): void 61 { 62 $result = Ip::ipToNumber($ip); 63 64 // force output of ipv4 to string for easy compare 65 if(PHP_INT_SIZE == 4 and !is_string($result['upper']) ) { 66 $result['upper'] = sprintf("%.0f", $result['upper']); 67 $result['lower'] = sprintf("%.0f", $result['lower']); 68 } 69 70 $this->assertSame($version, $result['version']); 71 $this->assertSame($upper, $result['upper']); 72 $this->assertSame($lower, $result['lower']); 73 } 74 75 /** 76 * The data provider for test_ip_in_range(). 77 * 78 * @return mixed[][] Returns an array of test cases. 79 */ 80 public function ip_in_range_provider(): array 81 { 82 $tests = [ 83 ['192.168.11.2', '192.168.0.0/16', true], 84 ['192.168.11.2', '192.168.64.1/16', true], 85 ['192.168.11.2', '192.168.64.1/18', false], 86 ['192.168.11.2', '192.168.11.0/20', true], 87 ['127.0.0.1', '127.0.0.0/7', true], 88 ['127.0.0.1', '127.0.0.0/8', true], 89 ['127.0.0.1', '127.200.0.0/8', true], 90 ['127.0.0.1', '127.200.0.0/9', false], 91 ['127.0.0.1', '127.0.0.0/31', true], 92 ['127.0.0.1', '127.0.0.0/32', false], 93 ['127.0.0.1', '127.0.0.1/32', true], 94 ['1111:2222:3333:4444:5555:6666:7777:8888', '1110::/12', true], 95 ['1110:2222:3333:4444:5555:6666:7777:8888', '1110::/12', true], 96 ['1100:2222:3333:4444:5555:6666:7777:8888', '1110::/12', false], 97 ['1111:2222:3333:4444:5555:6666:7777:8888', '1111:2222:3300::/40', true], 98 ['1111:2222:3333:4444:5555:6666:7777:8888', '1111:2222:3200::/40', false], 99 ['1111:2222:3333:4444:5555:6666:7777:8888', '1111:2222:3333:4444:5555:6666:7777:8889/127', true], 100 ['1111:2222:3333:4444:5555:6666:7777:8888', '1111:2222:3333:4444:5555:6666:7777:8889/128', false], 101 ['1111:2222:3333:4444:5555:6666:7777:8889', '1111:2222:3333:4444:5555:6666:7777:8889/128', true], 102 ['abcd:ef0a:bcde:f0ab:cdef:0abc:def0:abcd', 'abcd:ef0a:bcde:f0ab:cdef:0abc:def0:abcd/128', true], 103 ['abcd:ef0a:bcde:f0ab:cdef:0abc:def0:abce', 'abcd:ef0a:bcde:f0ab:cdef:0abc:def0:abcd/128', false], 104 ]; 105 106 return $tests; 107 } 108 109 /** 110 * Test ipInRange(). 111 * 112 * @dataProvider ip_in_range_provider 113 * 114 * @param string $ip The IP to test. 115 * @param string $range The IP range to test against. 116 * @param bool $expected The expected result from ipInRange(). 117 * 118 * @return void 119 */ 120 public function test_ip_in_range(string $ip, string $range, bool $expected): void 121 { 122 $result = Ip::ipInRange($ip, $range); 123 124 $this->assertSame($expected, $result); 125 } 126 127 /** 128 * Data provider for test_ip_matches(). 129 * 130 * @return mixed[][] Returns an array of test cases. 131 */ 132 public function ip_matches_provider(): array 133 { 134 // Tests for a CIDR range. 135 $rangeTests = $this->ip_in_range_provider(); 136 137 // Tests for an exact IP match. 138 $exactTests = [ 139 ['127.0.0.1', '127.0.0.1', true], 140 ['127.0.0.1', '127.0.0.0', false], 141 ['aaaa:bbbb:cccc:dddd:eeee::', 'aaaa:bbbb:cccc:dddd:eeee:0000:0000:0000', true], 142 ['aaaa:bbbb:cccc:dddd:eeee:0000:0000:0000', 'aaaa:bbbb:cccc:dddd:eeee::', true], 143 ['aaaa:bbbb:0000:0000:0000:0000:0000:0001', 'aaaa:bbbb::1', true], 144 ['aaaa:bbbb::0001', 'aaaa:bbbb::1', true], 145 ['aaaa:bbbb::0001', 'aaaa:bbbb::', false], 146 ['::ffff:127.0.0.1', '127.0.0.1', false], 147 ['::ffff:127.0.0.1', '::0:ffff:127.0.0.1', true], 148 ]; 149 150 151 return array_merge($rangeTests, $exactTests); 152 } 153 154 /** 155 * Test ipMatches(). 156 * 157 * @dataProvider ip_matches_provider 158 * 159 * @param string $ip The IP to test. 160 * @param string $ipOrRange The IP or IP range to test against. 161 * @param bool $expected The expeced result from ipMatches(). 162 * 163 * @return void 164 */ 165 public function test_ip_matches(string $ip, string $ipOrRange, bool $expected): void 166 { 167 $result = Ip::ipMatches($ip, $ipOrRange); 168 169 $this->assertSame($expected, $result); 170 } 171 172 /** 173 * Data provider for proxyIsTrusted(). 174 * 175 * @return mixed[][] Returns an array of test cases. 176 */ 177 public function proxy_is_trusted_provider(): array 178 { 179 // The new default configuration value. 180 $default = ['::1', 'fe80::/10', '127.0.0.0/8', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']; 181 182 // Adding some custom trusted proxies. 183 $custom = array_merge($default, ['1.2.3.4', '1122::', '3.0.0.1/8', '1111:2222::/32']); 184 185 $tests = [ 186 // Empty configuration. 187 ['', '127.0.0.1', false], 188 189 // Configuration with an array of IPs/CIDRs. 190 [$default, '127.0.0.1', true], 191 [$default, '127.1.2.3', true], 192 [$default, '10.1.2.3', true], 193 [$default, '11.1.2.3', false], 194 [$default, '172.16.0.1', true], 195 [$default, '172.160.0.1', false], 196 [$default, '172.31.255.255', true], 197 [$default, '172.32.0.0', false], 198 [$default, '172.200.0.0', false], 199 [$default, '192.168.2.3', true], 200 [$default, '192.169.1.2', false], 201 [$default, '::1', true], 202 [$default, '0000:0000:0000:0000:0000:0000:0000:0001', true], 203 204 // With custom proxies set. 205 [$custom, '127.0.0.1', true], 206 [$custom, '1.2.3.4', true], 207 [$custom, '3.0.1.2', true], 208 [$custom, '1122::', true], 209 [$custom, '1122:0000:0000:0000:0000:0000:0000:0000', true], 210 [$custom, '1111:2223::', false], 211 [$custom, '1111:2222::', true], 212 [$custom, '1111:2222:3333::', true], 213 [$custom, '1111:2222:3333::1', true], 214 ]; 215 216 return $tests; 217 } 218 219 /** 220 * Test proxyIsTrusted(). 221 * 222 * @dataProvider proxy_is_trusted_provider 223 * 224 * @param string|string[] $config The value for $conf[trustedproxies]. 225 * @param string $ip The proxy IP to test. 226 * @param bool $expected The expected result from proxyIsTrusted(). 227 */ 228 public function test_proxy_is_trusted($config, string $ip, bool $expected): void 229 { 230 global $conf; 231 $conf['trustedproxies'] = $config; 232 233 $result = Ip::proxyIsTrusted($ip); 234 235 $this->assertSame($expected, $result); 236 } 237 238 /** 239 * Data provider for test_forwarded_for(). 240 * 241 * @return mixed[][] Returns an array of test cases. 242 */ 243 public function forwarded_for_provider(): array 244 { 245 // The new default configuration value. 246 $default = ['::1', 'fe80::/10', '127.0.0.0/8', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']; 247 248 // Adding some custom trusted proxies. 249 $custom = array_merge($default, ['1.2.3.4', '1122::', '3.0.0.1/8', '1111:2222::/32']); 250 251 $tests = [ 252 // Empty config value should always return empty array. 253 [[], '', '127.0.0.1', []], 254 [[], '127.0.0.1', '127.0.0.1', []], 255 256 // The new default configuration. 257 [$default, '', '127.0.0.1', []], 258 [$default, '1.2.3.4', '127.0.0.1', ['1.2.3.4', '127.0.0.1']], 259 [$default, '1.2.3.4', '192.168.1.1', ['1.2.3.4', '192.168.1.1']], 260 [$default, '1.2.3.4,172.16.0.1', '192.168.1.1', ['1.2.3.4', '172.16.0.1', '192.168.1.1']], 261 [$default, '1.2.3.4,172.16.0.1', '::1', ['1.2.3.4', '172.16.0.1', '::1']], 262 [$default, '1.2.3.4,172.16.0.1', '::0001', ['1.2.3.4', '172.16.0.1', '::0001']], 263 264 // Directly from an untrusted proxy. 265 [$default, '', '127.0.0.1', []], 266 [$default, '1.2.3.4', '11.22.33.44', []], 267 [$default, '::1', '11.22.33.44', []], 268 [$default, '::1', '::2', []], 269 270 // From a trusted proxy, but via an untrusted proxy. 271 [$default, '1.2.3.4,11.22.33.44,172.16.0.1', '192.168.1.1', []], 272 [$default, '1.2.3.4,::2,172.16.0.1', '::1', []], 273 274 // A custom configuration. 275 [$custom, '', '127.0.0.1', []], 276 [$custom, '1.2.3.4', '127.0.0.1', ['1.2.3.4', '127.0.0.1']], 277 [$custom, '1.2.3.4', '192.168.1.1', ['1.2.3.4', '192.168.1.1']], 278 [$custom, '1.2.3.4,172.16.0.1', '192.168.1.1', ['1.2.3.4', '172.16.0.1', '192.168.1.1']], 279 [$custom, '1.2.3.4,172.16.0.1', '::1', ['1.2.3.4', '172.16.0.1', '::1']], 280 [$custom, '1.2.3.4,172.16.0.1', '::0001', ['1.2.3.4', '172.16.0.1', '::0001']], 281 282 // Directly from an untrusted proxy. 283 [$custom, '', '127.0.0.1', []], 284 [$custom, '1.2.3.4', '11.22.33.44', []], 285 [$custom, '::1', '11.22.33.44', []], 286 [$custom, '::1', '::2', []], 287 288 // From a trusted proxy, but via an untrusted proxy. 289 [$custom, '1.2.3.4,11.22.33.44,172.16.0.1', '192.168.1.1', []], 290 [$custom, '1.2.3.4,::2,172.16.0.1', '::1', []], 291 292 // Via a custom proxy. 293 [$custom, '11.2.3.4,3.1.2.3,172.16.0.1', '192.168.1.1', ['11.2.3.4', '3.1.2.3', '172.16.0.1', '192.168.1.1']], 294 [$custom, '11.2.3.4,1122::,172.16.0.1', '3.0.0.1', ['11.2.3.4', '1122::', '172.16.0.1', '3.0.0.1']], 295 [$custom, '11.2.3.4,1122::,172.16.0.1', '1111:2222:3333::', ['11.2.3.4', '1122::', '172.16.0.1', '1111:2222:3333::']], 296 ]; 297 298 return $tests; 299 } 300 301 /** 302 * Test forwardedFor(). 303 * 304 * @dataProvider forwarded_for_provider 305 * 306 * @param string|string[] $config The trustedproxies config value. 307 * @param string $header The X-Forwarded-For header value. 308 * @param string $remoteAddr The TCP/IP peer address. 309 * @param array $expected The expected result from forwardedFor(). 310 * 311 * @return void 312 */ 313 public function test_forwarded_for($config, string $header, string $remoteAddr, array $expected): void 314 { 315 /* @var Input $INPUT */ 316 global $INPUT, $conf; 317 318 $conf['trustedproxies'] = $config; 319 $INPUT->server->set('HTTP_X_FORWARDED_FOR', $header); 320 $INPUT->server->set('REMOTE_ADDR', $remoteAddr); 321 322 $result = Ip::forwardedFor(); 323 324 $this->assertSame($expected, $result); 325 } 326 327 /** 328 * Data provider for test_is_ssl(). 329 * 330 * @return mixed[][] Returns an array of test cases. 331 */ 332 public function is_ssl_provider(): array 333 { 334 // The new default configuration value. 335 $default = ['::1', 'fe80::/10', '127.0.0.0/8', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']; 336 337 $tests = [ 338 // Running behind an SSL proxy, HTTP between server and proxy 339 // Proxy (REMOTE_ADDR) is matched by trustedproxies config 340 // HTTPS not set, HTTP_X_FORWARDED_PROTO set to https 341 [$default, '127.0.0.1', '', 'https', true], 342 343 // Running behind an SSL proxy, HTTP between server and proxy 344 // Proxy (REMOTE_ADDR) is not matched by trustedproxies config 345 // HTTPS not set, HTTP_X_FORWARDED_PROTO set to https 346 [[], '8.8.8.8', '', 'https', false], 347 348 // Running behind a plain HTTP proxy, HTTP between server and proxy 349 // HTTPS not set, HTTP_X_FORWARDED_PROTO set to http 350 [$default, '127.0.0.1', '', 'http', false], 351 352 // Running behind an SSL proxy, HTTP between server and proxy 353 // HTTPS set to off, HTTP_X_FORWARDED_PROTO set to https 354 [$default, '127.0.0.1', 'off', 'https', true], 355 356 // Not running behind a proxy, HTTPS server 357 // HTTPS set to on, HTTP_X_FORWARDED_PROTO not set 358 [[], '8.8.8.8', 'on', '', true], 359 360 // Not running behind a proxy, plain HTTP server 361 // HTTPS not set, HTTP_X_FORWARDED_PROTO not set 362 [[], '8.8.8.8', '', '', false], 363 364 // Not running behind a proxy, plain HTTP server 365 // HTTPS set to off, HTTP_X_FORWARDED_PROTO not set 366 [[], '8.8.8.8', 'off', '', false], 367 368 // Running behind an SSL proxy, SSL between proxy and HTTP server 369 // HTTPS set to on, HTTP_X_FORWARDED_PROTO set to https 370 [$default, '127.0.0.1', 'on', 'https', true], 371 ]; 372 373 return $tests; 374 } 375 376 /** 377 * Test isSsl(). 378 * 379 * @dataProvider is_ssl_provider 380 * 381 * @param string|string[] $config The trustedproxies config value. 382 * @param string $remoteAddr The REMOTE_ADDR value. 383 * @param string $https The HTTPS value. 384 * @param string $forwardedProto The HTTP_X_FORWARDED_PROTO value. 385 * @param bool $expected The expected result from isSsl(). 386 * 387 * @return void 388 */ 389 public function test_is_ssl($config, string $remoteAddr, string $https, string $forwardedProto, bool $expected): void 390 { 391 /* @var Input $INPUT */ 392 global $INPUT, $conf; 393 394 $conf['trustedproxies'] = $config; 395 $INPUT->server->set('REMOTE_ADDR', $remoteAddr); 396 $INPUT->server->set('HTTPS', $https); 397 $INPUT->server->set('HTTP_X_FORWARDED_PROTO', $forwardedProto); 398 399 $result = Ip::isSsl(); 400 401 $this->assertSame($expected, $result); 402 } 403 404 /** 405 * Data provider for test_host_name(). 406 * 407 * @return mixed[][] Returns an array of test cases. 408 */ 409 public function host_name_provider(): array 410 { 411 // The new default configuration value. 412 $default = ['::1', 'fe80::/10', '127.0.0.0/8', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']; 413 414 $tests = [ 415 // X-Forwarded-Host with trusted proxy 416 [$default, '127.0.0.1', 'proxy.example.com', 'www.example.com', 'server.local', 'proxy.example.com'], 417 418 // X-Forwarded-Host with untrusted proxy (should fall back to HTTP_HOST) 419 [[], '8.8.8.8', 'proxy.example.com', 'www.example.com', 'server.local', 'www.example.com'], 420 421 // No X-Forwarded-Host, use HTTP_HOST 422 [$default, '127.0.0.1', '', 'www.example.com', 'server.local', 'www.example.com'], 423 424 // No X-Forwarded-Host or HTTP_HOST, use SERVER_NAME 425 [$default, '127.0.0.1', '', '', 'server.local', 'server.local'], 426 427 // No headers set, should fall back to system hostname 428 [$default, '127.0.0.1', '', '', '', php_uname('n')], 429 ]; 430 431 return $tests; 432 } 433 434 /** 435 * Test hostName(). 436 * 437 * @dataProvider host_name_provider 438 * 439 * @param string|string[] $config The trustedproxies config value. 440 * @param string $remoteAddr The REMOTE_ADDR value. 441 * @param string $forwardedHost The HTTP_X_FORWARDED_HOST value. 442 * @param string $httpHost The HTTP_HOST value. 443 * @param string $serverName The SERVER_NAME value. 444 * @param string $expected The expected result from hostName(). 445 * 446 * @return void 447 */ 448 public function test_host_name($config, string $remoteAddr, string $forwardedHost, string $httpHost, string $serverName, string $expected): void 449 { 450 /* @var Input $INPUT */ 451 global $INPUT, $conf; 452 453 $conf['trustedproxies'] = $config; 454 $INPUT->server->set('REMOTE_ADDR', $remoteAddr); 455 $INPUT->server->set('HTTP_X_FORWARDED_HOST', $forwardedHost); 456 $INPUT->server->set('HTTP_HOST', $httpHost); 457 $INPUT->server->set('SERVER_NAME', $serverName); 458 459 $result = Ip::hostName(); 460 461 $this->assertSame($expected, $result); 462 } 463} 464