Ticket #53668: 53668.6.diff
File 53668.6.diff, 16.2 KB (added by , 2 years ago) |
---|
-
src/wp-includes/class-wp-image-editor.php
591 591 * @return string|false 592 592 */ 593 593 protected static function get_extension( $mime_type = null ) { 594 $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); 595 596 if ( empty( $extensions[0] ) ) { 594 if ( empty( $mime_type ) ) { 597 595 return false; 598 596 } 599 597 600 return $extensions[0];598 return wp_get_default_extension_for_mime_type( $mime_type ); 601 599 } 602 600 } 603 601 -
src/wp-includes/functions.php
2486 2486 function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) { 2487 2487 // Sanitize the file name before we begin processing. 2488 2488 $filename = sanitize_file_name( $filename ); 2489 $ext2 = null;2490 2489 2491 2490 // Separate the filename into a name and extension. 2492 2491 $ext = pathinfo( $filename, PATHINFO_EXTENSION ); … … 2501 2500 $name = ''; 2502 2501 } 2503 2502 2503 // Reconstruct sanitized filename with lower case extension. 2504 $orig_ext = $ext; 2505 $ext = strtolower( $ext ); 2506 $filename = pathinfo( $filename, PATHINFO_FILENAME ) . $ext; 2507 2504 2508 /* 2505 2509 * Increment the file number until we have a unique file to save in $dir. 2506 2510 * Use callback if supplied. 2507 2511 */ 2508 2512 if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) { 2509 $filename = call_user_func( $unique_filename_callback, $dir, $name, $ ext );2513 $filename = call_user_func( $unique_filename_callback, $dir, $name, $orig_ext ); 2510 2514 } else { 2511 2515 $number = ''; 2512 2516 $fname = pathinfo( $filename, PATHINFO_FILENAME ); 2513 2517 2518 // If filename already versioned, get version and un-versioned filename. 2519 if ( preg_match( '/-(\d)$/', $fname, $matches ) ) { 2520 $fname = preg_replace( '/' . $matches[0] . '$/', '', $fname ); 2521 $number = (int) $matches[1]; 2522 } 2523 2514 2524 // Always append a number to file names that can potentially match image sub-size file names. 2515 2525 if ( $fname && preg_match( '/-(?:\d+x\d+|scaled|rotated)$/', $fname ) ) { 2516 $number = 1;2526 $number = (int) $number + 1; 2517 2527 2518 2528 // At this point the file name may not be unique. This is tested below and the $number is incremented. 2519 2529 $filename = str_replace( "{$fname}{$ext}", "{$fname}-{$number}{$ext}", $filename ); 2520 2530 } 2521 2531 2522 // Change '.ext' to lower case. 2523 if ( $ext && strtolower( $ext ) != $ext ) { 2524 $ext2 = strtolower( $ext ); 2525 $filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename ); 2532 // Check for both lower and upper case extension or image sub-sizes may be overwritten. 2533 $uc_ext = strtoupper( $ext ); 2534 $uc_ext_filename = preg_replace( '|' . preg_quote( $ext ) . '$|', $uc_ext, $filename ); 2526 2535 2527 // Check for both lower and upper case extension or image sub-sizes may be overwritten. 2528 while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$filename2}" ) ) { 2529 $new_number = (int) $number + 1; 2530 $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); 2531 $filename2 = str_replace( array( "-{$number}{$ext2}", "{$number}{$ext2}" ), "-{$new_number}{$ext2}", $filename2 ); 2532 $number = $new_number; 2533 } 2534 2535 $filename = $filename2; 2536 } else { 2537 while ( file_exists( $dir . "/{$filename}" ) ) { 2538 $new_number = (int) $number + 1; 2539 2540 if ( '' === "{$number}{$ext}" ) { 2541 $filename = "{$filename}-{$new_number}"; 2542 } else { 2543 $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); 2544 } 2545 2546 $number = $new_number; 2547 } 2536 while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$uc_ext_filename}" ) ) { 2537 $new_number = (int) $number + 1; 2538 $filename = str_replace( array( "{$fname}-{$number}{$ext}", "{$fname}{$number}{$ext}" ), "{$fname}-{$new_number}{$ext}", $filename ); 2539 $uc_ext_filename = str_replace( array( "{$fname}-{$number}{$uc_ext}", "{$fname}{$number}{$uc_ext}" ), "{$fname}-{$new_number}{$uc_ext}", $uc_ext_filename ); 2540 $number = $new_number; 2548 2541 } 2549 2542 2550 2543 // Prevent collisions with existing file names that contain dimension-like strings … … 2579 2572 } 2580 2573 2581 2574 if ( ! empty( $files ) ) { 2582 // The extension case may have changed above.2583 $new_ext = ! empty( $ext2 ) ? $ext2 : $ext;2584 2585 2575 // Ensure this never goes into infinite loop 2586 2576 // as it uses pathinfo() and regex in the check, but string replacement for the changes. 2587 2577 $count = count( $files ); … … 2589 2579 2590 2580 while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) { 2591 2581 $new_number = (int) $number + 1; 2592 $filename = str_replace( array( " -{$number}{$new_ext}", "{$number}{$new_ext}" ), "-{$new_number}{$new_ext}", $filename );2582 $filename = str_replace( array( "{$fname}-{$number}{$ext}", "{$fname}{$number}{$ext}" ), "{$fname}-{$new_number}{$ext}", $filename ); 2593 2583 $number = $new_number; 2594 2584 $i++; 2595 2585 } 2596 2586 } 2597 2587 } 2588 2589 // If a different file type might be produced for an image, check filename uniqueness for that format. 2590 $filename = _wp_check_alternate_output_format_uniqueness( $filename, $ext, $dir ); 2598 2591 } 2599 2592 2600 2593 /** … … 2603 2596 * @since 4.5.0 2604 2597 * 2605 2598 * @param string $filename Unique file name. 2606 * @param string $ ext File extension, eg. ".png".2599 * @param string $orig_ext File extension using original case, e.g. ".png" or ".PNG". 2607 2600 * @param string $dir Directory path. 2608 2601 * @param callable|null $unique_filename_callback Callback function that generates the unique file name. 2609 2602 */ 2610 return apply_filters( 'wp_unique_filename', $filename, $ ext, $dir, $unique_filename_callback );2603 return apply_filters( 'wp_unique_filename', $filename, $orig_ext, $dir, $unique_filename_callback ); 2611 2604 } 2612 2605 2613 2606 /** … … 2645 2638 } 2646 2639 2647 2640 /** 2641 * Helper function for wp_unique_filename to check potential alternate output formats for images. 2642 * 2643 * @since 5.8.1 2644 * @private 2645 * 2646 * @param string $filename 2647 * @param string $ext 2648 * @param string $dir 2649 * 2650 * @return string 2651 */ 2652 function _wp_check_alternate_output_format_uniqueness( $filename, $ext, $dir ) { 2653 static $checking_alternates; 2654 2655 if ( empty( $checking_alternates ) ) { 2656 $checking_alternates = true; 2657 $filename_changed = false; 2658 $file_type = wp_check_filetype_and_ext( trailingslashit( $dir ) . $filename, $filename ); 2659 $mime_type = $file_type['type']; 2660 2661 if ( ! empty( $mime_type ) && 0 === strpos( $mime_type, 'image/' ) ) { 2662 $output_formats = apply_filters( 'image_editor_output_format', array(), trailingslashit( $dir ) . $filename, $mime_type ); 2663 2664 if ( ! empty( $output_formats ) && is_array( $output_formats ) ) { 2665 // Temporarily add uploaded type to alts for simpler analysis. 2666 $alt_mime_types = array( $mime_type ); 2667 2668 // Alternate thumbnail format for uploaded file? 2669 if ( ! empty( $output_formats[ $mime_type ] ) ) { 2670 $alt_mime_types[] = $output_formats[ $mime_type ]; 2671 } 2672 2673 // Any other formats using uploaded or alternate format for thumbnails? 2674 $alt_mime_types = array_merge( $alt_mime_types, array_keys( array_intersect( $output_formats, $alt_mime_types ) ) ); 2675 2676 // Remove uploaded mime type as we've already tested that. 2677 $alt_mime_types = array_diff( $alt_mime_types, array( $mime_type ) ); 2678 } 2679 2680 if ( ! empty( $alt_mime_types ) ) { 2681 $alt_mime_types = array_unique( $alt_mime_types ); 2682 2683 foreach ( $alt_mime_types as $alt_mime_type ) { 2684 $alt_ext = wp_get_default_extension_for_mime_type( $alt_mime_type ); 2685 2686 if ( ! empty( $alt_ext ) && ".{$alt_ext}" !== $ext ) { 2687 $alt_filename = wp_basename( $filename, $ext ) . ".{$alt_ext}"; 2688 $alt_filename2 = wp_unique_filename( $dir, $alt_filename ); 2689 2690 // If a potential clash was found for alternate format, use its unique filename. 2691 if ( $alt_filename2 !== $alt_filename ) { 2692 $filename = wp_basename( $alt_filename2, ".{$alt_ext}" ) . $ext; 2693 $filename_changed = true; 2694 } 2695 } 2696 } 2697 } 2698 } 2699 $checking_alternates = false; 2700 2701 // Double check that incremented filename for original type or first tested alternate types do not clash. 2702 if ( $filename_changed ) { 2703 2704 return wp_unique_filename( $dir, $filename ); 2705 } 2706 } 2707 2708 return $filename; 2709 } 2710 2711 /** 2648 2712 * Create a file in the upload folder with given content. 2649 2713 * 2650 2714 * If there is an error, then the key 'error' will exist with the error message. … … 3042 3106 } 3043 3107 3044 3108 /** 3109 * Returns first matched extension from Mime-type, 3110 * as mapped from wp_get_mime_types() 3111 * 3112 * @since 5.8.1 3113 * 3114 * @param string $mime_type 3115 * 3116 * @return string|false 3117 * 3118 */ 3119 function wp_get_default_extension_for_mime_type( $mime_type ) { 3120 $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); 3121 3122 if ( empty( $extensions[0] ) ) { 3123 return false; 3124 } 3125 3126 return $extensions[0]; 3127 } 3128 3129 /** 3045 3130 * Returns the real mime type of an image file. 3046 3131 * 3047 3132 * This depends on exif_imagetype() or getimagesize() to determine real mime types. -
tests/phpunit/tests/functions.php
222 222 } 223 223 224 224 /** 225 * @ticket 53668 226 */ 227 function test__wp_check_alternate_output_format_uniqueness() { 228 $testdir = DIR_TESTDATA . '/images/'; 229 230 add_filter( 'upload_dir', array( $this, 'upload_dir_patch_basedir' ) ); 231 232 // Standard test that wp_unique_filename allows usage if file does not exist yet. 233 $this->assertSame( 'abcdef.png', wp_unique_filename( $testdir, 'abcdef.png' ), 'The abcdef.png image does not exist. The name does not need to be made unique.' ); 234 // Difference in extension does not affect wp_unique_filename by default (canola.jpg exists). 235 $this->assertSame( 'canola.png', wp_unique_filename( $testdir, 'canola.png' ), 'The canola.jpg image exists. Clashing base filename but not extension should not have name changed.' ); 236 // Run again with upper case extension. 237 $this->assertSame( 'canola.png', wp_unique_filename( $testdir, 'canola.PNG' ), 'The canola.jpg image exists. Clashing base filename but not extension should not have name changed.' ); 238 // Actual clash recognized. 239 $this->assertSame( 'canola-1.jpg', wp_unique_filename( $testdir, 'canola.jpg' ), 'The canola.jpg image exists. Uploading canola.jpg again should have unique name.' ); 240 // Future clash by regenerated thumbnails not applicable. 241 $this->assertSame( 'codeispoetry.jpg', wp_unique_filename( $testdir, 'codeispoetry.jpg' ), 'The codeispoetry.png image exists. Uploading codeispoetry.jpg does not need unique name.' ); 242 243 // When creating sub-sizes convert uploaded PNG images to JPG. 244 add_filter( 'image_editor_output_format', array( $this, 'image_editor_output_format_handler' ) ); 245 246 // Standard test that wp_unique_filename allows usage if file does not exist yet. 247 $this->assertSame( 'abcdef.png', wp_unique_filename( $testdir, 'abcdef.png' ), 'The abcdef.png image does not exist. Its name should not be changed.' ); 248 // Standard test that wp_unique_filename allows usage if file does not exist yet. 249 $this->assertSame( 'abcdef.bmp', wp_unique_filename( $testdir, 'abcdef.bmp' ), 'The abcdef.bmp and abcdef.pct images do not exist. When uploading abcdef.bmp its name should not be changed.' ); 250 // Difference in extension does affect wp_unique_filename when thumbnails use existing file's type. 251 $this->assertSame( 'canola-1.png', wp_unique_filename( $testdir, 'canola.png' ), 'The canola.jpg image exists. Uploading canola.png that will be converted to canola.jpg should produce unique file name.' ); 252 // Run again with upper case extension. 253 $this->assertSame( 'canola-1.png', wp_unique_filename( $testdir, 'canola.PNG' ), 'The canola.jpg image exists. Uploading canola.PNG that will be converted to canola.jpg should produce unique file name.' ); 254 // Actual clash recognized. 255 $this->assertSame( 'canola-1.jpg', wp_unique_filename( $testdir, 'canola.jpg' ), 'Existing file should have name changed.' ); 256 // Actual clash with images with different extensions. 257 $this->assertSame( 'test-image-3.png', wp_unique_filename( $testdir, 'test-image.png' ), 'The test-image.png, test-image-1-100x100.jpg, and test-image-2.gif images exist. All of them may be intersected when creating sub-sizes for the new image: test-image.png, so its filename should be unique.' ); 258 // Future clash by regenerated thumbnails recognized. 259 $this->assertSame( 'codeispoetry-1.jpg', wp_unique_filename( $testdir, 'codeispoetry.jpg' ), 'The codeispoetry.png image exists. When regenerating thumbnails for it they will be converted to JPG. The name of the newly uploaded codeispoetry.jpg should be made unique.' ); 260 261 // Ensure plugins take part in analysis of alternate extensions. 262 add_filter( 'wp_unique_filename', array( $this, 'wp_unique_filename_handler' ), 10, 4 ); 263 264 // Actual clash with images with different extensions. 265 $this->assertSame( 'test-image-4.png', wp_unique_filename( $testdir, 'test-image.png' ), 'The test-image.png, test-image-1-100x100.jpg, test-image-2.gif and offloaded test-image-3.jpg images exist. All of them may be intersected when creating sub-sizes for the new image: test-image.png, so its filename should be unique.' ); 266 267 remove_filter( 'wp_unique_filename', array( $this, 'wp_unique_filename_handler' ) ); 268 remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_format_handler' ) ); 269 remove_filter( 'upload_dir', array( $this, 'upload_dir_patch_basedir' ) ); 270 } 271 272 /** 273 * Changes the output format when editing images. When uploading a PNG file 274 * it will be converted to JPG (if the image editor in PHP supports it). 275 * 276 * @param array $formats 277 * 278 * @return array 279 */ 280 public function image_editor_output_format_handler( $formats ) { 281 $formats['image/png'] = 'image/jpeg'; 282 $formats['image/gif'] = 'image/jpeg'; 283 $formats['image/pct'] = 'image/bmp'; 284 285 return $formats; 286 } 287 288 /** 289 * Pretend that a filename has been uploaded but removed from the server's filesystem 290 * e.g. by an offloader plugin on a cluster with ephemeral filesystem. 291 * 292 * @param string $filename 293 * @param string $orig_ext 294 * @param string $dir 295 * @param callable $unique_filename_callback 296 * 297 * @return string 298 */ 299 public function wp_unique_filename_handler( $filename, $orig_ext, $dir, $unique_filename_callback ) { 300 if ( 'test-image-3.jpg' === $filename ) { 301 // Bump the filename version and re-check. 302 $filename = 'test-image-4.jpg'; 303 $filename = wp_unique_filename( $dir, $filename ); 304 } 305 306 return $filename; 307 } 308 309 /** 225 310 * @dataProvider data_is_not_serialized 226 311 */ 227 312 function test_maybe_serialize( $value ) { … … 1946 2031 array( 'application/activity+json, application/nojson', true ), 1947 2032 ); 1948 2033 } 2034 2035 /** 2036 * @ticket 53668 2037 */ 2038 public function test_wp_get_default_extension_for_mime_type() { 2039 $this->assertEquals( 'jpg', wp_get_default_extension_for_mime_type( 'image/jpeg' ), 'jpg not returned as default extension for "image/jpeg"' ); 2040 $this->assertNotEquals( 'jpeg', wp_get_default_extension_for_mime_type( 'image/jpeg' ), 'jpeg should not be returned as default extension for "image/jpeg"' ); 2041 $this->assertEquals( 'png', wp_get_default_extension_for_mime_type( 'image/png' ), 'png not returned as default extension for "image/png"' ); 2042 $this->assertFalse( wp_get_default_extension_for_mime_type( 'wibble/wobble' ), 'false not returned for unrecognized mime type' ); 2043 $this->assertFalse( wp_get_default_extension_for_mime_type( '' ), 'false not returned when empty string as mime type supplied' ); 2044 $this->assertFalse( wp_get_default_extension_for_mime_type( ' ' ), 'false not returned when empty string as mime type supplied' ); 2045 $this->assertFalse( wp_get_default_extension_for_mime_type( 123 ), 'false not returned when int as mime type supplied' ); 2046 $this->assertFalse( wp_get_default_extension_for_mime_type( null ), 'false not returned when null as mime type supplied' ); 2047 } 1949 2048 }