HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux wordpress 6.8.0-88-generic #89-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 01:02:46 UTC 2025 x86_64
User: www-data (33)
PHP: 8.3.28
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/wp-fail2ban/lib/ip.php
<?php declare(strict_types=1);
/**
 * IPv4/IPv6 Class
 *
 * Inspired by IPCalc
 *
 * @see     https://github.com/luzrain/ipcalc
 * @package wp-fail2ban
 * @since   5.0.0
 */
namespace org\lecklider\charles\wordpress\wp_fail2ban;

use org\lecklider\charles\wordpress\wp_fail2ban\InvalidIpException;

require_once __DIR__ . '/invalid-ip-exception.php';
require_once __DIR__ . '/ip-range-list.php';

/**
 * IP address.
 *
 * @since 5.0.0
 */
class IP {

	/**
	 * @var    bool     Valid IP?
	 * @since  5.0.0
	 */
	protected bool $valid = false;

	/**
	 * @var    string   Bare IP address
	 * @since  5.0.0
	 */
	protected string $ip;

	/**
	 * @var    int      CIDR
	 * @since  5.0.0
	 */
	protected int $cidr;

	/**
	 * @var    int      IP version
	 * @since  5.0.0
	 */
	protected int $version;

	/**
	 * @var    string   IP address in network order
	 * @since  5.0.0
	 */
	protected string $ip_long;

	/**
	 * @var    string   Netmask in network order
	 * @since  5.0.0
	 */
	protected string $netmask_long;

	/**
	 * Constructor.
	 *
	 * @since  5.0.0
	 *
	 * @param  string|null $ip
	 * @param  bool        $throw
	 * @param  string      $msg
	 *
	 * @throw  InvalidIpException
	 * @throw  \RuntimeException
	 */
	public function __construct( ?string $ip, bool $throw = false, string $msg = '' ) {
		if ( is_null( $ip ) ) {
			// noop
		} else {
			switch ( count( $p = explode( '/', $ip ) ) ) {
				case 2:
					if ( is_numeric( $p[1] ) ) {
						$this->cidr = intval( $p[1] );
					} elseif ( $throw ) {
						throw new InvalidIpException( $msg );
					} else {
						return;
					}
					// fallthrough
				case 1:
					$this->ip = $p[0];

					if ( false !== ( $bin = inet_pton( $p[0] ) ) ) {
						$this->ip_long = $bin;

						switch ( strlen( $bin ) ) {
							case 4:
								if ( ! isset( $this->cidr ) || $this->cidr < 0 || $this->cidr > 32 ) {
									$this->cidr = 32;
								}
								$this->version = 4;
								$this->netmask_v4();
								break;
							case 16:
								if ( ! isset( $this->cidr ) || $this->cidr < 0 || $this->cidr > 128 ) {
									$this->cidr = 128;
								}
								$this->version = 6;
								$this->netmask_v6();
								break;
							default:
								// Something somewhere has gone desperately wrong if we get here
								if ( $throw ) {
									throw new \RuntimeException( 'inet_pton' );
								} else {
									return;
								}
						}

						$this->valid = true;

					} elseif ( $throw ) {
						throw new InvalidIpException( $msg );
					}
					break;
				default:
					// completely bogus
					if ( $throw ) {
						throw new InvalidIpException( $msg );
					}
					break;
			}
		}
	}

	/**
	 * @since  5.0.0
	 *
	 * @codeCoverageIgnore
	 */
	protected function netmask_v4(): void {
		$netmask            = 0xFFFFFFFF << ( 32 - $this->cidr );
		$this->netmask_long = pack( 'N', $netmask );
	}

	/**
	 * @since  5.0.0
	 *
	 * @codeCoverageIgnore
	 */
	protected function netmask_v6(): void {
		$hosts    = ( 128 - $this->cidr );
		$networks = 128 - $hosts;
		$_m       = str_repeat( '1', $networks ) . str_repeat( '0', $hosts );

		$netmask = array_reduce(
			str_split( $_m, 4 ),
			function ( $carry, $elem ) {
				return $carry . base_convert( $elem, 2, 16 );
			}
		);
		$netmask = join( ':', str_split( $netmask, 4 ) );

		$this->netmask_long = inet_pton( $netmask );
	}

	/**
	 * Magic __toString
	 *
	 * @since  5.0.0
	 *
	 * @return string
	 */
	public function __toString() {
		return ( $this->valid )
			? $this->ip
			: '';
	}

	/**
	 * Valid?
	 *
	 * @since  5.0.0
	 *
	 * @return bool
	 */
	public function isValid(): bool {
		return $this->valid;
	}

	/**
	 * Return IP version
	 *
	 * @since  5.0.0
	 *
	 * @return int|null
	 */
	public function getVersion(): ?int {
		return ( $this->valid )
			? $this->version
			: null;
	}

	/**
	 * Return IP address in printable format
	 *
	 * @since  5.0.0
	 *
	 * @return string|null
	 */
	public function getIpP(): ?string {
		return ( $this->valid )
			? $this->ip
			: null;
	}

	/**
	 * Return IP address in network order
	 *
	 * @since  5.0.0
	 *
	 * @throws \RuntimeException
	 *
	 * @return string|null
	 */
	public function getIpN(): ?string {
		return ( $this->valid )
			? $this->ip_long
			: null;
	}

	/**
	 * Return CIDR
	 *
	 * @since  5.0.0
	 *
	 * @return int|null
	 */
	public function getCidr(): ?int {
		return ( $this->valid )
			? $this->cidr
			: null;
	}

	/**
	 * Return Netmask in printable format
	 *
	 * @since  5.0.0
	 *
	 * @throws \RuntimeException
	 *
	 * @return string|null
	 */
	public function getNetmaskP(): ?string {
		return ( $this->valid )
			? inet_ntop( $this->netmask_long )
			: null;
	}

	/**
	 * Return Netmask in network order
	 *
	 * @since  5.0.0
	 *
	 * @throws \RuntimeException
	 *
	 * @return string|null
	 */
	public function getNetmaskN(): ?string {
		return ( $this->valid )
			? $this->netmask_long
			: null;
	}

	/**
	 * Is IP in ranges?
	 *
	 * @since  5.0.0
	 *
	 * @param  array       $ranges     Array of IP Ranges to check
	 * @param  string|null $cacheTag   Cache tag
	 *
	 * @return bool
	 */
	public function inRanges( array $ranges, string $cacheTag = null ): bool {
		$rv = false;

		if ( $this->valid && ! empty( $ranges ) ) {
			$irl = new IpRangeList( $ranges, $cacheTag );
			$rv  = $irl->containsBinaryIP( $this->ip_long, $this->version );
		}

		return $rv;
	}
}