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