Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.77% covered (success)
96.77%
30 / 31
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
IpAddressUtils
96.77% covered (success)
96.77%
30 / 31
50.00% covered (danger)
50.00%
1 / 2
17
0.00% covered (danger)
0.00%
0 / 1
 normalizeIp
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
10
 isInRange
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
7.03
1<?php
2
3/**
4 * IP address utility functions.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) The National Library of Finland 2015.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Net
25 * @author   Ere Maijala <ere.maijala@helsinki.fi>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org Main Page
28 */
29
30namespace VuFind\Net;
31
32use function count;
33use function defined;
34
35/**
36 * IP address utility functions.
37 *
38 * @category VuFind
39 * @package  Net
40 * @author   Ere Maijala <ere.maijala@helsinki.fi>
41 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
42 * @link     https://vufind.org Main Page
43 */
44class IpAddressUtils
45{
46    /**
47     * Normalize an IP address or a beginning of it to an IPv6 address
48     *
49     * @param string $ip  IP Address
50     * @param bool   $end Whether to make a partial address  an "end of range"
51     * address
52     *
53     * @return string|false Packed in_addr representation if successful, false
54     * for invalid IP address
55     */
56    public function normalizeIp($ip, $end = false)
57    {
58        // The check for AF_INET6 allows fallback to IPv4 only if necessary.
59        // Hopefully that's not necessary.
60        if (!str_contains($ip, ':') || !defined('AF_INET6')) {
61            // IPv4 address
62
63            // Append parts until complete
64            $addr = explode('.', $ip);
65            for ($i = count($addr); $i < 4; $i++) {
66                $addr[] = $end ? 255 : 0;
67            }
68
69            // Get rid of leading zeros etc.
70            $ip = implode('.', array_map('intval', $addr));
71            if (!defined('AF_INET6')) {
72                return inet_pton($ip);
73            }
74            $ip = "::$ip";
75        } else {
76            // IPv6 address
77
78            // Expand :: with '0:' as many times as necessary for a complete address
79            $count = substr_count($ip, ':');
80            if ($count < 8) {
81                $ip = str_replace(
82                    '::',
83                    ':' . str_repeat('0:', 8 - $count),
84                    $ip
85                );
86            }
87            if ($ip[0] == ':') {
88                $ip = "0$ip";
89            }
90            // Append ':0' or ':ffff' to complete the address
91            $count = substr_count($ip, ':');
92            if ($count < 7) {
93                $ip .= str_repeat($end ? ':ffff' : ':0', 7 - $count);
94            }
95        }
96        return inet_pton($ip);
97    }
98
99    /**
100     * Check if an IP address is in a range. Works also with mixed IPv4 and IPv6
101     * addresses.
102     *
103     * @param string $ip     IP address to check
104     * @param array  $ranges An array of IP addresses or address ranges to check
105     *
106     * @return bool
107     */
108    public function isInRange($ip, $ranges)
109    {
110        $ip = $this->normalizeIp($ip);
111        foreach ($ranges as $range) {
112            $ips = explode('-', $range, 2);
113            if (!isset($ips[1])) {
114                $ips[1] = $ips[0];
115            }
116            $ips[0] = $this->normalizeIp($ips[0]);
117            $ips[1] = $this->normalizeIp($ips[1], true);
118            if ($ips[0] === false || $ips[1] === false) {
119                continue;
120            }
121            if ($ip >= $ips[0] && $ip <= $ips[1]) {
122                return true;
123            }
124        }
125        return false;
126    }
127}