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