Completed
Push — add/e2e-mailchimp-block-test ( e217db...6066d0 )
by Yaroslav
98:30 queued 85:55
created

shared-functions.php ➔ jetpack_protect_ip_is_private()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 1
dl 0
loc 24
rs 8.9137
c 0
b 0
f 0
1
<?php
2
/**
3
 * These functions are shared by the Protect module and its related json-endpoints
4
 */
5
/**
6
 * Returns an array of IP objects that will never be blocked by the Protect module
7
 *
8
 * The array is segmented into a local whitelist which applies only to the current site
9
 * and a global whitelist which, for multisite installs, applies to the entire networko
10
 *
11
 * @return array
12
 */
13
function jetpack_protect_format_whitelist() {
14
	$local_whitelist = jetpack_protect_get_local_whitelist();
15
	$formatted = array(
16
		'local' => array(),
17
	);
18 View Code Duplication
	foreach ( $local_whitelist as $item ) {
19
		if ( $item->range ) {
20
			$formatted['local'][] = $item->range_low . ' - ' . $item->range_high;
21
		} else {
22
			$formatted['local'][] = $item->ip_address;
23
		}
24
	}
25
	if ( is_multisite() && current_user_can( 'manage_network' ) ) {
26
		$formatted['global'] = array();
27
		$global_whitelist    = jetpack_protect_get_global_whitelist();
28
		if ( false === $global_whitelist ) {
29
			// If the global whitelist has never been set, check for a legacy option set prior to 3.6.
30
			$global_whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
31
		}
32 View Code Duplication
		foreach ( $global_whitelist as $item ) {
33
			if ( $item->range ) {
34
				$formatted['global'][] = $item->range_low . ' - ' . $item->range_high;
35
			} else {
36
				$formatted['global'][] = $item->ip_address;
37
			}
38
		}
39
	}
40
	return $formatted;
41
}
42
/**
43
 * Gets the local Protect whitelist
44
 *
45
 * The 'local' part of the whitelist only really applies to multisite installs,
46
 * which can have a network wide whitelist, as well as a local list that applies
47
 * only to the current site. On single site installs, there will only be a local
48
 * whitelist.
49
 *
50
 * @return array A list of IP Address objects or an empty array
51
 */
52
function jetpack_protect_get_local_whitelist() {
53
	$whitelist = Jetpack_Options::get_option( 'protect_whitelist' );
54
	if ( false === $whitelist ) {
55
		// The local whitelist has never been set.
56
		if ( is_multisite() ) {
57
			// On a multisite, we can check for a legacy site_option that existed prior to v 3.6, or default to an empty array.
58
			$whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
59
		} else {
60
			// On a single site, we can just use an empty array.
61
			$whitelist = array();
62
		}
63
	}
64
	return $whitelist;
65
}
66
67
/**
68
 * Get the global, network-wide whitelist
69
 *
70
 * It will revert to the legacy site_option if jetpack_protect_global_whitelist has never been set.
71
 *
72
 * @return array
73
 */
74
function jetpack_protect_get_global_whitelist() {
75
	$whitelist = get_site_option( 'jetpack_protect_global_whitelist' );
76
	if ( false === $whitelist ) {
77
		// The global whitelist has never been set. Check for legacy site_option, or default to an empty array.
78
		$whitelist = get_site_option( 'jetpack_protect_whitelist', array() );
79
	}
80
	return $whitelist;
81
}
82
83
/**
84
 * Jetpack Protect Save Whitelist.
85
 *
86
 * @access public
87
 * @param mixed $whitelist Whitelist.
88
 * @param bool  $global (default: false) Global.
89
 * @return Bool.
0 ignored issues
show
Documentation introduced by
The doc-type Bool. could not be parsed: Unknown type name "Bool." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
90
 */
