[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/utilities/src/ -> IpHelper.php (source)

   1  <?php
   2  /**
   3   * Part of the Joomla Framework Utilities Package
   4   *
   5   * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
   6   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   7   * @note        This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
   8   */
   9  
  10  namespace Joomla\Utilities;
  11  
  12  /**
  13   * IpHelper is a utility class for processing IP addresses
  14   *
  15   * @since  1.6.0
  16   */
  17  final class IpHelper
  18  {
  19      /**
  20       * The IP address of the current visitor
  21       *
  22       * @var    string
  23       * @since  1.6.0
  24       */
  25      private static $ip = null;
  26  
  27      /**
  28       * Should I allow IP overrides through X-Forwarded-For or Client-Ip HTTP headers?
  29       *
  30       * @var    boolean
  31       * @since  1.6.0
  32       * @note   The default value is false in version 2.0+
  33       */
  34      private static $allowIpOverrides = false;
  35  
  36      /**
  37       * Private constructor to prevent instantiation of this class
  38       *
  39       * @since   1.6.0
  40       */
  41  	private function __construct()
  42      {
  43      }
  44  
  45      /**
  46       * Get the current visitor's IP address
  47       *
  48       * @return  string
  49       *
  50       * @since   1.6.0
  51       */
  52  	public static function getIp()
  53      {
  54          if (self::$ip === null)
  55          {
  56              $ip = static::detectAndCleanIP();
  57  
  58              if (!empty($ip) && ($ip != '0.0.0.0') && \function_exists('inet_pton') && \function_exists('inet_ntop'))
  59              {
  60                  $myIP = @inet_pton($ip);
  61  
  62                  if ($myIP !== false)
  63                  {
  64                      $ip = inet_ntop($myIP);
  65                  }
  66              }
  67  
  68              static::setIp($ip);
  69          }
  70  
  71          return self::$ip;
  72      }
  73  
  74      /**
  75       * Set the IP address of the current visitor
  76       *
  77       * @param   string  $ip  The visitor's IP address
  78       *
  79       * @return  void
  80       *
  81       * @since   1.6.0
  82       */
  83  	public static function setIp($ip)
  84      {
  85          self::$ip = $ip;
  86      }
  87  
  88      /**
  89       * Is it an IPv6 IP address?
  90       *
  91       * @param   string   $ip  An IPv4 or IPv6 address
  92       *
  93       * @return  boolean
  94       *
  95       * @since   1.6.0
  96       */
  97  	public static function isIPv6($ip)
  98      {
  99          return strpos($ip, ':') !== false;
 100      }
 101  
 102      /**
 103       * Checks if an IP is contained in a list of IPs or IP expressions
 104       *
 105       * @param   string        $ip       The IPv4/IPv6 address to check
 106       * @param   array|string  $ipTable  An IP expression (or a comma-separated or array list of IP expressions) to check against
 107       *
 108       * @return  boolean
 109       *
 110       * @since   1.6.0
 111       */
 112  	public static function IPinList($ip, $ipTable = '')
 113      {
 114          // No point proceeding with an empty IP list
 115          if (empty($ipTable))
 116          {
 117              return false;
 118          }
 119  
 120          // If the IP list is not an array, convert it to an array
 121          if (!\is_array($ipTable))
 122          {
 123              if (strpos($ipTable, ',') !== false)
 124              {
 125                  $ipTable = explode(',', $ipTable);
 126                  $ipTable = array_map('trim', $ipTable);
 127              }
 128              else
 129              {
 130                  $ipTable = trim($ipTable);
 131                  $ipTable = array($ipTable);
 132              }
 133          }
 134  
 135          // If no IP address is found, return false
 136          if ($ip === '0.0.0.0')
 137          {
 138              return false;
 139          }
 140  
 141          // If no IP is given, return false
 142          if (empty($ip))
 143          {
 144              return false;
 145          }
 146  
 147          // Sanity check
 148          if (!\function_exists('inet_pton'))
 149          {
 150              return false;
 151          }
 152  
 153          // Get the IP's in_adds representation
 154          $myIP = @inet_pton($ip);
 155  
 156          // If the IP is in an unrecognisable format, quite
 157          if ($myIP === false)
 158          {
 159              return false;
 160          }
 161  
 162          $ipv6 = static::isIPv6($ip);
 163  
 164          foreach ($ipTable as $ipExpression)
 165          {
 166              $ipExpression = trim($ipExpression);
 167  
 168              // Inclusive IP range, i.e. 123.123.123.123-124.125.126.127
 169              if (strstr($ipExpression, '-'))
 170              {
 171                  list($from, $to) = explode('-', $ipExpression, 2);
 172  
 173                  if ($ipv6 && (!static::isIPv6($from) || !static::isIPv6($to)))
 174                  {
 175                      // Do not apply IPv4 filtering on an IPv6 address
 176                      continue;
 177                  }
 178  
 179                  if (!$ipv6 && (static::isIPv6($from) || static::isIPv6($to)))
 180                  {
 181                      // Do not apply IPv6 filtering on an IPv4 address
 182                      continue;
 183                  }
 184  
 185                  $from = @inet_pton(trim($from));
 186                  $to   = @inet_pton(trim($to));
 187  
 188                  // Sanity check
 189                  if (($from === false) || ($to === false))
 190                  {
 191                      continue;
 192                  }
 193  
 194                  // Swap from/to if they're in the wrong order
 195                  if ($from > $to)
 196                  {
 197                      list($from, $to) = array($to, $from);
 198                  }
 199  
 200                  if (($myIP >= $from) && ($myIP <= $to))
 201                  {
 202                      return true;
 203                  }
 204              }
 205              // Netmask or CIDR provided
 206              elseif (strstr($ipExpression, '/'))
 207              {
 208                  $binaryip = static::inetToBits($myIP);
 209  
 210                  list($net, $maskbits) = explode('/', $ipExpression, 2);
 211  
 212                  if ($ipv6 && !static::isIPv6($net))
 213                  {
 214                      // Do not apply IPv4 filtering on an IPv6 address
 215                      continue;
 216                  }
 217  
 218                  if (!$ipv6 && static::isIPv6($net))
 219                  {
 220                      // Do not apply IPv6 filtering on an IPv4 address
 221                      continue;
 222                  }
 223  
 224                  if ($ipv6 && strstr($maskbits, ':'))
 225                  {
 226                      // Perform an IPv6 CIDR check
 227                      if (static::checkIPv6CIDR($myIP, $ipExpression))
 228                      {
 229                          return true;
 230                      }
 231  
 232                      // If we didn't match it proceed to the next expression
 233                      continue;
 234                  }
 235  
 236                  if (!$ipv6 && strstr($maskbits, '.'))
 237                  {
 238                      // Convert IPv4 netmask to CIDR
 239                      $long     = ip2long($maskbits);
 240                      $base     = ip2long('255.255.255.255');
 241                      $maskbits = 32 - log(($long ^ $base) + 1, 2);
 242                  }
 243  
 244                  // Convert network IP to in_addr representation
 245                  $net = @inet_pton($net);
 246  
 247                  // Sanity check
 248                  if ($net === false)
 249                  {
 250                      continue;
 251                  }
 252  
 253                  // Get the network's binary representation
 254                  $expectedNumberOfBits = $ipv6 ? 128 : 24;
 255                  $binarynet            = str_pad(static::inetToBits($net), $expectedNumberOfBits, '0', STR_PAD_RIGHT);
 256  
 257                  // Check the corresponding bits of the IP and the network
 258                  $ipNetBits = substr($binaryip, 0, $maskbits);
 259                  $netBits   = substr($binarynet, 0, $maskbits);
 260  
 261                  if ($ipNetBits === $netBits)
 262                  {
 263                      return true;
 264                  }
 265              }
 266              else
 267              {
 268                  // IPv6: Only single IPs are supported
 269                  if ($ipv6)
 270                  {
 271                      $ipExpression = trim($ipExpression);
 272  
 273                      if (!static::isIPv6($ipExpression))
 274                      {
 275                          continue;
 276                      }
 277  
 278                      $ipCheck = @inet_pton($ipExpression);
 279  
 280                      if ($ipCheck === false)
 281                      {
 282                          continue;
 283                      }
 284  
 285                      if ($ipCheck == $myIP)
 286                      {
 287                          return true;
 288                      }
 289                  }
 290                  else
 291                  {
 292                      // Standard IPv4 address, i.e. 123.123.123.123 or partial IP address, i.e. 123.[123.][123.][123]
 293                      $dots = 0;
 294  
 295                      if (substr($ipExpression, -1) == '.')
 296                      {
 297                          // Partial IP address. Convert to CIDR and re-match
 298                          foreach (count_chars($ipExpression, 1) as $i => $val)
 299                          {
 300                              if ($i == 46)
 301                              {
 302                                  $dots = $val;
 303                              }
 304                          }
 305  
 306                          switch ($dots)
 307                          {
 308                              case 1:
 309                                  $netmask = '255.0.0.0';
 310                                  $ipExpression .= '0.0.0';
 311  
 312                                  break;
 313  
 314                              case 2:
 315                                  $netmask = '255.255.0.0';
 316                                  $ipExpression .= '0.0';
 317  
 318                                  break;
 319  
 320                              case 3:
 321                                  $netmask = '255.255.255.0';
 322                                  $ipExpression .= '0';
 323  
 324                                  break;
 325  
 326                              default:
 327                                  $dots = 0;
 328                          }
 329  
 330                          if ($dots)
 331                          {
 332                              $binaryip = static::inetToBits($myIP);
 333  
 334                              // Convert netmask to CIDR
 335                              $long     = ip2long($netmask);
 336                              $base     = ip2long('255.255.255.255');
 337                              $maskbits = 32 - log(($long ^ $base) + 1, 2);
 338  
 339                              $net = @inet_pton($ipExpression);
 340  
 341                              // Sanity check
 342                              if ($net === false)
 343                              {
 344                                  continue;
 345                              }
 346  
 347                              // Get the network's binary representation
 348                              $expectedNumberOfBits = $ipv6 ? 128 : 24;
 349                              $binarynet            = str_pad(static::inetToBits($net), $expectedNumberOfBits, '0', STR_PAD_RIGHT);
 350  
 351                              // Check the corresponding bits of the IP and the network
 352                              $ipNetBits = substr($binaryip, 0, $maskbits);
 353                              $netBits   = substr($binarynet, 0, $maskbits);
 354  
 355                              if ($ipNetBits === $netBits)
 356                              {
 357                                  return true;
 358                              }
 359                          }
 360                      }
 361  
 362                      if (!$dots)
 363                      {
 364                          $ip = @inet_pton(trim($ipExpression));
 365  
 366                          if ($ip == $myIP)
 367                          {
 368                              return true;
 369                          }
 370                      }
 371                  }
 372              }
 373          }
 374  
 375          return false;
 376      }
 377  
 378      /**
 379       * Works around the REMOTE_ADDR not containing the user's IP
 380       *
 381       * @return  void
 382       *
 383       * @since   1.6.0
 384       */
 385  	public static function workaroundIPIssues()
 386      {
 387          $ip = static::getIp();
 388  
 389          if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] === $ip)
 390          {
 391              return;
 392          }
 393  
 394          if (isset($_SERVER['REMOTE_ADDR']))
 395          {
 396              $_SERVER['JOOMLA_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
 397          }
 398          elseif (\function_exists('getenv'))
 399          {
 400              if (getenv('REMOTE_ADDR'))
 401              {
 402                  $_SERVER['JOOMLA_REMOTE_ADDR'] = getenv('REMOTE_ADDR');
 403              }
 404          }
 405  
 406          $_SERVER['REMOTE_ADDR'] = $ip;
 407      }
 408  
 409      /**
 410       * Should I allow the remote client's IP to be overridden by an X-Forwarded-For or Client-Ip HTTP header?
 411       *
 412       * @param   boolean  $newState  True to allow the override
 413       *
 414       * @return  void
 415       *
 416       * @since   1.6.0
 417       */
 418  	public static function setAllowIpOverrides($newState)
 419      {
 420          self::$allowIpOverrides = $newState ? true : false;
 421      }
 422  
 423      /**
 424       * Gets the visitor's IP address.
 425       *
 426       * Automatically handles reverse proxies reporting the IPs of intermediate devices, like load balancers. Examples:
 427       *
 428       * - https://www.akeebabackup.com/support/admin-tools/13743-double-ip-adresses-in-security-exception-log-warnings.html
 429       * - https://stackoverflow.com/questions/2422395/why-is-request-envremote-addr-returning-two-ips
 430       *
 431       * The solution used is assuming that the last IP address is the external one.
 432       *
 433       * @return  string
 434       *
 435       * @since   1.6.0
 436       */
 437  	protected static function detectAndCleanIP()
 438      {
 439          $ip = static::detectIP();
 440  
 441          if (strstr($ip, ',') !== false || strstr($ip, ' ') !== false)
 442          {
 443              $ip  = str_replace(' ', ',', $ip);
 444              $ip  = str_replace(',,', ',', $ip);
 445              $ips = explode(',', $ip);
 446              $ip  = '';
 447  
 448              while (empty($ip) && !empty($ips))
 449              {
 450                  $ip = array_shift($ips);
 451                  $ip = trim($ip);
 452              }
 453          }
 454          else
 455          {
 456              $ip = trim($ip);
 457          }
 458  
 459          return $ip;
 460      }
 461  
 462      /**
 463       * Gets the visitor's IP address
 464       *
 465       * @return  string
 466       *
 467       * @since   1.6.0
 468       */
 469  	protected static function detectIP()
 470      {
 471          // Normally the $_SERVER superglobal is set
 472          if (isset($_SERVER))
 473          {
 474              // Do we have an x-forwarded-for HTTP header (e.g. NginX)?
 475              if (self::$allowIpOverrides && isset($_SERVER['HTTP_X_FORWARDED_FOR']))
 476              {
 477                  return $_SERVER['HTTP_X_FORWARDED_FOR'];
 478              }
 479  
 480              // Do we have a client-ip header (e.g. non-transparent proxy)?
 481              if (self::$allowIpOverrides && isset($_SERVER['HTTP_CLIENT_IP']))
 482              {
 483                  return $_SERVER['HTTP_CLIENT_IP'];
 484              }
 485  
 486              // Normal, non-proxied server or server behind a transparent proxy
 487              if (isset($_SERVER['REMOTE_ADDR']))
 488              {
 489                  return $_SERVER['REMOTE_ADDR'];
 490              }
 491          }
 492  
 493          /*
 494           * This part is executed on PHP running as CGI, or on SAPIs which do not set the $_SERVER superglobal
 495           * If getenv() is disabled, you're screwed
 496           */
 497          if (!\function_exists('getenv'))
 498          {
 499              return '';
 500          }
 501  
 502          // Do we have an x-forwarded-for HTTP header?
 503          if (self::$allowIpOverrides && getenv('HTTP_X_FORWARDED_FOR'))
 504          {
 505              return getenv('HTTP_X_FORWARDED_FOR');
 506          }
 507  
 508          // Do we have a client-ip header?
 509          if (self::$allowIpOverrides && getenv('HTTP_CLIENT_IP'))
 510          {
 511              return getenv('HTTP_CLIENT_IP');
 512          }
 513  
 514          // Normal, non-proxied server or server behind a transparent proxy
 515          if (getenv('REMOTE_ADDR'))
 516          {
 517              return getenv('REMOTE_ADDR');
 518          }
 519  
 520          // Catch-all case for broken servers, apparently
 521          return '';
 522      }
 523  
 524      /**
 525       * Converts inet_pton output to bits string
 526       *
 527       * @param   string  $inet  The in_addr representation of an IPv4 or IPv6 address
 528       *
 529       * @return  string
 530       *
 531       * @since   1.6.0
 532       */
 533  	protected static function inetToBits($inet)
 534      {
 535          if (\strlen($inet) == 4)
 536          {
 537              $unpacked = unpack('A4', $inet);
 538          }
 539          else
 540          {
 541              $unpacked = unpack('A16', $inet);
 542          }
 543  
 544          $unpacked = str_split($unpacked[1]);
 545          $binaryip = '';
 546  
 547          foreach ($unpacked as $char)
 548          {
 549              $binaryip .= str_pad(decbin(\ord($char)), 8, '0', STR_PAD_LEFT);
 550          }
 551  
 552          return $binaryip;
 553      }
 554  
 555      /**
 556       * Checks if an IPv6 address $ip is part of the IPv6 CIDR block $cidrnet
 557       *
 558       * @param   string  $ip       The IPv6 address to check, e.g. 21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A
 559       * @param   string  $cidrnet  The IPv6 CIDR block, e.g. 21DA:00D3:0000:2F3B::/64
 560       *
 561       * @return  boolean
 562       *
 563       * @since   1.6.0
 564       */
 565  	protected static function checkIPv6CIDR($ip, $cidrnet)
 566      {
 567          $ip       = inet_pton($ip);
 568          $binaryip = static::inetToBits($ip);
 569  
 570          list($net, $maskbits) = explode('/', $cidrnet);
 571          $net                  = inet_pton($net);
 572          $binarynet            = static::inetToBits($net);
 573  
 574          $ipNetBits = substr($binaryip, 0, $maskbits);
 575          $netBits   = substr($binarynet, 0, $maskbits);
 576  
 577          return $ipNetBits === $netBits;
 578      }
 579  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer