From 6e9cc65df561edbe5eb1e16ea195d86f42e4478c Mon Sep 17 00:00:00 2001
From: Paul Biron <paul@sparrowhawkcomputing.com>
Date: Tue, 3 Dec 2019 13:36:27 -0700
Subject: [PATCH] Avoid conflicts with dimension-like filenames.
---
src/wp-includes/functions.php | 70 ++++++++++++++++++++++---------
tests/phpunit/tests/functions.php | 12 ++++++
2 files changed, 62 insertions(+), 20 deletions(-)
diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
index 6102da94f2..6446488bb8 100644
a
|
b
|
function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) |
2425 | 2425 | if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) { |
2426 | 2426 | $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext ); |
2427 | 2427 | } else { |
| 2428 | // Always append a number to filenames that can potentially match subsize filenames. |
| 2429 | $ext_esc = preg_quote( $ext ); |
| 2430 | $fname = pathinfo( $filename, PATHINFO_FILENAME ); |
| 2431 | if ( preg_match( "/-\d+x\d+{$ext_esc}\$/", $filename ) ) { |
| 2432 | $number = 1; |
| 2433 | while ( file_exists( "{$dir}/{$fname}-{$number}{$ext}" ) ) { |
| 2434 | $number++; |
| 2435 | } |
| 2436 | |
| 2437 | $filename = "{$fname}-{$number}{$ext}"; |
| 2438 | } |
| 2439 | |
2428 | 2440 | $number = ''; |
2429 | 2441 | |
2430 | 2442 | // Change '.ext' to lower case. |
… |
… |
function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) |
2440 | 2452 | $number = $new_number; |
2441 | 2453 | } |
2442 | 2454 | |
2443 | | /** |
2444 | | * Filters the result when generating a unique file name. |
2445 | | * |
2446 | | * @since 4.5.0 |
2447 | | * |
2448 | | * @param string $filename Unique file name. |
2449 | | * @param string $ext File extension, eg. ".png". |
2450 | | * @param string $dir Directory path. |
2451 | | * @param callable|null $unique_filename_callback Callback function that generates the unique file name. |
2452 | | */ |
2453 | | return apply_filters( 'wp_unique_filename', $filename2, $ext, $dir, $unique_filename_callback ); |
| 2455 | $filename = $filename2; |
2454 | 2456 | } |
2455 | | |
2456 | | while ( file_exists( $dir . "/$filename" ) ) { |
2457 | | $new_number = (int) $number + 1; |
2458 | | if ( '' == "$number$ext" ) { |
2459 | | $filename = "$filename-" . $new_number; |
2460 | | } else { |
2461 | | $filename = str_replace( array( "-$number$ext", "$number$ext" ), '-' . $new_number . $ext, $filename ); |
| 2457 | else { |
| 2458 | while ( file_exists( $dir . "/$filename" ) ) { |
| 2459 | $new_number = (int) $number + 1; |
| 2460 | if ( '' == "$number$ext" ) { |
| 2461 | $filename = "$filename-" . $new_number; |
| 2462 | } else { |
| 2463 | $filename = str_replace( array( "-$number$ext", "$number$ext" ), '-' . $new_number . $ext, $filename ); |
| 2464 | } |
| 2465 | $number = $new_number; |
2462 | 2466 | } |
2463 | | $number = $new_number; |
| 2467 | } |
| 2468 | |
| 2469 | // prevent collisions with existing filenames that contain dimension-like strings (whether they |
| 2470 | // are subsizes or originals uploaded prior to patch #xxx |
| 2471 | // the array_filter() calls are similar to glob() but with the full |
| 2472 | // expressiveness of regular expressions. |
| 2473 | $number = 0; |
| 2474 | // get the fname again, in case $filename has been modified above. |
| 2475 | $fname = pathinfo( $filename, PATHINFO_FILENAME ); |
| 2476 | $regex = "/^{$fname}-\d+x\d+{$ext_esc}\$/"; |
| 2477 | $files = array_filter( scandir( $dir ), function( $file ) { return ! in_array( $file, array( '.', '..' ) ); } ); |
| 2478 | if ( array_filter( $files, function( $file ) use ( $regex ) { return 1 === preg_match( $regex, $file ); } ) ) { |
| 2479 | do { |
| 2480 | $number++; |
| 2481 | $regex = "/^{$fname}-{$number}-\d+x\d+{$ext_esc}\$/"; |
| 2482 | } while ( array_filter( $files, function( $file ) use ( $regex ) { return 1 === preg_match( $regex, $file ); } ) ); |
| 2483 | |
| 2484 | $filename = "{$fname}-{$number}{$ext}"; |
2464 | 2485 | } |
2465 | 2486 | } |
2466 | 2487 | |
2467 | | /** This filter is documented in wp-includes/functions.php */ |
| 2488 | /** |
| 2489 | * Filters the result when generating a unique file name. |
| 2490 | * |
| 2491 | * @since 4.5.0 |
| 2492 | * |
| 2493 | * @param string $filename Unique file name. |
| 2494 | * @param string $ext File extension, eg. ".png". |
| 2495 | * @param string $dir Directory path. |
| 2496 | * @param callable|null $unique_filename_callback Callback function that generates the unique file name. |
| 2497 | */ |
2468 | 2498 | return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback ); |
2469 | 2499 | } |
2470 | 2500 | |
diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
index dd29c779e5..fee99b0ed4 100644
a
|
b
|
class Tests_Functions extends WP_UnitTestCase { |
195 | 195 | $this->assertEquals( 'abcdefg.png', wp_unique_filename( $testdir, 'abcde\\\fg.png' ), 'Tripple slashed not removed' ); |
196 | 196 | } |
197 | 197 | |
| 198 | /** |
| 199 | * @group 42437 |
| 200 | */ |
| 201 | function test_unique_filename_with_dimension_like_filename() { |
| 202 | $testdir = DIR_TESTDATA . '/images/'; |
| 203 | |
| 204 | // test collision with "dimension-like" original filename. |
| 205 | $this->assertEquals( 'one-blue-pixel-100x100-1.png', wp_unique_filename( $testdir, 'one-blue-pixel-100x100.png' ) ); |
| 206 | // test collision with existing sub-size filename. |
| 207 | $this->assertEquals( 'one-blue-pixel-1.png', wp_unique_filename( $testdir, 'one-blue-pixel.png' ) ); |
| 208 | } |
| 209 | |
198 | 210 | function test_is_serialized() { |
199 | 211 | $cases = array( |
200 | 212 | serialize( null ), |