91
function jetpack_protect_save_whitelist( $whitelist, $global = false ) {
92
	$whitelist_error = false;
93
	$new_items       = array();
94
	if ( ! is_array( $whitelist ) ) {
95
		return new WP_Error( 'invalid_parameters', __( 'Expecting an array', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_parameters'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
96
	}
97
	if ( $global && ! is_multisite() ) {
98
		return new WP_Error( 'invalid_parameters', __( 'Cannot use global flag on non-multisites', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_parameters'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
99
	}
100
	if ( $global && ! current_user_can( 'manage_network' ) ) {
101
		return new WP_Error( 'permission_denied', __( 'Only super admins can edit the global whitelist', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'permission_denied'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
102
	}
103
	// Validate each item.
104
	foreach ( $whitelist as $item ) {
105
		$item = trim( $item );
106
		if ( empty( $item ) ) {
107
			continue;
108
		}
109
		$range = false;
110
		if ( strpos( $item, '-' ) ) {
111
			$item  = explode( '-', $item );
112
			$range = true;
113
		}
114
		$new_item        = new stdClass();
115
		$new_item->range = $range;
116
		if ( ! empty( $range ) ) {
117
			$low  = trim( $item[0] );
118
			$high = trim( $item[1] );
119
			if ( ! filter_var( $low, FILTER_VALIDATE_IP ) || ! filter_var( $high, FILTER_VALIDATE_IP ) ) {
120
				$whitelist_error = true;
121
				break;
122
			}
123
			if ( ! jetpack_convert_ip_address( $low ) || ! jetpack_convert_ip_address( $high ) ) {
124
				$whitelist_error = true;
125
				break;
126
			}
127
			$new_item->range_low  = $low;
128
			$new_item->range_high = $high;
129
		} else {
130
			if ( ! filter_var( $item, FILTER_VALIDATE_IP ) ) {
131
				$whitelist_error = true;
132
				break;
133
			}
134
			if ( ! jetpack_convert_ip_address( $item ) ) {
135
				$whitelist_error = true;
136
				break;
137
			}
138
			$new_item->ip_address = $item;
139
		}
140
		$new_items[] = $new_item;
141
	} // End item loop.
142
	if ( ! empty( $whitelist_error ) ) {
143
		return new WP_Error( 'invalid_ip', __( 'One of your IP addresses was not valid.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_ip'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
144
	}
145
	if ( $global ) {
146
		update_site_option( 'jetpack_protect_global_whitelist', $new_items );
147
		// Once a user has saved their global whitelist, we can permanently remove the legacy option.
148
		delete_site_option( 'jetpack_protect_whitelist' );
149
	} else {
150
		Jetpack_Options::update_option( 'protect_whitelist', $new_items );
151
	}
152
	return true;
153
}
154
155
/**
156
 * Jetpack Protect Get IP.
157
 *
158
 * @access public
159
 * @return IP.
0 ignored issues
show
Documentation introduced by
The doc-type IP. could not be parsed: Unknown type name "IP." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
160
 */
161
function jetpack_protect_get_ip() {
162
	$trusted_header_data = get_site_option( 'trusted_ip_header' );
163
	if ( isset( $trusted_header_data->trusted_header ) && isset( $_SERVER[ $trusted_header_data->trusted_header ] ) ) {
164
		$ip            = $_SERVER[ $trusted_header_data->trusted_header ];
165
		$segments      = $trusted_header_data->segments;
166
		$reverse_order = $trusted_header_data->reverse;
167
	} else {
168
		$ip = $_SERVER['REMOTE_ADDR'];
169
	}
170
171
	if ( ! $ip ) {
172
		return false;
173
	}
174
175
176
177
	$ips = explode( ',', $ip );
178
	if ( ! isset( $segments ) || ! $segments ) {
179
		$segments = 1;
180
	}
181
	if ( isset( $reverse_order ) && $reverse_order ) {
182
		$ips = array_reverse( $ips );
183
	}
184
	$ip_count = count( $ips );
185
	if ( 1 === $ip_count ) {
186
		return jetpack_clean_ip( $ips[0] );
187
	} elseif ( $ip_count >= $segments ) {
188
		$the_one = $ip_count - $segments;
189
		return jetpack_clean_ip( $ips[ $the_one ] );
190
	} else {
191
		return jetpack_clean_ip( $_SERVER['REMOTE_ADDR'] );
192
	}
193
}
194
195
/**
196
 * Jetpack Clean IP.
197
 *
198
 * @access public
199
 * @param mixed $ip IP.
200
 * @return $ip IP.
0 ignored issues
show
Documentation introduced by
The doc-type $ip could not be parsed: Unknown type name "$ip" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
201
 */
202
function jetpack_clean_ip( $ip ) {
203
204
	// Some misconfigured servers give back extra info, which comes after "unless"
205
	$ips = explode( ' unless ', $ip );
206
	$ip = $ips[0];
207
208
	$ip = trim( $ip );
209
	// Check for IPv4 IP cast as IPv6.
210
	if ( preg_match( '/^::ffff:(\d+\.\d+\.\d+\.\d+)$/', $ip, $matches ) ) {
211
		$ip = $matches[1];
212
	}
213
214
	if ( function_exists( 'parse_url' ) ) {
215
		$parsed_url = parse_url( $ip );
216
217
		if ( isset( $parsed_url['host'] ) ) {
218
			$ip = $parsed_url['host'];
219
		} elseif ( isset( $parsed_url['path'] ) ) {
220
			$ip = $parsed_url['path'];
221
		}
222
	} else {
223
		$colon_count = substr_count( $ip, ':' );
224
		if ( 1 == $colon_count ) {
225
			$ips = explode( ':', $ip );
226
			$ip  = $ips[0];
227
		}
228
	}
229
230
	return $ip;
231
}
232
233
/**
234
 * Checks an IP to see if it is within a private range.
235
 *
236
 * @param int $ip IP.
237
 * @return bool
238
 */
239
function jetpack_protect_ip_is_private( $ip ) {
240
	// We are dealing with ipv6, so we can simply rely on filter_var.
241
	if ( false === strpos( $ip, '.' ) ) {
242
		return ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE );
243
	}
244
	// We are dealing with ipv4.
245
	$private_ip4_addresses = array(
246
		'10.0.0.0|10.255.255.255',     // Single class A network.
247
		'172.16.0.0|172.31.255.255',   // 16 contiguous class B network.
248
		'192.168.0.0|192.168.255.255', // 256 contiguous class C network.
249
		'169.254.0.0|169.254.255.255', // Link-local address also referred to as Automatic Private IP Addressing.
250
		'127.0.0.0|127.255.255.255',    // localhost.
251
	);
252
	$long_ip = ip2long( $ip );
253
	if ( -1 !== $long_ip ) {
254
		foreach ( $private_ip4_addresses as $pri_addr ) {
255
			list ( $start, $end ) = explode( '|', $pri_addr );
256
			if ( $long_ip >= ip2long( $start ) && $long_ip <= ip2long( $end ) ) {
257
				return true;
258
			}
259
		}
260
	}
261
	return false;
262
}
263
264
/**
265
 * Uses inet_pton if available to convert an IP address to a binary string.
266
 * If inet_pton is not available, ip2long will convert the address to an integer.
267
 * Returns false if an invalid IP address is given.
268
 *
269
 * NOTE: ip2long will return false for any ipv6 address. servers that do not support
270
 * inet_pton will not support ipv6
271
 *
272
 * @access public
273
 * @param mixed $ip IP.
274
 * @return int|string|bool
275
 */
276
function jetpack_convert_ip_address( $ip ) {
277
	if ( function_exists( 'inet_pton' ) ) {
278
		return inet_pton( $ip );
279
	}
280
	return ip2long( $ip );
281
}
282
283
/**
284
 * Checks that a given IP address is within a given low - high range.
285
 * Servers that support inet_pton will use that function to convert the ip to number,
286
 * while other servers will use ip2long.
287
 *
288
 * NOTE: servers that do not support inet_pton cannot support ipv6.
289
 *
290
 * @access public
291
 * @param mixed $ip IP.
292
 * @param mixed $range_low Range Low.
293
 * @param mixed $range_high Range High.
294
 * @return Bool.
0 ignored issues
show
Documentation introduced by
The doc-type Bool. could not be parsed: Unknown type name "Bool." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
295
 */
296
function jetpack_protect_ip_address_is_in_range( $ip, $range_low, $range_high ) {
297
	// The inet_pton will give us binary string of an ipv4 or ipv6.
298
	// We can then use strcmp to see if the address is in range.
299
	if ( function_exists( 'inet_pton' ) ) {
300
		$ip_num  = inet_pton( $ip );
301
		$ip_low  = inet_pton( $range_low );
302
		$ip_high = inet_pton( $range_high );
303
		if ( $ip_num && $ip_low && $ip_high && strcmp( $ip_num, $ip_low ) >= 0 && strcmp( $ip_num, $ip_high ) <= 0 ) {
304
			return true;
305
		}
306
		// The ip2long will give us an integer of an ipv4 address only. it will produce FALSE for ipv6.
307
	} else {
308
		$ip_num  = ip2long( $ip );
309
		$ip_low  = ip2long( $range_low );
310
		$ip_high = ip2long( $range_high );
311
		if ( $ip_num && $ip_low && $ip_high && $ip_num >= $ip_low && $ip_num <= $ip_high ) {
312
			return true;
313
		}
314
	}
315
	return false;
316
}
317