xref: /dokuwiki/_test/tests/inc/IpTest.php (revision 2f70db90a6d48ecf1f6af51603dba5a630662d2a)
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