first commit
This commit is contained in:
514
libraries/vendor/joomla/utilities/src/IpHelper.php
vendored
Normal file
514
libraries/vendor/joomla/utilities/src/IpHelper.php
vendored
Normal file
@ -0,0 +1,514 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Part of the Joomla Framework Utilities Package
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
|
||||
*/
|
||||
|
||||
namespace Joomla\Utilities;
|
||||
|
||||
/**
|
||||
* IpHelper is a utility class for processing IP addresses
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
final class IpHelper
|
||||
{
|
||||
/**
|
||||
* The IP address of the current visitor
|
||||
*
|
||||
* @var string
|
||||
* @since 1.6.0
|
||||
*/
|
||||
private static $ip = null;
|
||||
|
||||
/**
|
||||
* Should I allow IP overrides through X-Forwarded-For or Client-Ip HTTP headers?
|
||||
*
|
||||
* @var boolean
|
||||
* @since 1.6.0
|
||||
* @note The default value is false in version 2.0+
|
||||
*/
|
||||
private static $allowIpOverrides = false;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this class
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current visitor's IP address
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static function getIp()
|
||||
{
|
||||
if (self::$ip === null) {
|
||||
$ip = static::detectAndCleanIP();
|
||||
|
||||
if (!empty($ip) && ($ip != '0.0.0.0') && \function_exists('inet_pton') && \function_exists('inet_ntop')) {
|
||||
$myIP = @inet_pton($ip);
|
||||
|
||||
if ($myIP !== false) {
|
||||
$ip = inet_ntop($myIP);
|
||||
}
|
||||
}
|
||||
|
||||
static::setIp($ip);
|
||||
}
|
||||
|
||||
return self::$ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IP address of the current visitor
|
||||
*
|
||||
* @param string $ip The visitor's IP address
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static function setIp($ip)
|
||||
{
|
||||
self::$ip = $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it an IPv6 IP address?
|
||||
*
|
||||
* @param string $ip An IPv4 or IPv6 address
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static function isIPv6($ip)
|
||||
{
|
||||
return strpos($ip, ':') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an IP is contained in a list of IPs or IP expressions
|
||||
*
|
||||
* @param string $ip The IPv4/IPv6 address to check
|
||||
* @param array|string $ipTable An IP expression (or a comma-separated or array list of IP expressions) to check against
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static function IPinList($ip, $ipTable = '')
|
||||
{
|
||||
// No point proceeding with an empty IP list
|
||||
if (empty($ipTable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the IP list is not an array, convert it to an array
|
||||
if (!\is_array($ipTable)) {
|
||||
if (strpos($ipTable, ',') !== false) {
|
||||
$ipTable = explode(',', $ipTable);
|
||||
$ipTable = array_map('trim', $ipTable);
|
||||
} else {
|
||||
$ipTable = trim($ipTable);
|
||||
$ipTable = [$ipTable];
|
||||
}
|
||||
}
|
||||
|
||||
// If no IP address is found, return false
|
||||
if ($ip === '0.0.0.0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no IP is given, return false
|
||||
if (empty($ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!\function_exists('inet_pton')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the IP's in_adds representation
|
||||
$myIP = @inet_pton($ip);
|
||||
|
||||
// If the IP is in an unrecognisable format, quite
|
||||
if ($myIP === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ipv6 = static::isIPv6($ip);
|
||||
|
||||
foreach ($ipTable as $ipExpression) {
|
||||
$ipExpression = trim($ipExpression);
|
||||
|
||||
// Inclusive IP range, i.e. 123.123.123.123-124.125.126.127
|
||||
if (strstr($ipExpression, '-')) {
|
||||
list($from, $to) = explode('-', $ipExpression, 2);
|
||||
|
||||
if ($ipv6 && (!static::isIPv6($from) || !static::isIPv6($to))) {
|
||||
// Do not apply IPv4 filtering on an IPv6 address
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$ipv6 && (static::isIPv6($from) || static::isIPv6($to))) {
|
||||
// Do not apply IPv6 filtering on an IPv4 address
|
||||
continue;
|
||||
}
|
||||
|
||||
$from = @inet_pton(trim($from));
|
||||
$to = @inet_pton(trim($to));
|
||||
|
||||
// Sanity check
|
||||
if (($from === false) || ($to === false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Swap from/to if they're in the wrong order
|
||||
if ($from > $to) {
|
||||
list($from, $to) = [$to, $from];
|
||||
}
|
||||
|
||||
if (($myIP >= $from) && ($myIP <= $to)) {
|
||||
return true;
|
||||
}
|
||||
} elseif (strstr($ipExpression, '/')) {
|
||||
// Netmask or CIDR provided
|
||||
$binaryip = static::inetToBits($myIP);
|
||||
|
||||
list($net, $maskbits) = explode('/', $ipExpression, 2);
|
||||
|
||||
if ($ipv6 && !static::isIPv6($net)) {
|
||||
// Do not apply IPv4 filtering on an IPv6 address
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$ipv6 && static::isIPv6($net)) {
|
||||
// Do not apply IPv6 filtering on an IPv4 address
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ipv6 && strstr($maskbits, ':')) {
|
||||
// Perform an IPv6 CIDR check
|
||||
if (static::checkIPv6CIDR($myIP, $ipExpression)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we didn't match it proceed to the next expression
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$ipv6 && strstr($maskbits, '.')) {
|
||||
// Convert IPv4 netmask to CIDR
|
||||
$long = ip2long($maskbits);
|
||||
$base = ip2long('255.255.255.255');
|
||||
$maskbits = 32 - log(($long ^ $base) + 1, 2);
|
||||
}
|
||||
|
||||
// Convert network IP to in_addr representation
|
||||
$net = @inet_pton($net);
|
||||
|
||||
// Sanity check
|
||||
if ($net === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the network's binary representation
|
||||
$expectedNumberOfBits = $ipv6 ? 128 : 24;
|
||||
$binarynet = str_pad(static::inetToBits($net), $expectedNumberOfBits, '0', STR_PAD_RIGHT);
|
||||
|
||||
// Check the corresponding bits of the IP and the network
|
||||
$ipNetBits = substr($binaryip, 0, $maskbits);
|
||||
$netBits = substr($binarynet, 0, $maskbits);
|
||||
|
||||
if ($ipNetBits === $netBits) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// IPv6: Only single IPs are supported
|
||||
if ($ipv6) {
|
||||
$ipExpression = trim($ipExpression);
|
||||
|
||||
if (!static::isIPv6($ipExpression)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ipCheck = @inet_pton($ipExpression);
|
||||
|
||||
if ($ipCheck === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ipCheck == $myIP) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Standard IPv4 address, i.e. 123.123.123.123 or partial IP address, i.e. 123.[123.][123.][123]
|
||||
$dots = 0;
|
||||
|
||||
if (substr($ipExpression, -1) == '.') {
|
||||
// Partial IP address. Convert to CIDR and re-match
|
||||
foreach (count_chars($ipExpression, 1) as $i => $val) {
|
||||
if ($i == 46) {
|
||||
$dots = $val;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($dots) {
|
||||
case 1:
|
||||
$netmask = '255.0.0.0';
|
||||
$ipExpression .= '0.0.0';
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$netmask = '255.255.0.0';
|
||||
$ipExpression .= '0.0';
|
||||
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$netmask = '255.255.255.0';
|
||||
$ipExpression .= '0';
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$dots = 0;
|
||||
}
|
||||
|
||||
if ($dots) {
|
||||
$binaryip = static::inetToBits($myIP);
|
||||
|
||||
// Convert netmask to CIDR
|
||||
$long = ip2long($netmask);
|
||||
$base = ip2long('255.255.255.255');
|
||||
$maskbits = 32 - log(($long ^ $base) + 1, 2);
|
||||
|
||||
$net = @inet_pton($ipExpression);
|
||||
|
||||
// Sanity check
|
||||
if ($net === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the network's binary representation
|
||||
$expectedNumberOfBits = $ipv6 ? 128 : 24;
|
||||
$binarynet = str_pad(static::inetToBits($net), $expectedNumberOfBits, '0', STR_PAD_RIGHT);
|
||||
|
||||
// Check the corresponding bits of the IP and the network
|
||||
$ipNetBits = substr($binaryip, 0, $maskbits);
|
||||
$netBits = substr($binarynet, 0, $maskbits);
|
||||
|
||||
if ($ipNetBits === $netBits) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dots) {
|
||||
$ip = @inet_pton(trim($ipExpression));
|
||||
|
||||
if ($ip == $myIP) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Works around the REMOTE_ADDR not containing the user's IP
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static function workaroundIPIssues()
|
||||
{
|
||||
$ip = static::getIp();
|
||||
|
||||
if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] === $ip) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
$_SERVER['JOOMLA_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
|
||||
} elseif (\function_exists('getenv')) {
|
||||
if (getenv('REMOTE_ADDR')) {
|
||||
$_SERVER['JOOMLA_REMOTE_ADDR'] = getenv('REMOTE_ADDR');
|
||||
}
|
||||
}
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should I allow the remote client's IP to be overridden by an X-Forwarded-For or Client-Ip HTTP header?
|
||||
*
|
||||
* @param boolean $newState True to allow the override
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public static function setAllowIpOverrides($newState)
|
||||
{
|
||||
self::$allowIpOverrides = $newState ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the visitor's IP address.
|
||||
*
|
||||
* Automatically handles reverse proxies reporting the IPs of intermediate devices, like load balancers. Examples:
|
||||
*
|
||||
* - https://www.akeebabackup.com/support/admin-tools/13743-double-ip-adresses-in-security-exception-log-warnings.html
|
||||
* - https://stackoverflow.com/questions/2422395/why-is-request-envremote-addr-returning-two-ips
|
||||
*
|
||||
* The solution used is assuming that the last IP address is the external one.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
protected static function detectAndCleanIP()
|
||||
{
|
||||
$ip = static::detectIP();
|
||||
|
||||
if (strstr($ip, ',') !== false || strstr($ip, ' ') !== false) {
|
||||
$ip = str_replace(' ', ',', $ip);
|
||||
$ip = str_replace(',,', ',', $ip);
|
||||
$ips = explode(',', $ip);
|
||||
$ip = '';
|
||||
|
||||
while (empty($ip) && !empty($ips)) {
|
||||
$ip = array_shift($ips);
|
||||
$ip = trim($ip);
|
||||
}
|
||||
} else {
|
||||
$ip = trim($ip);
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the visitor's IP address
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
protected static function detectIP()
|
||||
{
|
||||
// Normally the $_SERVER superglobal is set
|
||||
if (isset($_SERVER)) {
|
||||
// Do we have an x-forwarded-for HTTP header (e.g. NginX)?
|
||||
if (self::$allowIpOverrides && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
return $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
}
|
||||
|
||||
// Do we have a client-ip header (e.g. non-transparent proxy)?
|
||||
if (self::$allowIpOverrides && isset($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
return $_SERVER['HTTP_CLIENT_IP'];
|
||||
}
|
||||
|
||||
// Normal, non-proxied server or server behind a transparent proxy
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
return $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This part is executed on PHP running as CGI, or on SAPIs which do not set the $_SERVER superglobal
|
||||
* If getenv() is disabled, you're screwed
|
||||
*/
|
||||
if (!\function_exists('getenv')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Do we have an x-forwarded-for HTTP header?
|
||||
if (self::$allowIpOverrides && getenv('HTTP_X_FORWARDED_FOR')) {
|
||||
return getenv('HTTP_X_FORWARDED_FOR');
|
||||
}
|
||||
|
||||
// Do we have a client-ip header?
|
||||
if (self::$allowIpOverrides && getenv('HTTP_CLIENT_IP')) {
|
||||
return getenv('HTTP_CLIENT_IP');
|
||||
}
|
||||
|
||||
// Normal, non-proxied server or server behind a transparent proxy
|
||||
if (getenv('REMOTE_ADDR')) {
|
||||
return getenv('REMOTE_ADDR');
|
||||
}
|
||||
|
||||
// Catch-all case for broken servers, apparently
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts inet_pton output to bits string
|
||||
*
|
||||
* @param string $inet The in_addr representation of an IPv4 or IPv6 address
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
protected static function inetToBits($inet)
|
||||
{
|
||||
if (\strlen($inet) == 4) {
|
||||
$unpacked = unpack('A4', $inet);
|
||||
} else {
|
||||
$unpacked = unpack('A16', $inet);
|
||||
}
|
||||
|
||||
$unpacked = str_split($unpacked[1]);
|
||||
$binaryip = '';
|
||||
|
||||
foreach ($unpacked as $char) {
|
||||
$binaryip .= str_pad(decbin(\ord($char)), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $binaryip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an IPv6 address $ip is part of the IPv6 CIDR block $cidrnet
|
||||
*
|
||||
* @param string $ip The IPv6 address to check, e.g. 21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A
|
||||
* @param string $cidrnet The IPv6 CIDR block, e.g. 21DA:00D3:0000:2F3B::/64
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
protected static function checkIPv6CIDR($ip, $cidrnet)
|
||||
{
|
||||
$ip = inet_pton($ip);
|
||||
$binaryip = static::inetToBits($ip);
|
||||
|
||||
list($net, $maskbits) = explode('/', $cidrnet);
|
||||
$net = inet_pton($net);
|
||||
$binarynet = static::inetToBits($net);
|
||||
|
||||
$ipNetBits = substr($binaryip, 0, $maskbits);
|
||||
$netBits = substr($binarynet, 0, $maskbits);
|
||||
|
||||
return $ipNetBits === $netBits;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user