IPv6.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <?php
  2. /**
  3. * Class to validate and to work with IPv6 addresses
  4. *
  5. * @package Requests
  6. * @subpackage Utilities
  7. */
  8. /**
  9. * Class to validate and to work with IPv6 addresses
  10. *
  11. * This was originally based on the PEAR class of the same name, but has been
  12. * entirely rewritten.
  13. *
  14. * @package Requests
  15. * @subpackage Utilities
  16. */
  17. class Requests_IPv6 {
  18. /**
  19. * Uncompresses an IPv6 address
  20. *
  21. * RFC 4291 allows you to compress consecutive zero pieces in an address to
  22. * '::'. This method expects a valid IPv6 address and expands the '::' to
  23. * the required number of zero pieces.
  24. *
  25. * Example: FF01::101 -> FF01:0:0:0:0:0:0:101
  26. * ::1 -> 0:0:0:0:0:0:0:1
  27. *
  28. * @author Alexander Merz <alexander.merz@web.de>
  29. * @author elfrink at introweb dot nl
  30. * @author Josh Peck <jmp at joshpeck dot org>
  31. * @copyright 2003-2005 The PHP Group
  32. * @license http://www.opensource.org/licenses/bsd-license.php
  33. * @param string $ip An IPv6 address
  34. * @return string The uncompressed IPv6 address
  35. */
  36. public static function uncompress($ip) {
  37. if (substr_count($ip, '::') !== 1) {
  38. return $ip;
  39. }
  40. list($ip1, $ip2) = explode('::', $ip);
  41. $c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':');
  42. $c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':');
  43. if (strpos($ip2, '.') !== false) {
  44. $c2++;
  45. }
  46. // ::
  47. if ($c1 === -1 && $c2 === -1) {
  48. $ip = '0:0:0:0:0:0:0:0';
  49. }
  50. // ::xxx
  51. else if ($c1 === -1) {
  52. $fill = str_repeat('0:', 7 - $c2);
  53. $ip = str_replace('::', $fill, $ip);
  54. }
  55. // xxx::
  56. else if ($c2 === -1) {
  57. $fill = str_repeat(':0', 7 - $c1);
  58. $ip = str_replace('::', $fill, $ip);
  59. }
  60. // xxx::xxx
  61. else {
  62. $fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
  63. $ip = str_replace('::', $fill, $ip);
  64. }
  65. return $ip;
  66. }
  67. /**
  68. * Compresses an IPv6 address
  69. *
  70. * RFC 4291 allows you to compress consecutive zero pieces in an address to
  71. * '::'. This method expects a valid IPv6 address and compresses consecutive
  72. * zero pieces to '::'.
  73. *
  74. * Example: FF01:0:0:0:0:0:0:101 -> FF01::101
  75. * 0:0:0:0:0:0:0:1 -> ::1
  76. *
  77. * @see uncompress()
  78. * @param string $ip An IPv6 address
  79. * @return string The compressed IPv6 address
  80. */
  81. public static function compress($ip) {
  82. // Prepare the IP to be compressed
  83. $ip = self::uncompress($ip);
  84. $ip_parts = self::split_v6_v4($ip);
  85. // Replace all leading zeros
  86. $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
  87. // Find bunches of zeros
  88. if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
  89. $max = 0;
  90. $pos = null;
  91. foreach ($matches[0] as $match) {
  92. if (strlen($match[0]) > $max) {
  93. $max = strlen($match[0]);
  94. $pos = $match[1];
  95. }
  96. }
  97. $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
  98. }
  99. if ($ip_parts[1] !== '') {
  100. return implode(':', $ip_parts);
  101. }
  102. else {
  103. return $ip_parts[0];
  104. }
  105. }
  106. /**
  107. * Splits an IPv6 address into the IPv6 and IPv4 representation parts
  108. *
  109. * RFC 4291 allows you to represent the last two parts of an IPv6 address
  110. * using the standard IPv4 representation
  111. *
  112. * Example: 0:0:0:0:0:0:13.1.68.3
  113. * 0:0:0:0:0:FFFF:129.144.52.38
  114. *
  115. * @param string $ip An IPv6 address
  116. * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
  117. */
  118. protected static function split_v6_v4($ip) {
  119. if (strpos($ip, '.') !== false) {
  120. $pos = strrpos($ip, ':');
  121. $ipv6_part = substr($ip, 0, $pos);
  122. $ipv4_part = substr($ip, $pos + 1);
  123. return array($ipv6_part, $ipv4_part);
  124. }
  125. else {
  126. return array($ip, '');
  127. }
  128. }
  129. /**
  130. * Checks an IPv6 address
  131. *
  132. * Checks if the given IP is a valid IPv6 address
  133. *
  134. * @param string $ip An IPv6 address
  135. * @return bool true if $ip is a valid IPv6 address
  136. */
  137. public static function check_ipv6($ip) {
  138. $ip = self::uncompress($ip);
  139. list($ipv6, $ipv4) = self::split_v6_v4($ip);
  140. $ipv6 = explode(':', $ipv6);
  141. $ipv4 = explode('.', $ipv4);
  142. if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
  143. foreach ($ipv6 as $ipv6_part) {
  144. // The section can't be empty
  145. if ($ipv6_part === '') {
  146. return false;
  147. }
  148. // Nor can it be over four characters
  149. if (strlen($ipv6_part) > 4) {
  150. return false;
  151. }
  152. // Remove leading zeros (this is safe because of the above)
  153. $ipv6_part = ltrim($ipv6_part, '0');
  154. if ($ipv6_part === '') {
  155. $ipv6_part = '0';
  156. }
  157. // Check the value is valid
  158. $value = hexdec($ipv6_part);
  159. if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
  160. return false;
  161. }
  162. }
  163. if (count($ipv4) === 4) {
  164. foreach ($ipv4 as $ipv4_part) {
  165. $value = (int) $ipv4_part;
  166. if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
  167. return false;
  168. }
  169. }
  170. }
  171. return true;
  172. }
  173. else {
  174. return false;
  175. }
  176. }
  177. }