1<?php
2
3declare(strict_types=1);
4/**
5 * SimplePie
6 *
7 * A PHP-Based RSS and Atom Feed Framework.
8 * Takes the hard work out of managing a complete RSS/Atom solution.
9 *
10 * Copyright (c) 2004-2022, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without modification, are
14 * permitted provided that the following conditions are met:
15 *
16 * 	* Redistributions of source code must retain the above copyright notice, this list of
17 * 	  conditions and the following disclaimer.
18 *
19 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
20 * 	  of conditions and the following disclaimer in the documentation and/or other materials
21 * 	  provided with the distribution.
22 *
23 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
24 * 	  to endorse or promote products derived from this software without specific prior
25 * 	  written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
28 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
29 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
30 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 *
37 * @package SimplePie
38 * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue
39 * @author Ryan Parman
40 * @author Sam Sneddon
41 * @author Ryan McCue
42 * @link http://simplepie.org/ SimplePie
43 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
44 */
45
46namespace SimplePie\Net;
47
48/**
49 * Class to validate and to work with IPv6 addresses.
50 *
51 * @package SimplePie
52 * @subpackage HTTP
53 * @copyright 2003-2005 The PHP Group
54 * @license http://www.opensource.org/licenses/bsd-license.php
55 * @link http://pear.php.net/package/Net_IPv6
56 * @author Alexander Merz <alexander.merz@web.de>
57 * @author elfrink at introweb dot nl
58 * @author Josh Peck <jmp at joshpeck dot org>
59 * @author Sam Sneddon <geoffers@gmail.com>
60 */
61class IPv6
62{
63    /**
64     * Uncompresses an IPv6 address
65     *
66     * RFC 4291 allows you to compress concecutive zero pieces in an address to
67     * '::'. This method expects a valid IPv6 address and expands the '::' to
68     * the required number of zero pieces.
69     *
70     * Example:  FF01::101   ->  FF01:0:0:0:0:0:0:101
71     *           ::1         ->  0:0:0:0:0:0:0:1
72     *
73     * @author Alexander Merz <alexander.merz@web.de>
74     * @author elfrink at introweb dot nl
75     * @author Josh Peck <jmp at joshpeck dot org>
76     * @copyright 2003-2005 The PHP Group
77     * @license http://www.opensource.org/licenses/bsd-license.php
78     * @param string $ip An IPv6 address
79     * @return string The uncompressed IPv6 address
80     */
81    public static function uncompress($ip)
82    {
83        $c1 = -1;
84        $c2 = -1;
85        if (substr_count($ip, '::') === 1) {
86            [$ip1, $ip2] = explode('::', $ip);
87            if ($ip1 === '') {
88                $c1 = -1;
89            } else {
90                $c1 = substr_count($ip1, ':');
91            }
92            if ($ip2 === '') {
93                $c2 = -1;
94            } else {
95                $c2 = substr_count($ip2, ':');
96            }
97            if (strpos($ip2, '.') !== false) {
98                $c2++;
99            }
100            // ::
101            if ($c1 === -1 && $c2 === -1) {
102                $ip = '0:0:0:0:0:0:0:0';
103            }
104            // ::xxx
105            elseif ($c1 === -1) {
106                $fill = str_repeat('0:', 7 - $c2);
107                $ip = str_replace('::', $fill, $ip);
108            }
109            // xxx::
110            elseif ($c2 === -1) {
111                $fill = str_repeat(':0', 7 - $c1);
112                $ip = str_replace('::', $fill, $ip);
113            }
114            // xxx::xxx
115            else {
116                $fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
117                $ip = str_replace('::', $fill, $ip);
118            }
119        }
120        return $ip;
121    }
122
123    /**
124     * Compresses an IPv6 address
125     *
126     * RFC 4291 allows you to compress concecutive zero pieces in an address to
127     * '::'. This method expects a valid IPv6 address and compresses consecutive
128     * zero pieces to '::'.
129     *
130     * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
131     *           0:0:0:0:0:0:0:1        ->  ::1
132     *
133     * @see uncompress()
134     * @param string $ip An IPv6 address
135     * @return string The compressed IPv6 address
136     */
137    public static function compress($ip)
138    {
139        // Prepare the IP to be compressed
140        $ip = self::uncompress($ip);
141        $ip_parts = self::split_v6_v4($ip);
142
143        // Replace all leading zeros
144        $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
145
146        // Find bunches of zeros
147        if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
148            $max = 0;
149            $pos = null;
150            foreach ($matches[0] as $match) {
151                if (strlen($match[0]) > $max) {
152                    $max = strlen($match[0]);
153                    $pos = $match[1];
154                }
155            }
156
157            $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
158        }
159
160        if ($ip_parts[1] !== '') {
161            return implode(':', $ip_parts);
162        }
163
164        return $ip_parts[0];
165    }
166
167    /**
168     * Splits an IPv6 address into the IPv6 and IPv4 representation parts
169     *
170     * RFC 4291 allows you to represent the last two parts of an IPv6 address
171     * using the standard IPv4 representation
172     *
173     * Example:  0:0:0:0:0:0:13.1.68.3
174     *           0:0:0:0:0:FFFF:129.144.52.38
175     *
176     * @param string $ip An IPv6 address
177     * @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part
178     */
179    private static function split_v6_v4($ip)
180    {
181        if (strpos($ip, '.') !== false) {
182            $pos = strrpos($ip, ':');
183            $ipv6_part = substr($ip, 0, $pos);
184            $ipv4_part = substr($ip, $pos + 1);
185            return [$ipv6_part, $ipv4_part];
186        }
187
188        return [$ip, ''];
189    }
190
191    /**
192     * Checks an IPv6 address
193     *
194     * Checks if the given IP is a valid IPv6 address
195     *
196     * @param string $ip An IPv6 address
197     * @return bool true if $ip is a valid IPv6 address
198     */
199    public static function check_ipv6($ip)
200    {
201        $ip = self::uncompress($ip);
202        [$ipv6, $ipv4] = self::split_v6_v4($ip);
203        $ipv6 = explode(':', $ipv6);
204        $ipv4 = explode('.', $ipv4);
205        if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
206            foreach ($ipv6 as $ipv6_part) {
207                // The section can't be empty
208                if ($ipv6_part === '') {
209                    return false;
210                }
211
212                // Nor can it be over four characters
213                if (strlen($ipv6_part) > 4) {
214                    return false;
215                }
216
217                // Remove leading zeros (this is safe because of the above)
218                $ipv6_part = ltrim($ipv6_part, '0');
219                if ($ipv6_part === '') {
220                    $ipv6_part = '0';
221                }
222
223                // Check the value is valid
224                $value = hexdec($ipv6_part);
225                if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
226                    return false;
227                }
228            }
229            if (count($ipv4) === 4) {
230                foreach ($ipv4 as $ipv4_part) {
231                    $value = (int) $ipv4_part;
232                    if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
233                        return false;
234                    }
235                }
236            }
237            return true;
238        }
239
240        return false;
241    }
242
243    /**
244     * Checks if the given IP is a valid IPv6 address
245     *
246     * @codeCoverageIgnore
247     * @deprecated Use {@see IPv6::check_ipv6()} instead
248     * @see check_ipv6
249     * @param string $ip An IPv6 address
250     * @return bool true if $ip is a valid IPv6 address
251     */
252    public static function checkIPv6($ip)
253    {
254        return self::check_ipv6($ip);
255    }
256}
257
258class_alias('SimplePie\Net\IPv6', 'SimplePie_Net_IPv6');
259