Ticket #43545: 43545.7.diff
File 43545.7.diff, 18.4 KB (added by , 7 years ago) |
---|
-
src/wp-admin/includes/class-wp-community-events.php
diff --git src/wp-admin/includes/class-wp-community-events.php src/wp-admin/includes/class-wp-community-events.php index fe16dc4..77bf34d 100644
class WP_Community_Events { 234 234 */ 235 235 public static function get_unsafe_client_ip() { 236 236 $client_ip = false; 237 $ip_prefix = '';238 237 239 238 // In order of preference, with the best ones for this purpose first. 240 239 $address_headers = array( … … class WP_Community_Events { 265 264 return false; 266 265 } 267 266 268 // Detect what kind of IP address this is. 269 $is_ipv6 = substr_count( $client_ip, ':' ) > 1; 270 $is_ipv4 = ( 3 === substr_count( $client_ip, '.' ) ); 267 $anon_ip = wp_privacy_anonymize_ip( $client_ip, true ); 271 268 272 if ( $is_ipv6 && $is_ipv4 ) { 273 // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4. 274 $ip_prefix = '::ffff:'; 275 $client_ip = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $client_ip ); 276 $client_ip = str_replace( ']', '', $client_ip ); 277 $is_ipv6 = false; 278 } 279 280 if ( $is_ipv6 ) { 281 // IPv6 addresses will always be enclosed in [] if there's a port. 282 $left_bracket = strpos( $client_ip, '[' ); 283 $right_bracket = strpos( $client_ip, ']' ); 284 $percent = strpos( $client_ip, '%' ); 285 $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; 286 287 // Strip the port (and [] from IPv6 addresses), if they exist. 288 if ( false !== $left_bracket && false !== $right_bracket ) { 289 $client_ip = substr( $client_ip, $left_bracket + 1, $right_bracket - $left_bracket - 1 ); 290 } elseif ( false !== $left_bracket || false !== $right_bracket ) { 291 // The IP has one bracket, but not both, so it's malformed. 292 return false; 293 } 294 295 // Strip the reachability scope. 296 if ( false !== $percent ) { 297 $client_ip = substr( $client_ip, 0, $percent ); 298 } 299 300 // No invalid characters should be left. 301 if ( preg_match( '/[^0-9a-f:]/i', $client_ip ) ) { 302 return false; 303 } 304 305 // Partially anonymize the IP by reducing it to the corresponding network ID. 306 if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) { 307 $client_ip = inet_ntop( inet_pton( $client_ip ) & inet_pton( $netmask ) ); 308 } 309 } elseif ( $is_ipv4 ) { 310 // Strip any port and partially anonymize the IP. 311 $last_octet_position = strrpos( $client_ip, '.' ); 312 $client_ip = substr( $client_ip, 0, $last_octet_position ) . '.0'; 313 } else { 269 if ( '0.0.0.0' === $anon_ip || '::' === $anon_ip ) { 314 270 return false; 315 271 } 316 272 317 // Restore the IPv6 prefix to compatibility mode addresses. 318 return $ip_prefix . $client_ip; 273 return $anon_ip; 319 274 } 320 275 321 276 /** -
src/wp-includes/functions.php
diff --git src/wp-includes/functions.php src/wp-includes/functions.php index 3f11cce..26ab303 100644
All at ###SITENAME### 6127 6127 ), $email_change_email['message'], $email_change_email['headers'] 6128 6128 ); 6129 6129 } 6130 6131 /** 6132 * Return an anonymized IPv4 or IPv6 address. 6133 * 6134 * @since 5.0.0 Abstracted from `WP_Community_Events::get_unsafe_client_ip()`. 6135 * 6136 * @param string $ip_addr The IPv4 or IPv6 address to be anonymized. 6137 * @param bool $ipv6_fallback Optional. Whether to return the original IPv6 address if the needed functions 6138 * to anonymize it are not present. Default false, return `::` (unspecified address). 6139 * @return string The anonymized IP address. 6140 */ 6141 function wp_privacy_anonymize_ip( $ip_addr, $ipv6_fallback = false ) { 6142 // Detect what kind of IP address this is. 6143 $ip_prefix = ''; 6144 $is_ipv6 = substr_count( $ip_addr, ':' ) > 1; 6145 $is_ipv4 = ( 3 === substr_count( $ip_addr, '.' ) ); 6146 6147 if ( $is_ipv6 && $is_ipv4 ) { 6148 // IPv6 compatibility mode, temporarily strip the IPv6 part, and treat it like IPv4. 6149 $ip_prefix = '::ffff:'; 6150 $ip_addr = preg_replace( '/^\[?[0-9a-f:]*:/i', '', $ip_addr ); 6151 $ip_addr = str_replace( ']', '', $ip_addr ); 6152 $is_ipv6 = false; 6153 } 6154 6155 if ( $is_ipv6 ) { 6156 // IPv6 addresses will always be enclosed in [] if there's a port. 6157 $left_bracket = strpos( $ip_addr, '[' ); 6158 $right_bracket = strpos( $ip_addr, ']' ); 6159 $percent = strpos( $ip_addr, '%' ); 6160 $netmask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; 6161 6162 // Strip the port (and [] from IPv6 addresses), if they exist. 6163 if ( false !== $left_bracket && false !== $right_bracket ) { 6164 $ip_addr = substr( $ip_addr, $left_bracket + 1, $right_bracket - $left_bracket - 1 ); 6165 } elseif ( false !== $left_bracket || false !== $right_bracket ) { 6166 // The IP has one bracket, but not both, so it's malformed. 6167 return '::'; 6168 } 6169 6170 // Strip the reachability scope. 6171 if ( false !== $percent ) { 6172 $ip_addr = substr( $ip_addr, 0, $percent ); 6173 } 6174 6175 // No invalid characters should be left. 6176 if ( preg_match( '/[^0-9a-f:]/i', $ip_addr ) ) { 6177 return '::'; 6178 } 6179 6180 // Partially anonymize the IP by reducing it to the corresponding network ID. 6181 if ( function_exists( 'inet_pton' ) && function_exists( 'inet_ntop' ) ) { 6182 $ip_addr = inet_ntop( inet_pton( $ip_addr ) & inet_pton( $netmask ) ); 6183 if ( false === $ip_addr) { 6184 return '::'; 6185 } 6186 } elseif ( ! $ipv6_fallback ) { 6187 return '::'; 6188 } 6189 } elseif ( $is_ipv4 ) { 6190 // Strip any port and partially anonymize the IP. 6191 $last_octet_position = strrpos( $ip_addr, '.' ); 6192 $ip_addr = substr( $ip_addr, 0, $last_octet_position ) . '.0'; 6193 } else { 6194 return '0.0.0.0'; 6195 } 6196 6197 // Restore the IPv6 prefix to compatibility mode addresses. 6198 return $ip_prefix . $ip_addr; 6199 } 6200 6201 /** 6202 * Return uniform "anonymous" data by type. 6203 * 6204 * @since 5.0.0 6205 * 6206 * @param string $type The type of data to be anonymized. 6207 * @param string $data Optional The data to be anonymized. 6208 * @return string The anonymous data for the requested type. 6209 */ 6210 function wp_privacy_anonymize_data( $type, $data = '' ) { 6211 6212 switch ( $type ) { 6213 case 'email': 6214 $anonymous = 'deleted@site.invalid'; 6215 break; 6216 case 'url': 6217 $anonymous = 'https://site.invalid'; 6218 break; 6219 case 'ip': 6220 $anonymous = wp_privacy_anonymize_ip( $data ); 6221 break; 6222 case 'date': 6223 $anonymous = '0000-00-00 00:00:00'; 6224 break; 6225 case 'text': 6226 /* translators: deleted text */ 6227 $anonymous = __( '[deleted]' ); 6228 break; 6229 case 'longtext': 6230 /* translators: deleted long text */ 6231 $anonymous = __( 'This content was deleted by the author.' ); 6232 break; 6233 default: 6234 $anonymous = ''; 6235 } 6236 6237 /** 6238 * Filters the anonymous data for each type. 6239 * 6240 * @since 5.0.0 6241 * 6242 * @param string $anonymous Anonymized data. 6243 * @param string $type Type of the data. 6244 * @param string $data Original data. 6245 */ 6246 return apply_filters( 'wp_privacy_anonymize_data', $anonymous, $type, $data ); 6247 } -
tests/phpunit/tests/admin/includesCommunityEvents.php
diff --git tests/phpunit/tests/admin/includesCommunityEvents.php tests/phpunit/tests/admin/includesCommunityEvents.php index bd6a6cd..0de7f0a 100644
class Test_WP_Community_Events extends WP_UnitTestCase { 479 479 /** 480 480 * Test that get_unsafe_client_ip() properly anonymizes all possible address formats 481 481 * 482 * @dataProvider data_get_unsafe_client_ip _anonymization482 * @dataProvider data_get_unsafe_client_ip 483 483 * 484 484 * @ticket 41083 485 485 */ 486 public function test_get_unsafe_client_ip_anonymization( $raw_ip, $expected_result ) { 487 $_SERVER['REMOTE_ADDR'] = $raw_ip; 488 $actual_result = WP_Community_Events::get_unsafe_client_ip(); 486 public function test_get_unsafe_client_ip( $raw_ip, $expected_result ) { 487 $_SERVER['REMOTE_ADDR'] = 'this should not be used'; 488 $_SERVER['HTTP_CLIENT_IP'] = $raw_ip; 489 $actual_result = WP_Community_Events::get_unsafe_client_ip(); 489 490 490 491 $this->assertEquals( $expected_result, $actual_result ); 491 492 } 492 493 493 public function data_get_unsafe_client_ip_anonymization() { 494 /** 495 * Provide test cases for `test_get_unsafe_client_ip()`. 496 * 497 * @return array 498 */ 499 public function data_get_unsafe_client_ip() { 494 500 return array( 495 // Invalid IP. 496 array( 497 '', // Raw IP address 498 false, // Expected result 499 ), 500 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 501 array( 502 'unknown', 503 false, 504 ), 505 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 501 // Handle '::' returned from `wp_privacy_anonymize_ip()`. 506 502 array( 507 503 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001', 508 504 false, 509 505 ), 510 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 511 array( 512 'or=\"1000:0000:0000:0000:0000:0000:0000:0001', 513 false, 514 ), 515 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 506 507 // Handle '0.0.0.0' returned from `wp_privacy_anonymize_ip()`. 516 508 array( 517 ' 1000:0000:0000:0000:0000:0000:0000:0001or=\"',509 'unknown', 518 510 false, 519 511 ), 520 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 521 array( 522 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]:400', 523 '1000::', 524 ), 525 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 526 array( 527 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]', 528 '1000::', 529 ), 530 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 531 array( 532 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]400', 533 '1000::', 534 ), 535 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 536 array( 537 '[1000:0000:0000:0000:0000:0000:0000:0001]:235\"or=', 538 '1000::', 539 ), 540 // IPv4, no port 541 array( 542 '10.20.30.45', 543 '10.20.30.0', 544 ), 545 // IPv4, port 512 513 // Valid IPv4. 546 514 array( 547 '1 0.20.30.45:20000',548 '1 0.20.30.0',515 '198.143.164.252', 516 '198.143.164.0', 549 517 ), 550 // IPv6, no port 518 519 // Valid IPv6. 551 520 array( 552 521 '2a03:2880:2110:df07:face:b00c::1', 553 522 '2a03:2880:2110:df07::', 554 523 ), 555 // IPv6, port556 array(557 '[2a03:2880:2110:df07:face:b00c::1]:20000',558 '2a03:2880:2110:df07::',559 ),560 // IPv6, no port, reducible representation561 array(562 '0000:0000:0000:0000:0000:0000:0000:0001',563 '::',564 ),565 // IPv6, no port, partially reducible representation566 array(567 '1000:0000:0000:0000:0000:0000:0000:0001',568 '1000::',569 ),570 // IPv6, port, reducible representation571 array(572 '[0000:0000:0000:0000:0000:0000:0000:0001]:1234',573 '::',574 ),575 // IPv6, port, partially reducible representation576 array(577 '[1000:0000:0000:0000:0000:0000:0000:0001]:5678',578 '1000::',579 ),580 // IPv6, no port, reduced representation581 array(582 '::',583 '::',584 ),585 // IPv6, no port, reduced representation586 array(587 '::1',588 '::',589 ),590 // IPv6, port, reduced representation591 array(592 '[::]:20000',593 '::',594 ),595 // IPv6, address brackets without port delimiter and number, reduced representation596 array(597 '[::1]',598 '::',599 ),600 // IPv6, no port, compatibility mode601 array(602 '::ffff:10.15.20.25',603 '::ffff:10.15.20.0',604 ),605 // IPv6, port, compatibility mode606 array(607 '[::FFFF:10.15.20.25]:30000',608 '::ffff:10.15.20.0',609 ),610 // IPv6, no port, compatibility mode shorthand611 array(612 '::127.0.0.1',613 '::ffff:127.0.0.0',614 ),615 // IPv6, port, compatibility mode shorthand616 array(617 '[::127.0.0.1]:30000',618 '::ffff:127.0.0.0',619 ),620 // IPv6 with reachability scope621 array(622 'fe80::b059:65f4:e877:c40%16',623 'fe80::',624 ),625 // IPv6 with reachability scope626 array(627 'FE80::B059:65F4:E877:C40%eth0',628 'fe80::',629 ),630 524 ); 631 525 } 632 526 } -
new file tests/phpunit/tests/functions/anonymization.php
diff --git tests/phpunit/tests/functions/anonymization.php tests/phpunit/tests/functions/anonymization.php new file mode 100644 index 0000000..a5c9c8a
- + 1 <?php 2 /** 3 * Test anonymization functions. 4 * 5 * @package WordPress 6 * 7 * @since 5.0.0 8 */ 9 10 /** 11 * Class Tests_Functions_Anonymization. 12 * 13 * @group functions.php 14 * @group privacy 15 * 16 * @since 5.0.0 17 */ 18 class Tests_Functions_Anonymization extends WP_UnitTestCase { 19 /** 20 * Test that wp_privacy_anonymize_ip() properly anonymizes all possible IP address formats. 21 * 22 * @dataProvider data_wp_privacy_anonymize_ip 23 * 24 * @ticket 41083 25 * @ticket 43545 26 * 27 * @param string $raw_ip Raw IP address. 28 * @param string $expected_result Expected result. 29 */ 30 public function test_wp_privacy_anonymize_ip( $raw_ip, $expected_result ) { 31 if ( ! function_exists( 'inet_ntop' ) || ! function_exists( 'inet_pton' ) ) { 32 $this->markTestSkipped( 'This test requires both the inet_ntop() and inet_pton() functions.' ); 33 } 34 35 $actual_result = wp_privacy_anonymize_data( 'ip', $raw_ip ); 36 37 /* Todo test ipv6_fallback mode if keeping it.*/ 38 39 $this->assertEquals( $expected_result, $actual_result ); 40 } 41 42 /** 43 * Provide test cases for `test_wp_privacy_anonymize_ip()`. 44 * 45 * @since 5.0.0 Moved from `Test_WP_Community_Events::data_get_unsafe_client_ip_anonymization()`. 46 * 47 * @return array { 48 * @type array { 49 * @string string $raw_ip Raw IP address. 50 * @string string $expected_result Expected result. 51 * } 52 * } 53 */ 54 public function data_wp_privacy_anonymize_ip() { 55 return array( 56 // Invalid IP. 57 array( 58 null, 59 '0.0.0.0', 60 ), 61 array( 62 '', 63 '0.0.0.0', 64 ), 65 array( 66 '0.0.0.0.0', 67 '0.0.0.0', 68 ), 69 array( 70 '0000:0000:0000:0000:0000:0000:0127:2258', 71 '::', 72 ), 73 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 74 array( 75 'unknown', 76 '0.0.0.0', 77 ), 78 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 79 array( 80 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001', 81 '::', 82 ), 83 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 84 array( 85 'or=\"1000:0000:0000:0000:0000:0000:0000:0001', 86 '::', 87 ), 88 // Invalid IP. Sometimes proxies add things like this, or other arbitrary strings. 89 array( 90 '1000:0000:0000:0000:0000:0000:0000:0001or=\"', 91 '::', 92 ), 93 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 94 array( 95 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]:400', 96 '1000::', 97 ), 98 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 99 array( 100 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]', 101 '1000::', 102 ), 103 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 104 array( 105 'or=\"[1000:0000:0000:0000:0000:0000:0000:0001]400', 106 '1000::', 107 ), 108 // Malformed string with valid IP substring. Sometimes proxies add things like this, or other arbitrary strings. 109 array( 110 '[1000:0000:0000:0000:0000:0000:0000:0001]:235\"or=', 111 '1000::', 112 ), 113 // IPv4, no port. 114 array( 115 '10.20.30.45', 116 '10.20.30.0', 117 ), 118 // IPv4, port. 119 array( 120 '10.20.30.45:20000', 121 '10.20.30.0', 122 ), 123 // IPv4, netmask. 124 array( 125 '10.20.30.45/24', 126 '10.20.30.0', 127 ), 128 // IPv6, no port. 129 array( 130 '2a03:2880:2110:df07:face:b00c::1', 131 '2a03:2880:2110:df07::', 132 ), 133 // IPv6, port. 134 array( 135 '[2a03:2880:2110:df07:face:b00c::1]:20000', 136 '2a03:2880:2110:df07::', 137 ), 138 // IPv6, no port, reducible representation. 139 array( 140 '0000:0000:0000:0000:0000:0000:0000:0001', 141 '::', 142 ), 143 // IPv6, no port, partially reducible representation. 144 array( 145 '1000:0000:0000:0000:0000:0000:0000:0001', 146 '1000::', 147 ), 148 // IPv6, port, reducible representation. 149 array( 150 '[0000:0000:0000:0000:0000:0000:0000:0001]:1234', 151 '::', 152 ), 153 // IPv6, port, partially reducible representation. 154 array( 155 '[1000:0000:0000:0000:0000:0000:0000:0001]:5678', 156 '1000::', 157 ), 158 // IPv6, no port, reduced representation. 159 array( 160 '::', 161 '::', 162 ), 163 // IPv6, no port, reduced representation. 164 array( 165 '::1', 166 '::', 167 ), 168 // IPv6, port, reduced representation. 169 array( 170 '[::]:20000', 171 '::', 172 ), 173 // IPv6, address brackets without port delimiter and number, reduced representation. 174 array( 175 '[::1]', 176 '::', 177 ), 178 // IPv6, no port, compatibility mode. 179 array( 180 '::ffff:10.15.20.25', 181 '::ffff:10.15.20.0', 182 ), 183 // IPv6, port, compatibility mode. 184 array( 185 '[::FFFF:10.15.20.25]:30000', 186 '::ffff:10.15.20.0', 187 ), 188 // IPv6, no port, compatibility mode shorthand. 189 array( 190 '::127.0.0.1', 191 '::ffff:127.0.0.0', 192 ), 193 // IPv6, port, compatibility mode shorthand. 194 array( 195 '[::127.0.0.1]:30000', 196 '::ffff:127.0.0.0', 197 ), 198 // IPv6 with reachability scope. 199 array( 200 'fe80::b059:65f4:e877:c40%16', 201 'fe80::', 202 ), 203 // IPv6 with reachability scope. 204 array( 205 'FE80::B059:65F4:E877:C40%eth0', 206 'fe80::', 207 ), 208 ); 209 } 210 211 /** 212 * Test email anonymization of `wp_privacy_anonymize_data()`. 213 */ 214 public function test_anonymize_email() { 215 $this->assertEquals( 'deleted@site.invalid', wp_privacy_anonymize_data( 'email', 'bar@example.com' ) ); 216 } 217 218 /** 219 * Test url anonymization of `wp_privacy_anonymize_data()`. 220 */ 221 public function test_anonymize_url() { 222 $this->assertEquals( 'https://site.invalid', wp_privacy_anonymize_data( 'url', 'https://example.com/author/username' ) ); 223 } 224 225 /** 226 * Test date anonymization of `wp_privacy_anonymize_data()`. 227 */ 228 public function test_anonymize_date() { 229 $this->assertEquals( '0000-00-00 00:00:00', wp_privacy_anonymize_data( 'date', '2003-12-25 12:34:56' ) ); 230 } 231 232 /** 233 * Test text anonymization of `wp_privacy_anonymize_data()`. 234 */ 235 public function test_anonymize_text() { 236 $text = __( 'Four score and seven years ago' ); 237 $this->assertEquals( '[deleted]', wp_privacy_anonymize_data( 'text', $text ) ); 238 } 239 240 /** 241 * Test long text anonymization of `wp_privacy_anonymize_data()`. 242 */ 243 public function test_anonymize_long_text() { 244 $text = __( 'Four score and seven years ago' ); 245 $this->assertEquals( 'This content was deleted by the author.', wp_privacy_anonymize_data( 'longtext', $text ) ); 246 } 247 }