run:R W Run
7.21 KB
2026-03-11 16:18:52
R W Run
error_log
📄IPv6.php
1<?php
2
3// SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue
4// SPDX-License-Identifier: BSD-3-Clause
5
6declare(strict_types=1);
7
8namespace SimplePie\Net;
9
10/**
11 * Class to validate and to work with IPv6 addresses.
12 *
13 * @copyright 2003-2005 The PHP Group
14 * @license http://www.opensource.org/licenses/bsd-license.php
15 * @link http://pear.php.net/package/Net_IPv6
16 * @author Alexander Merz <alexander.merz@web.de>
17 * @author elfrink at introweb dot nl
18 * @author Josh Peck <jmp at joshpeck dot org>
19 * @author Sam Sneddon <geoffers@gmail.com>
20 */
21class IPv6
22{
23 /**
24 * Uncompresses an IPv6 address
25 *
26 * RFC 4291 allows you to compress consecutive zero pieces in an address to
27 * '::'. This method expects a valid IPv6 address and expands the '::' to
28 * the required number of zero pieces.
29 *
30 * Example: FF01::101 -> FF01:0:0:0:0:0:0:101
31 * ::1 -> 0:0:0:0:0:0:0:1
32 *
33 * @author Alexander Merz <alexander.merz@web.de>
34 * @author elfrink at introweb dot nl
35 * @author Josh Peck <jmp at joshpeck dot org>
36 * @copyright 2003-2005 The PHP Group
37 * @license http://www.opensource.org/licenses/bsd-license.php
38 * @param string $ip An IPv6 address
39 * @return string The uncompressed IPv6 address
40 */
41 public static function uncompress(string $ip)
42 {
43 $c1 = -1;
44 $c2 = -1;
45 if (substr_count($ip, '::') === 1) {
46 [$ip1, $ip2] = explode('::', $ip);
47 if ($ip1 === '') {
48 $c1 = -1;
49 } else {
50 $c1 = substr_count($ip1, ':');
51 }
52 if ($ip2 === '') {
53 $c2 = -1;
54 } else {
55 $c2 = substr_count($ip2, ':');
56 }
57 if (strpos($ip2, '.') !== false) {
58 $c2++;
59 }
60 // ::
61 if ($c1 === -1 && $c2 === -1) {
62 $ip = '0:0:0:0:0:0:0:0';
63 }
64 // ::xxx
65 elseif ($c1 === -1) {
66 $fill = str_repeat('0:', 7 - $c2);
67 $ip = str_replace('::', $fill, $ip);
68 }
69 // xxx::
70 elseif ($c2 === -1) {
71 $fill = str_repeat(':0', 7 - $c1);
72 $ip = str_replace('::', $fill, $ip);
73 }
74 // xxx::xxx
75 else {
76 $fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
77 $ip = str_replace('::', $fill, $ip);
78 }
79 }
80 return $ip;
81 }
82
83 /**
84 * Compresses an IPv6 address
85 *
86 * RFC 4291 allows you to compress consecutive zero pieces in an address to
87 * '::'. This method expects a valid IPv6 address and compresses consecutive
88 * zero pieces to '::'.
89 *
90 * Example: FF01:0:0:0:0:0:0:101 -> FF01::101
91 * 0:0:0:0:0:0:0:1 -> ::1
92 *
93 * @see uncompress()
94 * @param string $ip An IPv6 address
95 * @return string The compressed IPv6 address
96 */
97 public static function compress(string $ip)
98 {
99 // Prepare the IP to be compressed
100 $ip = self::uncompress($ip);
101 $ip_parts = self::split_v6_v4($ip);
102
103 // Replace all leading zeros
104 $ip_parts[0] = (string) preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
105
106 // Find bunches of zeros
107 if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
108 $max = 0;
109 $pos = null;
110 foreach ($matches[0] as $match) {
111 if (strlen($match[0]) > $max) {
112 $max = strlen($match[0]);
113 $pos = $match[1];
114 }
115 }
116
117 assert($pos !== null, 'For PHPStan: Since the regex matched, there is at least one match. And because the pattern is non-empty, the loop will always end with $pos ≥ 1.');
118 $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
119 }
120
121 if ($ip_parts[1] !== '') {
122 return implode(':', $ip_parts);
123 }
124
125 return $ip_parts[0];
126 }
127
128 /**
129 * Splits an IPv6 address into the IPv6 and IPv4 representation parts
130 *
131 * RFC 4291 allows you to represent the last two parts of an IPv6 address
132 * using the standard IPv4 representation
133 *
134 * Example: 0:0:0:0:0:0:13.1.68.3
135 * 0:0:0:0:0:FFFF:129.144.52.38
136 *
137 * @param string $ip An IPv6 address
138 * @return array{string, string} [0] contains the IPv6 represented part, and [1] the IPv4 represented part
139 */
140 private static function split_v6_v4(string $ip): array
141 {
142 if (strpos($ip, '.') !== false) {
143 $pos = strrpos($ip, ':');
144 assert($pos !== false, 'For PHPStan: IPv6 address must contain colon, since split_v6_v4 is only ever called after uncompress.');
145 $ipv6_part = substr($ip, 0, $pos);
146 $ipv4_part = substr($ip, $pos + 1);
147 return [$ipv6_part, $ipv4_part];
148 }
149
150 return [$ip, ''];
151 }
152
153 /**
154 * Checks an IPv6 address
155 *
156 * Checks if the given IP is a valid IPv6 address
157 *
158 * @param string $ip An IPv6 address
159 * @return bool true if $ip is a valid IPv6 address
160 */
161 public static function check_ipv6(string $ip)
162 {
163 $ip = self::uncompress($ip);
164 [$ipv6, $ipv4] = self::split_v6_v4($ip);
165 $ipv6 = explode(':', $ipv6);
166 $ipv4 = explode('.', $ipv4);
167 if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
168 foreach ($ipv6 as $ipv6_part) {
169 // The section can't be empty
170 if ($ipv6_part === '') {
171 return false;
172 }
173
174 // Nor can it be over four characters
175 if (strlen($ipv6_part) > 4) {
176 return false;
177 }
178
179 // Remove leading zeros (this is safe because of the above)
180 $ipv6_part = ltrim($ipv6_part, '0');
181 if ($ipv6_part === '') {
182 $ipv6_part = '0';
183 }
184
185 // Check the value is valid
186 $value = hexdec($ipv6_part);
187 if ($value < 0 || $value > 0xFFFF) {
188 return false;
189 }
190 assert(is_int($value), 'For PHPStan: $value is only float when $ipv6_part > PHP_INT_MAX');
191 if (dechex($value) !== strtolower($ipv6_part)) {
192 return false;
193 }
194 }
195 if (count($ipv4) === 4) {
196 foreach ($ipv4 as $ipv4_part) {
197 $value = (int) $ipv4_part;
198 if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
199 return false;
200 }
201 }
202 }
203 return true;
204 }
205
206 return false;
207 }
208
209 /**
210 * Checks if the given IP is a valid IPv6 address
211 *
212 * @codeCoverageIgnore
213 * @deprecated Use {@see IPv6::check_ipv6()} instead
214 * @see check_ipv6
215 * @param string $ip An IPv6 address
216 * @return bool true if $ip is a valid IPv6 address
217 */
218 public static function checkIPv6(string $ip)
219 {
220 return self::check_ipv6($ip);
221 }
222}
223
224class_alias('SimplePie\Net\IPv6', 'SimplePie_Net_IPv6');
225