Make WordPress Core


Ignore:
Timestamp:
08/31/2021 11:56:11 PM (5 years ago)
Author:
desrosj
Message:

Media: Fix wp_unique_filename() to check for name collisions with all alternate file names when an image may be converted after uploading. This includes possible collinions with pre-existing images whose sub-sizes/thumbnails are regenerated.

Props ianmjones, azaozz.
Merges [51653] to the 5.8 branch.
Fixes #53668.

Location:
branches/5.8
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • branches/5.8

  • branches/5.8/src/wp-includes/functions.php

    r51223 r51706  
    24862486        $ext2     = null;
    24872487
     2488        // Initialize vars used in the wp_unique_filename filter.
     2489        $number        = '';
     2490        $alt_filenames = array();
     2491
    24882492        // Separate the filename into a name and extension.
    24892493        $ext  = pathinfo( $filename, PATHINFO_EXTENSION );
    … …  
    25062510                $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext );
    25072511        } else {
    2508                 $number = '';
    2509                 $fname  = pathinfo( $filename, PATHINFO_FILENAME );
     2512                $fname = pathinfo( $filename, PATHINFO_FILENAME );
    25102513
    25112514                // Always append a number to file names that can potentially match image sub-size file names.
    … …  
    25172520                }
    25182521
    2519                 // Change '.ext' to lower case.
    2520                 if ( $ext && strtolower( $ext ) != $ext ) {
    2521                         $ext2      = strtolower( $ext );
    2522                         $filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename );
    2523 
    2524                         // Check for both lower and upper case extension or image sub-sizes may be overwritten.
    2525                         while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$filename2}" ) ) {
    2526                                 $new_number = (int) $number + 1;
    2527                                 $filename   = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
    2528                                 $filename2  = str_replace( array( "-{$number}{$ext2}", "{$number}{$ext2}" ), "-{$new_number}{$ext2}", $filename2 );
    2529                                 $number     = $new_number;
     2522                // Get the mime type. Uploaded files were already checked with wp_check_filetype_and_ext()
     2523                // in _wp_handle_upload(). Using wp_check_filetype() would be sufficient here.
     2524                $file_type = wp_check_filetype( $filename );
     2525                $mime_type = $file_type['type'];
     2526
     2527                $is_image    = ( ! empty( $mime_type ) && 0 === strpos( $mime_type, 'image/' ) );
     2528                $upload_dir  = wp_get_upload_dir();
     2529                $lc_filename = null;
     2530
     2531                $lc_ext = strtolower( $ext );
     2532                $_dir   = trailingslashit( $dir );
     2533
     2534                // If the extension is uppercase add an alternate file name with lowercase extension. Both need to be tested
     2535                // for uniqueness as the extension will be changed to lowercase for better compatibility with different filesystems.
     2536                // Fixes an inconsistency in WP < 2.9 where uppercase extensions were allowed but image sub-sizes were created with
     2537                // lowercase extensions.
     2538                if ( $ext && $lc_ext !== $ext ) {
     2539                        $lc_filename = preg_replace( '|' . preg_quote( $ext ) . '$|', $lc_ext, $filename );
     2540                }
     2541
     2542                // Increment the number added to the file name if there are any files in $dir whose names match one of the
     2543                // possible name variations.
     2544                while ( file_exists( $_dir . $filename ) || ( $lc_filename && file_exists( $_dir . $lc_filename ) ) ) {
     2545                        $new_number = (int) $number + 1;
     2546
     2547                        if ( $lc_filename ) {
     2548                                $lc_filename = str_replace( array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ), "-{$new_number}{$lc_ext}", $lc_filename );
    25302549                        }
    25312550
    2532                         $filename = $filename2;
    2533                 } else {
    2534                         while ( file_exists( $dir . "/{$filename}" ) ) {
    2535                                 $new_number = (int) $number + 1;
    2536 
    2537                                 if ( '' === "{$number}{$ext}" ) {
    2538                                         $filename = "{$filename}-{$new_number}";
    2539                                 } else {
    2540                                         $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
    2541                                 }
    2542 
    2543                                 $number = $new_number;
     2551                        if ( '' === "{$number}{$ext}" ) {
     2552                                $filename = "{$filename}-{$new_number}";
     2553                        } else {
     2554                                $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename );
    25442555                        }
     2556
     2557                        $number = $new_number;
     2558                }
     2559
     2560                // Change the extension to lowercase if needed.
     2561                if ( $lc_filename ) {
     2562                        $filename = $lc_filename;
    25452563                }
    25462564
    25472565                // Prevent collisions with existing file names that contain dimension-like strings
    25482566                // (whether they are subsizes or originals uploaded prior to #42437).
    2549                 $upload_dir = wp_get_upload_dir();
     2567
     2568                $files = array();
     2569                $count = 10000;
    25502570
    25512571                // The (resized) image files would have name and extension, and will be in the uploads dir.
    … …  
    25772597
    25782598                        if ( ! empty( $files ) ) {
    2579                                 // The extension case may have changed above.
    2580                                 $new_ext = ! empty( $ext2 ) ? $ext2 : $ext;
     2599                                $count = count( $files );
    25812600
    25822601                                // Ensure this never goes into infinite loop
    25832602                                // as it uses pathinfo() and regex in the check, but string replacement for the changes.
    2584                                 $count = count( $files );
    2585                                 $i     = 0;
     2603                                $i = 0;
    25862604
    25872605                                while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) {
    25882606                                        $new_number = (int) $number + 1;
    2589                                         $filename   = str_replace( array( "-{$number}{$new_ext}", "{$number}{$new_ext}" ), "-{$new_number}{$new_ext}", $filename );
    2590                                         $number     = $new_number;
     2607
     2608                                        // If $ext is uppercase it was replaced with the lowercase version after the previous loop.
     2609                                        $filename = str_replace( array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ), "-{$new_number}{$lc_ext}", $filename );
     2610
     2611                                        $number = $new_number;
    25912612                                        $i++;
    25922613                                }
    25932614                        }
    25942615                }
     2616
     2617                // Check if an image will be converted after uploading or some existing images sub-sizes file names may conflict
     2618                // when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes.
     2619                if ( $is_image ) {
     2620                        $output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type );
     2621                        $alt_types      = array();
     2622
     2623                        if ( ! empty( $output_formats[ $mime_type ] ) ) {
     2624                                // The image will be converted to this format/mime type.
     2625                                $alt_mime_type = $output_formats[ $mime_type ];
     2626
     2627                                // Other types of images whose names may conflict if their sub-sizes are regenerated.
     2628                                $alt_types = array_keys( array_intersect( $output_formats, array( $mime_type, $alt_mime_type ) ) );
     2629                                $alt_types[] = $alt_mime_type;
     2630                        } elseif ( ! empty( $output_formats ) ) {
     2631                                $alt_types = array_keys( array_intersect( $output_formats, array( $mime_type ) ) );
     2632                        }
     2633
     2634                        // Remove duplicates and the original mime type. It will be added later if needed.
     2635                        $alt_types = array_unique( array_diff( $alt_types, array( $mime_type ) ) );
     2636
     2637                        foreach ( $alt_types as $alt_type ) {
     2638                                $alt_ext = wp_get_default_extension_for_mime_type( $alt_type );
     2639
     2640                                if ( ! $alt_ext ) {
     2641                                        continue;
     2642                                }
     2643
     2644                                $alt_ext      = ".{$alt_ext}";
     2645                                $alt_filename = preg_replace( '|' . preg_quote( $lc_ext ) . '$|', $alt_ext, $filename );
     2646
     2647                                $alt_filenames[ $alt_ext ] = $alt_filename;
     2648                        }
     2649
     2650                        if ( ! empty( $alt_filenames ) ) {
     2651                                // Add the original filename. It needs to be checked again together with the alternate filenames
     2652                                // when $number is incremented.
     2653                                $alt_filenames[ $lc_ext ] = $filename;
     2654
     2655                                // Ensure no infinite loop.
     2656                                $i = 0;
     2657
     2658                                while ( $i <= $count && _wp_check_alternate_file_names( $alt_filenames, $_dir, $files ) ) {
     2659                                        $new_number = (int) $number + 1;
     2660
     2661                                        foreach ( $alt_filenames as $alt_ext => $alt_filename ) {
     2662                                                $alt_filenames[ $alt_ext ] = str_replace( array( "-{$number}{$alt_ext}", "{$number}{$alt_ext}" ), "-{$new_number}{$alt_ext}", $alt_filename );
     2663                                        }
     2664
     2665                                        // Also update the $number in (the output) $filename.
     2666                                        // If the extension was uppercase it was already replaced with the lowercase version.
     2667                                        $filename = str_replace( array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ), "-{$new_number}{$lc_ext}", $filename );
     2668
     2669                                        $number = $new_number;
     2670                                        $i++;
     2671                                }
     2672                        }
     2673                }
    25952674        }
    25962675
    … …  
    25992678         *
    26002679         * @since 4.5.0
     2680         * @since 5.8.1 The `$alt_filenames` and `$number` parameters were added.
    26012681         *
    26022682         * @param string        $filename                 Unique file name.
    … …  
    26042684         * @param string        $dir                      Directory path.
    26052685         * @param callable|null $unique_filename_callback Callback function that generates the unique file name.
     2686         * @param string[]      $alt_filenames            Array of alternate file names that were checked for collisions.
     2687         * @param int|string    $number                   The highest number that was used to make the file name unique
     2688         *                                                or an empty string if unused.
    26062689         */
    2607         return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback );
     2690        return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number );
     2691}
     2692
     2693/**
     2694 * Helper function to test if each of an array of file names could conflict with existing files.
     2695 *
     2696 * @since 5.8.1
     2697 * @access private
     2698 *
     2699 * @param string[] $filenames Array of file names to check.
     2700 * @param string   $dir       The directory containing the files.
     2701 * @param array    $files     An array of existing files in the directory. May be empty.
     2702 * @return bool True if the tested file name could match an existing file, false otherwise.
     2703 */
     2704function _wp_check_alternate_file_names( $filenames, $dir, $files ) {
     2705        foreach ( $filenames as $filename ) {
     2706                if ( file_exists( $dir . $filename ) ) {
     2707                        return true;
     2708                }
     2709
     2710                if ( ! empty( $files ) && _wp_check_existing_file_names( $filename, $files ) ) {
     2711                        return true;
     2712                }
     2713        }
     2714
     2715        return false;
    26082716}
    26092717
    … …  
    27892897                }
    27902898        }
     2899}
     2900
     2901/**
     2902 * Returns first matched extension for the mime-type,
     2903 * as mapped from wp_get_mime_types().
     2904 *
     2905 * @since 5.8.1
     2906 *
     2907 * @param string $mime_type
     2908 *
     2909 * @return string|false
     2910 */
     2911function wp_get_default_extension_for_mime_type( $mime_type ) {
     2912        $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) );
     2913
     2914        if ( empty( $extensions[0] ) ) {
     2915                return false;
     2916        }
     2917
     2918        return $extensions[0];
    27912919}
    27922920
Note: See TracChangeset for help on using the changeset viewer.