Completed
Push — member-groupset-delete ( a90a9a )
by Loz
11:22
created

IPUtils::checkIP4()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 6
eloc 13
c 1
b 0
f 1
nc 5
nop 2
dl 0
loc 22
rs 8.6737
1
<?php
2
/**
3
 * These helpful functions were lifted from the Symfony library
4
 * https://github.com/symfony/http-foundation/blob/master/LICENSE
5
 *
6
 * Http utility functions.
7
 *
8
 * @author Fabien Potencier <[email protected]>
9
 */
10
namespace SilverStripe\Control\Util;
11
/**
12
 * Http utility functions.
13
 *
14
 * @author Fabien Potencier <[email protected]>
15
 */
16
class IPUtils {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
17
	/**
18
	 * This class should not be instantiated.
19
	 */
20
	private function __construct()
21
	{
22
	}
23
	/**
24
	 * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets.
25
	 *
26
	 * @param string       $requestIP IP to check
27
	 * @param string|array $ips       List of IPs or subnets (can be a string if only a single one)
28
	 *
29
	 * @return bool Whether the IP is valid
30
	 *
31
	 * @package framework
32
	 * @subpackage core
33
	 */
34
	public static function checkIP($requestIP, $ips) {
35
		if (!is_array($ips)) {
36
			$ips = array($ips);
37
		}
38
39
		$method = substr_count($requestIP, ':') > 1 ? 'checkIP6' : 'checkIP4';
40
41
		foreach ($ips as $ip) {
42
			if (self::$method($requestIP, trim($ip))) {
43
				return true;
44
			}
45
		}
46
47
		return false;
48
	}
49
	/**
50
	 * Compares two IPv4 addresses.
51
	 * In case a subnet is given, it checks if it contains the request IP.
52
	 *
53
	 * @param string $requestIP IPv4 address to check
54
	 * @param string $ip        IPv4 address or subnet in CIDR notation
55
	 *
56
	 * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet
57
	 */
58
	public static function checkIP4($requestIP, $ip) {
59
		if (!filter_var($requestIP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
60
			return false;
61
		}
62
63
		if (false !== strpos($ip, '/')) {
64
			list($address, $netmask) = explode('/', $ip, 2);
65
66
			if ($netmask === '0') {
67
				return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
68
			}
69
70
			if ($netmask < 0 || $netmask > 32) {
71
				return false;
72
			}
73
		} else {
74
			$address = $ip;
75
			$netmask = 32;
76
		}
77
78
		return 0 === substr_compare(sprintf('%032b', ip2long($requestIP)), sprintf('%032b', ip2long($address)), 0, $netmask);
79
	}
80
	/**
81
	 * Compares two IPv6 addresses.
82
	 * In case a subnet is given, it checks if it contains the request IP.
83
	 *
84
	 * @author David Soria Parra <dsp at php dot net>
85
	 *
86
	 * @see https://github.com/dsp/v6tools
87
	 *
88
	 * @param string $requestIP IPv6 address to check
89
	 * @param string $ip        IPv6 address or subnet in CIDR notation
90
	 *
91
	 * @return bool Whether the IP is valid
92
	 *
93
	 * @throws \RuntimeException When IPV6 support is not enabled
94
	 */
95
	public static function checkIP6($requestIP, $ip) {
96
		if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) {
97
			throw new \RuntimeException('Unable to check IPv6. Check that PHP was not compiled with option "disable-ipv6".');
98
		}
99
100
		if (false !== strpos($ip, '/')) {
101
			list($address, $netmask) = explode('/', $ip, 2);
102
103
			if ($netmask < 1 || $netmask > 128) {
104
				return false;
105
			}
106
		} else {
107
			$address = $ip;
108
			$netmask = 128;
109
		}
110
111
		$bytesAddr = unpack('n*', @inet_pton($address));
112
		$bytesTest = unpack('n*', @inet_pton($requestIP));
113
114
		if (!$bytesAddr || !$bytesTest) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bytesAddr of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $bytesTest of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
115
			return false;
116
		}
117
118
		for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
119
			$left = $netmask - 16 * ($i - 1);
120
			$left = ($left <= 16) ? $left : 16;
121
			$mask = ~(0xffff >> $left) & 0xffff;
122
			if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
123
				return false;
124
			}
125
		}
126
127
		return true;
128
	}
129
}
130