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