<?php
namespace IPv6Helper;
if (isset($_GET['source']) || strpos($_SERVER['REQUEST_URI'], 'ipv6.class.php') !== false) {
highlight_file(__FILE__);
exit;
}
class IPv6 {
/**
* string expand(string ip_address, bool|int force_v6 = false, bool no_leading = false, bool no_doublecolon = false)
* Expands an IPv6 address to its full size (adds all the zeros)
* Will return boolean false upon:
* A. Invalid in IPv6 address (note that it does not perform extensive
* tests); or
* B. When $force_v6 is set to true AND it's an IPv4 address
* If you pass an IPv4 address and force_v6 is false (the default),
* the address is converted to IPv6 format automatically.
*
* If $force_v6 is set to integer 2 (or anything other than a boolean),
* AND an IPv4 address is passed, it will simply return the IPv4 formatted
* address.
*
* If $no_leading is set to true, it will only expand the double colon in
* the address, if present. It will also replace the double colon with
* ':0:' instead of ':0000:'.
*
* If $no_doublecolon is set to true, it will only expand the leading zeros
* and leave the double colon alone (if present, it won't add it either).
**/
public static function expand($ip, $force_v6 = false, $no_leading = false, $no_doublecolon = false) {
// ... so, did we actually get an IPv6 address or are they tricking us?
if (!self::isv6($ip)) {
if ($force_v6 === true) {
return false; // Trickery! That'll teach 'em.
}
else if ($force_v6 === false) {
$ip = self::v4tov6($ip);
}
else {
return $ip;
}
}
// If we should process leading zeros in parts of the IPv6 address
if (!$no_leading) {
$ip = explode(':', $ip);
foreach ($ip as $partindex=>$part) {
if (empty($part)) {
// This is probably a double colon in the address
continue;
}
$ip[$partindex] = str_repeat('0', 4 - strlen($part)) . $part;
}
$ip = implode(':', $ip);
}
// If we should process a double colon, and there actually is one
if (!$no_doublecolon && strpos($ip, '::') !== false) {
$replacement = $no_leading ? ':0:' : ':0000:';
// Create a string with the right number of :0:s or :0000:s by
// counting the current number colons, then replace the double
// colon with that.
$ip = str_replace('::', str_repeat($replacement, 8 - substr_count($ip, ':')), $ip);
// Remove excess colons created by the previous step.
$ip = str_replace('::', ':', $ip);
}
return $ip;
}
/**
* string collapse(string ip_address, bool force_v6 = false, no_leading = false, no_doublecolon = false)
* Returns a shortened IPv6 address. Beware that prepending and appending a
* double colon (::) is not possible since it is not possible to know
* whether it should be pre- or postfixed. Given 0001:0002:0001, would you
* prepend or append the double colon? (Hint: there is no right answer.)
* For this reason, any such IPv6 address is seen as invalid.
*
* Will return boolean false upon:
* A. Invalid in IPv6 address (note that it does not perform very extensive
* tests); or
* B. When $force_v6 is set to true AND it's an IPv4 address
* If you pass an IPv4 address and force_v6 is false (the default),
* the address is converted to IPv6 format automatically.
*
* If $force_v6 is set to integer 2 (or anything other than a boolean),
* AND an IPv4 address is passed, it will simply return the IPv4 formatted
* address.
*
* If $no_leading is set to true, it will only collapse the double colon in
* the address, if present.
*
* If $no_doublecolon is set to true, it will only collapse the leading
* zeros and never add a double colon.
**/
/* TODO: Implement this
public static function collapse($ip, $force_v6 = false, $no_leading = false, $no_doublecolon = false) {
// ... so, did we actually get an IPv6 address or are they tricking us?
if (!self::isv6($ip)) {
if ($force_v6 === true) {
return false; // Trickery! That'll teach 'em.
}
else if ($force_v6 === false) {
$ip = self::v4tov6($ip);
}
else {
return $ip;
}
}
// Check validity.
if (substr_count($ip, ':') != 7 && strpos($ip, '::') === false) {
return false; // Invalid IPv6 address. Must contain seven parts or be expandable to that count.
}
if (!$no_leading) {
}
}*/
/**
* string v4tov6(string|int ipv4_address, bool visual_format = false)
* Encapsulates an IPv4 address in IPv6 format. If $visual_format is set to
* true, the address will just be converted to a technically valid format,
* which is easier to read but I doubt whether all parsers will accept it.
* See also: https://lucb1e.com/!ipv6#address_notation
*
* Returns false when it's detected to be a valid IPv6 address or when an
* integer of >2^32 is passed (or smaller than -2^31).
*
* Examples:
* v4tov6('192.168.123.123') = '::FFFF:C0A8:7B7B'
* v4tov6('192.168.1.1', true) = '::FFFF:192.168.1.1'
* v4tov6(ip2long('192.168.0.123')) = '::FFFF:C0A8:007B'
**/
public static function v4tov6($ip, $visual_format = false) {
if (is_int($ip)) {
if ($ip > pow(2, 32) || $ip < -pow(2, 31)) {
return false;
}
$ip = long2ip($ip);
}
else {
if (self::isv6($ip)) {
return false;
}
}
if ($visual_format) {
return '::FFFF:' . $ip;
}
// Expand all pieces into an array
$ip = explode('.', $ip);
// Then convert each to hexadecimal
foreach ($ip as $index=>$part) {
$ip[$index] = dechex($part);
}
// Reassemble the IP address, 2 bytes at a time
$ip = $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
return '::FFFF:' . $ip;
}
/**
* bool isv6(string ip_address)
* Returns true if the given IP address is in IPv6 format.
**/
public static function isv6($ip) {
return strpos($ip, ':') !== false;
}
// TODO: Documentation.
public static function chompInterface($ip) {
return substr($ip, 0, strpos($ip, '%'));
}
// TODO: Documentation.
public static function containsInterface($ip) {
return strpos($ip, '%') !== false;
}
// TODO: Documentation.
public static function getInterface($ip) {
return substr($ip, strpos($ip, '%') + 1);
}
}