diff --git a/wp-admin/includes/class-wp-community-events.php b/wp-admin/includes/class-wp-community-events.php index 79c4a85adb..bbb743d555 100644 --- a/wp-admin/includes/class-wp-community-events.php +++ b/wp-admin/includes/class-wp-community-events.php @@ -234,7 +234,6 @@ class WP_Community_Events { */ public static function get_unsafe_client_ip() { $client_ip = $netmask = false; - $ip_prefix = ''; // In order of preference, with the best ones for this purpose first. $address_headers = array( @@ -265,43 +264,13 @@ class WP_Community_Events { return false; } - // Detect what kind of IP address this is. - $is_ipv6 = substr_count( $client_ip, ':' ) > 1; - $is_ipv4 = ( 3 === substr_count( $client_ip, '.' ) ); + $anon_ip = wp_privacy_anonymize_ip( $client_ip, true ); - if ( $is_ipv6 && $is_ipv4 ) { - // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4. - $ip_prefix = '::ffff:'; - $client_ip = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $client_ip ); - $client_ip = str_replace( ']', '', $client_ip ); - $is_ipv6 = false; - } - - if ( $is_ipv6 ) { - // IPv6 addresses will always be enclosed in [] if there's a port. - $ip_start = 1; - $ip_end = (int) strpos( $client_ip, ']' ) - 1; - $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; - - // Strip the port (and [] from IPv6 addresses), if they exist. - if ( $ip_end > 0 ) { - $client_ip = substr( $client_ip, $ip_start, $ip_end ); - } - - // Partially anonymize the IP by reducing it to the corresponding network ID. - if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) { - $client_ip = inet_ntop( inet_pton( $client_ip ) & inet_pton( $netmask ) ); - } - } elseif ( $is_ipv4 ) { - // Strip any port and partially anonymize the IP. - $last_octet_position = strrpos( $client_ip, '.' ); - $client_ip = substr( $client_ip, 0, $last_octet_position ) . '.0'; - } else { + if ( '0.0.0.0' === $anon_ip || '::' === $anon_ip ) { return false; } - // Restore the IPv6 prefix to compatibility mode addresses. - return $ip_prefix . $client_ip; + return $anon_ip; } /** diff --git a/wp-includes/functions.php b/wp-includes/functions.php index c8e1e7148f..655a18ab4f 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -5816,3 +5816,121 @@ All at ###SITENAME### $site_name ), $email_change_email['message'], $email_change_email['headers'] ); } + +/** + * Return an anonymized IPv4 or IPv6 address. + * + * @since 4.9.6 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`. + * + * @param string $ip_addr The IPv4 or IPv6 address to be anonymized. + * @param bool $ipv6_fallback Optional. Whether to return the original IPv6 address if the needed functions + * to anonymize it are not present. Default false, return `::` (unspecified address). + * @return string The anonymized IP address. + */ +function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) { + // Detect what kind of IP address this is. + $ip_prefix = ''; + $is_ipv6 = substr_count( $ip_addr, ':' ) > 1; + $is_ipv4 = ( 3 === substr_count( $ip_addr, '.' ) ); + + if ( $is_ipv6 && $is_ipv4 ) { + // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4. + $ip_prefix = '::ffff:'; + $ip_addr = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr ); + $ip_addr = str_replace( ']', '', $ip_addr ); + $is_ipv6 = false; + } + + if ( $is_ipv6 ) { + // IPv6 addresses will always be enclosed in [] if there's a port. + $left_bracket = strpos( $ip_addr, '[' ); + $right_bracket = strpos( $ip_addr, ']' ); + $percent = strpos( $ip_addr, '%' ); + $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + + // Strip the port (and [] from IPv6 addresses), if they exist. + if ( false !== $left_bracket && false !== $right_bracket ) { + $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 ); + } elseif ( false !== $left_bracket || false !== $right_bracket ) { + // The IP has one bracket, but not both, so it's malformed. + return '::'; + } + + // Strip the reachability scope. + if ( false !== $percent ) { + $ip_addr = substr( $ip_addr, 0, $percent ); + } + + // No invalid characters should be left. + if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) { + return '::'; + } + + // Partially anonymize the IP by reducing it to the corresponding network ID. + if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) { + $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) ); + if ( false === $ip_addr) { + return '::'; + } + } elseif ( ! $ipv6_fallback ) { + return '::'; + } + } elseif ( $is_ipv4 ) { + // Strip any port and partially anonymize the IP. + $last_octet_position = strrpos( $ip_addr, '.' ); + $ip_addr = substr( $ip_addr, 0, $last_octet_position ) . '.0'; + } else { + return '0.0.0.0'; + } + + // Restore the IPv6 prefix to compatibility mode addresses. + return $ip_prefix . $ip_addr; +} + +/** + * Return uniform "anonymous" data by type. + * + * @since 4.9.6 + * + * @param string $type The type of data to be anonymized. + * @param string $data Optional The data to be anonymized. + * @return string The anonymous data for the requested type. + */ +function wp_privacy_anonymize_data( $type, $data = '' ) { + + switch ( $type ) { + case 'email': + $anonymous = 'deleted@site.invalid'; + break; + case 'url': + $anonymous = 'https://site.invalid'; + break; + case 'ip': + $anonymous = wp_privacy_anonymize_ip( $data ); + break; + case 'date': + $anonymous = '0000-00-00 00:00:00'; + break; + case 'text': + /* translators: deleted text */ + $anonymous = __( '[deleted]' ); + break; + case 'longtext': + /* translators: deleted long text */ + $anonymous = __( 'This content was deleted by the author.' ); + break; + default: + $anonymous = ''; + } + + /** + * Filters the anonymous data for each type. + * + * @since 4.9.6 + * + * @param string $anonymous Anonymized data. + * @param string $type Type of the data. + * @param string $data Original data. + */ + return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data ); +} diff --git a/wp-includes/version.php b/wp-includes/version.php index 47703752d7..8c83d05476 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.9.6-alpha-43080'; +$wp_version = '4.9.6-alpha-43082'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.