From 67f87024772added12682ef123368932125e0788 Mon Sep 17 00:00:00 2001 From: Andrew Ozz Date: Tue, 24 Aug 2021 20:50:21 +0000 Subject: [PATCH] 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. Fixes #53668. git-svn-id: https://develop.svn.wordpress.org/trunk@51653 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-image-editor.php | 6 +- src/wp-includes/functions.php | 192 +++++++++++++++--- .../data/images/test-image-1-100x100.jpg | Bin 0 -> 2028 bytes tests/phpunit/data/images/test-image-2.gif | Bin 0 -> 524 bytes tests/phpunit/data/images/test-image-3.jpg | Bin 0 -> 2028 bytes tests/phpunit/data/images/test-image-4.png | Bin 0 -> 1011 bytes tests/phpunit/tests/functions.php | 110 +++++++++- 7 files changed, 269 insertions(+), 39 deletions(-) create mode 100644 tests/phpunit/data/images/test-image-1-100x100.jpg create mode 100644 tests/phpunit/data/images/test-image-2.gif create mode 100644 tests/phpunit/data/images/test-image-3.jpg create mode 100644 tests/phpunit/data/images/test-image-4.png diff --git a/src/wp-includes/class-wp-image-editor.php b/src/wp-includes/class-wp-image-editor.php index 11210180b2..0bc7ee6aee 100644 --- a/src/wp-includes/class-wp-image-editor.php +++ b/src/wp-includes/class-wp-image-editor.php @@ -591,13 +591,11 @@ abstract class WP_Image_Editor { * @return string|false */ protected static function get_extension( $mime_type = null ) { - $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); - - if ( empty( $extensions[0] ) ) { + if ( empty( $mime_type ) ) { return false; } - return $extensions[0]; + return wp_get_default_extension_for_mime_type( $mime_type ); } } diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 7b01568ad4..02d74e1b70 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -2488,6 +2488,10 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) $filename = sanitize_file_name( $filename ); $ext2 = null; + // Initialize vars used in the wp_unique_filename filter. + $number = ''; + $alt_filenames = array(); + // Separate the filename into a name and extension. $ext = pathinfo( $filename, PATHINFO_EXTENSION ); $name = pathinfo( $filename, PATHINFO_BASENAME ); @@ -2508,8 +2512,7 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) { $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext ); } else { - $number = ''; - $fname = pathinfo( $filename, PATHINFO_FILENAME ); + $fname = pathinfo( $filename, PATHINFO_FILENAME ); // Always append a number to file names that can potentially match image sub-size file names. if ( $fname && preg_match( '/-(?:\d+x\d+|scaled|rotated)$/', $fname ) ) { @@ -2519,37 +2522,54 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) $filename = str_replace( "{$fname}{$ext}", "{$fname}-{$number}{$ext}", $filename ); } - // Change '.ext' to lower case. - if ( $ext && strtolower( $ext ) != $ext ) { - $ext2 = strtolower( $ext ); - $filename2 = preg_replace( '|' . preg_quote( $ext ) . '$|', $ext2, $filename ); + // Get the mime type. Uploaded files were already checked with wp_check_filetype_and_ext() + // in _wp_handle_upload(). Using wp_check_filetype() would be sufficient here. + $file_type = wp_check_filetype( $filename ); + $mime_type = $file_type['type']; - // Check for both lower and upper case extension or image sub-sizes may be overwritten. - while ( file_exists( $dir . "/{$filename}" ) || file_exists( $dir . "/{$filename2}" ) ) { - $new_number = (int) $number + 1; - $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); - $filename2 = str_replace( array( "-{$number}{$ext2}", "{$number}{$ext2}" ), "-{$new_number}{$ext2}", $filename2 ); - $number = $new_number; + $is_image = ( ! empty( $mime_type ) && 0 === strpos( $mime_type, 'image/' ) ); + $upload_dir = wp_get_upload_dir(); + $lc_filename = null; + + $lc_ext = strtolower( $ext ); + $_dir = trailingslashit( $dir ); + + // If the extension is uppercase add an alternate file name with lowercase extension. Both need to be tested + // for uniqueness as the extension will be changed to lowercase for better compatibility with different filesystems. + // Fixes an inconsistency in WP < 2.9 where uppercase extensions were allowed but image sub-sizes were created with + // lowercase extensions. + if ( $ext && $lc_ext !== $ext ) { + $lc_filename = preg_replace( '|' . preg_quote( $ext ) . '$|', $lc_ext, $filename ); + } + + // Increment the number added to the file name if there are any files in $dir whose names match one of the + // possible name variations. + while ( file_exists( $_dir . $filename ) || ( $lc_filename && file_exists( $_dir . $lc_filename ) ) ) { + $new_number = (int) $number + 1; + + if ( $lc_filename ) { + $lc_filename = str_replace( array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ), "-{$new_number}{$lc_ext}", $lc_filename ); } - $filename = $filename2; - } else { - while ( file_exists( $dir . "/{$filename}" ) ) { - $new_number = (int) $number + 1; - - if ( '' === "{$number}{$ext}" ) { - $filename = "{$filename}-{$new_number}"; - } else { - $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); - } - - $number = $new_number; + if ( '' === "{$number}{$ext}" ) { + $filename = "{$filename}-{$new_number}"; + } else { + $filename = str_replace( array( "-{$number}{$ext}", "{$number}{$ext}" ), "-{$new_number}{$ext}", $filename ); } + + $number = $new_number; + } + + // Change the extension to lowercase if needed. + if ( $lc_filename ) { + $filename = $lc_filename; } // Prevent collisions with existing file names that contain dimension-like strings // (whether they are subsizes or originals uploaded prior to #42437). - $upload_dir = wp_get_upload_dir(); + + $files = array(); + $count = 10000; // The (resized) image files would have name and extension, and will be in the uploads dir. if ( $name && $ext && @is_dir( $dir ) && false !== strpos( $dir, $upload_dir['basedir'] ) ) { @@ -2579,18 +2599,77 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) } if ( ! empty( $files ) ) { - // The extension case may have changed above. - $new_ext = ! empty( $ext2 ) ? $ext2 : $ext; + $count = count( $files ); // Ensure this never goes into infinite loop // as it uses pathinfo() and regex in the check, but string replacement for the changes. - $count = count( $files ); - $i = 0; + $i = 0; while ( $i <= $count && _wp_check_existing_file_names( $filename, $files ) ) { $new_number = (int) $number + 1; - $filename = str_replace( array( "-{$number}{$new_ext}", "{$number}{$new_ext}" ), "-{$new_number}{$new_ext}", $filename ); - $number = $new_number; + + // If $ext is uppercase it was replaced with the lowercase version after the previous loop. + $filename = str_replace( array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ), "-{$new_number}{$lc_ext}", $filename ); + + $number = $new_number; + $i++; + } + } + } + + // Check if an image will be converted after uploading or some existing images sub-sizes file names may conflict + // when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes. + if ( $is_image ) { + $output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type ); + $alt_types = array(); + + if ( ! empty( $output_formats[ $mime_type ] ) ) { + // The image will be converted to this format/mime type. + $alt_mime_type = $output_formats[ $mime_type ]; + + // Other types of images whose names may conflict if their sub-sizes are regenerated. + $alt_types = array_keys( array_intersect( $output_formats, array( $mime_type, $alt_mime_type ) ) ); + $alt_types[] = $alt_mime_type; + } elseif ( ! empty( $output_formats ) ) { + $alt_types = array_keys( array_intersect( $output_formats, array( $mime_type ) ) ); + } + + // Remove duplicates and the original mime type. It will be added later if needed. + $alt_types = array_unique( array_diff( $alt_types, array( $mime_type ) ) ); + + foreach ( $alt_types as $alt_type ) { + $alt_ext = wp_get_default_extension_for_mime_type( $alt_type ); + + if ( ! $alt_ext ) { + continue; + } + + $alt_ext = ".{$alt_ext}"; + $alt_filename = preg_replace( '|' . preg_quote( $lc_ext ) . '$|', $alt_ext, $filename ); + + $alt_filenames[ $alt_ext ] = $alt_filename; + } + + if ( ! empty( $alt_filenames ) ) { + // Add the original filename. It needs to be checked again together with the alternate filenames + // when $number is incremented. + $alt_filenames[ $lc_ext ] = $filename; + + // Ensure no infinite loop. + $i = 0; + + while ( $i <= $count && _wp_check_alternate_file_names( $alt_filenames, $_dir, $files ) ) { + $new_number = (int) $number + 1; + + foreach ( $alt_filenames as $alt_ext => $alt_filename ) { + $alt_filenames[ $alt_ext ] = str_replace( array( "-{$number}{$alt_ext}", "{$number}{$alt_ext}" ), "-{$new_number}{$alt_ext}", $alt_filename ); + } + + // Also update the $number in (the output) $filename. + // If the extension was uppercase it was already replaced with the lowercase version. + $filename = str_replace( array( "-{$number}{$lc_ext}", "{$number}{$lc_ext}" ), "-{$new_number}{$lc_ext}", $filename ); + + $number = $new_number; $i++; } } @@ -2601,13 +2680,42 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) * Filters the result when generating a unique file name. * * @since 4.5.0 + * @since 5.8.1 The `$alt_filenames` and `$number` parameters were added. * * @param string $filename Unique file name. * @param string $ext File extension, eg. ".png". * @param string $dir Directory path. * @param callable|null $unique_filename_callback Callback function that generates the unique file name. + * @param string[] $alt_filenames Array of alternate file names that were checked for collisions. + * @param int|string $number The highest number that was used to make the file name unique + * or an empty string if unused. */ - return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback ); + return apply_filters( 'wp_unique_filename', $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number ); +} + +/** + * Helper function to test if each of an array of file names could conflict with existing files. + * + * @since 5.8.1 + * @access private + * + * @param string[] $filenames Array of file names to check. + * @param string $dir The directory containing the files. + * @param array $files An array of existing files in the directory. May be empty. + * @return bool True if the tested file name could match an existing file, false otherwise. + */ +function _wp_check_alternate_file_names( $filenames, $dir, $files ) { + foreach ( $filenames as $filename ) { + if ( file_exists( $dir . $filename ) ) { + return true; + } + + if ( ! empty( $files ) && _wp_check_existing_file_names( $filename, $files ) ) { + return true; + } + } + + return false; } /** @@ -2793,6 +2901,26 @@ function wp_ext2type( $ext ) { } } +/** + * Returns first matched extension for the mime-type, + * as mapped from wp_get_mime_types(). + * + * @since 5.8.1 + * + * @param string $mime_type + * + * @return string|false + */ +function wp_get_default_extension_for_mime_type( $mime_type ) { + $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types(), true ) ); + + if ( empty( $extensions[0] ) ) { + return false; + } + + return $extensions[0]; +} + /** * Retrieve the file type from the file name. * diff --git a/tests/phpunit/data/images/test-image-1-100x100.jpg b/tests/phpunit/data/images/test-image-1-100x100.jpg new file mode 100644 index 0000000000000000000000000000000000000000..534aac1d6bf494f7e83c96b32cfe29271e8aac5a GIT binary patch literal 2028 zcmbu9dr(t%7RP@#A%s9M1_M#3O~Dt1TBd+Rgp##vMX-pVB9KCg6dQ;vBM%J{Kmmyu zlp-oER04)V-4!WI0zQD41VL?4UdqEok{}O7I7=&|K(rj7AT%0fhQ?sb zjDvu(jPHRN5o6={$yW0fp|Mz}LnQa(CDj&dK5LZNhV{a0J@%*M;wP>w&H zn4Fq^_FVOP?#;Y<0r_pw#03HL(#ZZRmvIOX%FGOHhBa|Ps3RtDq8Y~V6LXubq1f0% zE1cYqTaZ30scw|u)_8=$w)<0hE$!BND%Pn?Xm82>H?Z9Qi);z(53Uh_M?=Qtp^1P7 z^m=Ddg*xUEF*fx4@ZHSXCnJe=DZag3N8(bgM}=qUA?rlawXkoAha?sMXwEsMH-uEO zIg)q|(2ZG^z?e+9Pe5D)UY2EQaM_Za)qdVc_^NaoZ{g@BzVyNHxXkPD*ob}0DKXQH zM{Y#cD8kEXrWG;KDOuExEy8Tqfizw^m0XT3m_NZ=R&|Up`B;xv7(nhZ7K7-woReh? z*_`M{^WN?2IB1oZNF`95ZYjL_aYYA}v8**md4Q`Fb0-RP?ITTW2c^2E-SJ`No2=$& zf92E0yCtz76rnZDrB8)2X{ii@)iwYtZ=RNH(-m>hvRgA<0#vbJ(l9 z>bHJ)hd_T%Z}G4vgJ<3bYov1wZCuZW$Mzvf84U>w_^(fSRuLxtp|=dJouR>78OUY> zP)xmSkk@-@cIqyQnaQ>dUBRp``Zi8z%zuBdcC{Sr<=O3SCqLq z-P&zW|0~5e1<$*7HD1eo)u&v~oXU-u*yeKUTkFKPOCjq7*}iOoIvT;Miq$-%I!nig z4M5J96_YtZsy%m9^#hBHd?CFpCfFl5GBSKB@=<&(f^!$qO zv_g*yyleoHL+vE33sSEMMW}k57{-p13yt*z4BG};5`m-*LrOJYKBHK-#{PsHQD*b% z5*hebQYF4rcJ%yIftDiA!Hq%+YpY#1sO9m^ZJe|8p(~eT6QS1&+V`k%oZfO)lcK-f ztM%8_h~*W0lpkT5-}pqJq?|70gd@?KaHiN+%c|CXTJe`GsxtdzCEft?lfSq&;JYa! zfu$H*iJZxF5Z>&+1Y;@%&Ga3tJMlmlV*q&};$#CrSPzi(Ps-p8om&Rg#kUP$h!@tM zZT-A)^vjHq3j#QGx2Sa>+nm++Vcf*#+3xCWm|qe*F3mWo`SuMK9*R{fhLC zH2G6E#Pz`oo{Dvw*2LA?3`%M5YpGYXA0v61-CAb`;@s7d1I^g5+E*iVc(;T?O0&u# za=Zu9X9-r1>9Z>$XY3ZaMcVipW*IrEEB^qJRLfla0he?2mpFh`2f_hLuEec~hWB+H z=MN7m39^vFG3?bBJI~9<>^L8&0t&RTu%1OB1TDUIBm6`?J7{57PxCa{&SttLD!u+z zmeZ@wB=<=x7BBLvBD9Y>AK_|fS`Wm3JUUZdty+*i;8M`@8=YpPF+M76XEK@1k}wE= z6E=_~Zrn-u)%AL$DYWm+e&4{=+Hz0(gDv~B!s(51EYjSsB|l?-+nytFpvkMnIOFz9 zDR2a3)r>u@!>8CLTUP0fss2j>~b=h(03{cN-c_1$N%Z5La;vo0Y! zI;s8swf|z}Ny>|h(z@NdXGN(=&e+9YI4(#NEFQ0u&EmBYh?|Na;oG*aMDE|uGyoi8 zykHB3o_sRfzr*W7%V1RWiyJ$|)uT_IwbaV?{w(ejt*pM5Ak>9RHNkCXtY&<^a{_Pd PO?Tnlf9gLS!7%(gr&MST literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/images/test-image-2.gif b/tests/phpunit/data/images/test-image-2.gif new file mode 100644 index 0000000000000000000000000000000000000000..8fad364479d96df7f659438ba45ef2dcc76d12ae GIT binary patch literal 524 zcmZ?wbhEHbG-5DfxXJ(mTwGitA|f&}GRn%zT3TAh#>O@_HqOq@zP`S}!ND;xF^P$Z z85tQRB_-9>)y>V#ot>Q%Cr+F_d-jqgOIEE~wPnkeojZ3PIdbI8nKPF!U%q$m-s8uQ zU%h(u>C>lw|Nen(pdq37lZBIkL4iRBWCzGk3~c`o^cQ&ONcEpsQgkxMYW{^4C0=Wz z*56q3ZP&@1)m)9wwVyID2p3%`)LSuM?x18rrMY@B!=ZB~PZ*_YJS7;ID|wncxEO>u ziy37*UDFul#480Rx$rY|$g>nP&vGt`lNYKKSuDc9?kvYx%(l30l{0Up#3V@uCReFS zu1VYs{BG=((oM4IGH#-kd`$ujoE~lIQ)}A9JgyWAm0e)*Oe=npe}YT&&D(c%#dlK< z)PMb2ox;Pw#`E{z|G!Vp7%pAvtkdLaaW?XU>Y;sV&z`wlO49HZPAcw6ZCtjNbD`zF zO+P+9S>hnq9j3=P;rWq=GUnkE7$rWp+?&G{_F>M#mnx4WHJNM{7W^o4Yc%Tk^-sp( jhD+*R2A}STj@0#MSFDcMoOSoq*45Y7CkV4LGFSru!Q!!l literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/images/test-image-3.jpg b/tests/phpunit/data/images/test-image-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..534aac1d6bf494f7e83c96b32cfe29271e8aac5a GIT binary patch literal 2028 zcmbu9dr(t%7RP@#A%s9M1_M#3O~Dt1TBd+Rgp##vMX-pVB9KCg6dQ;vBM%J{Kmmyu zlp-oER04)V-4!WI0zQD41VL?4UdqEok{}O7I7=&|K(rj7AT%0fhQ?sb zjDvu(jPHRN5o6={$yW0fp|Mz}LnQa(CDj&dK5LZNhV{a0J@%*M;wP>w&H zn4Fq^_FVOP?#;Y<0r_pw#03HL(#ZZRmvIOX%FGOHhBa|Ps3RtDq8Y~V6LXubq1f0% zE1cYqTaZ30scw|u)_8=$w)<0hE$!BND%Pn?Xm82>H?Z9Qi);z(53Uh_M?=Qtp^1P7 z^m=Ddg*xUEF*fx4@ZHSXCnJe=DZag3N8(bgM}=qUA?rlawXkoAha?sMXwEsMH-uEO zIg)q|(2ZG^z?e+9Pe5D)UY2EQaM_Za)qdVc_^NaoZ{g@BzVyNHxXkPD*ob}0DKXQH zM{Y#cD8kEXrWG;KDOuExEy8Tqfizw^m0XT3m_NZ=R&|Up`B;xv7(nhZ7K7-woReh? z*_`M{^WN?2IB1oZNF`95ZYjL_aYYA}v8**md4Q`Fb0-RP?ITTW2c^2E-SJ`No2=$& zf92E0yCtz76rnZDrB8)2X{ii@)iwYtZ=RNH(-m>hvRgA<0#vbJ(l9 z>bHJ)hd_T%Z}G4vgJ<3bYov1wZCuZW$Mzvf84U>w_^(fSRuLxtp|=dJouR>78OUY> zP)xmSkk@-@cIqyQnaQ>dUBRp``Zi8z%zuBdcC{Sr<=O3SCqLq z-P&zW|0~5e1<$*7HD1eo)u&v~oXU-u*yeKUTkFKPOCjq7*}iOoIvT;Miq$-%I!nig z4M5J96_YtZsy%m9^#hBHd?CFpCfFl5GBSKB@=<&(f^!$qO zv_g*yyleoHL+vE33sSEMMW}k57{-p13yt*z4BG};5`m-*LrOJYKBHK-#{PsHQD*b% z5*hebQYF4rcJ%yIftDiA!Hq%+YpY#1sO9m^ZJe|8p(~eT6QS1&+V`k%oZfO)lcK-f ztM%8_h~*W0lpkT5-}pqJq?|70gd@?KaHiN+%c|CXTJe`GsxtdzCEft?lfSq&;JYa! zfu$H*iJZxF5Z>&+1Y;@%&Ga3tJMlmlV*q&};$#CrSPzi(Ps-p8om&Rg#kUP$h!@tM zZT-A)^vjHq3j#QGx2Sa>+nm++Vcf*#+3xCWm|qe*F3mWo`SuMK9*R{fhLC zH2G6E#Pz`oo{Dvw*2LA?3`%M5YpGYXA0v61-CAb`;@s7d1I^g5+E*iVc(;T?O0&u# za=Zu9X9-r1>9Z>$XY3ZaMcVipW*IrEEB^qJRLfla0he?2mpFh`2f_hLuEec~hWB+H z=MN7m39^vFG3?bBJI~9<>^L8&0t&RTu%1OB1TDUIBm6`?J7{57PxCa{&SttLD!u+z zmeZ@wB=<=x7BBLvBD9Y>AK_|fS`Wm3JUUZdty+*i;8M`@8=YpPF+M76XEK@1k}wE= z6E=_~Zrn-u)%AL$DYWm+e&4{=+Hz0(gDv~B!s(51EYjSsB|l?-+nytFpvkMnIOFz9 zDR2a3)r>u@!>8CLTUP0fss2j>~b=h(03{cN-c_1$N%Z5La;vo0Y! zI;s8swf|z}Ny>|h(z@NdXGN(=&e+9YI4(#NEFQ0u&EmBYh?|Na;oG*aMDE|uGyoi8 zykHB3o_sRfzr*W7%V1RWiyJ$|)uT_IwbaV?{w(ejt*pM5Ak>9RHNkCXtY&<^a{_Pd PO?Tnlf9gLS!7%(gr&MST literal 0 HcmV?d00001 diff --git a/tests/phpunit/data/images/test-image-4.png b/tests/phpunit/data/images/test-image-4.png new file mode 100644 index 0000000000000000000000000000000000000000..642ce92975eae50f9cf63bbf2d863edf3381b8bb GIT binary patch literal 1011 zcmV|W}bKT&fD$s&i!$2@ANiL-Nilk zob!A?_c_x!-LU`w{RiUybpfnY0LeEMK=Mrm5VeZMVsw0bY_<&%Ojc)SC!9_vlF6j0 zKBj_*q6n+i3L?Vd;-Z;WFm-7yEG$quohBlpU@%B9^AKUy8%idVa5|kJqU^P^vtyQ> zd5Ns9uF~`Kv#f1)cGfIA^AhoTy|Ofj2o{S4S65f2_?eQ(!NCCufEC5h2 z7&ODuoJ4whdO$=7g+lQA{jyXX4hNo|o|t{M@PqB`=wGTxLht7NJ&Wv-rn8}*I*bV zl}e$aq5?#O?d|QH{U;_S6sgS3%^5AjILPniQd3idOeT}Fe>@&XQBjc$q_(ye>2%sq z9X6w9wfXsZA|mp5JhZvFscMYd?IuAGviH~5*R-{@MSj0ue<4QR!uk2RVwvc@+S}Xz zT_V5J@cDey(9oc1Y$Ot)L?V&B-`m?GK@iC6_3A4|?;A)Y5^y*iigT4(jf{*aQt9vS z*ISPMAN$PAjG;5r-Q68*Hk%B@Znxv%;X&7Yx*DhKtu=x0(yqa;~brKP2; z5yx>%Ktxge7Nokm8ZR#|s^?J!5|76*K0XeP<78&t-QBpkxzP=AdwYvOAn@aE-`d)W zqoX5L%Pc2IG#Z7)V)^Uwg6Daho}Q`(h(sbNE-qGmd~v(oIp?$hh5y;}N(GR7QvoF3 hQ~=306+rS$zW`mu!rH;U*1`Y)002ovPDHLkV1kDNassertSame( 'abcdefg.png', wp_unique_filename( $testdir, 'abcdefg.png' ), 'Sanitiy check failed' ); + $this->assertSame( 'abcdefg.png', wp_unique_filename( $testdir, 'abcdefg.png' ), 'Test non-existing file, file name should be unchanged.' ); - // Check number is appended for file already exists. + // Ensure correct images exist. $this->assertFileExists( $testdir . 'test-image.png', 'Test image does not exist' ); - $this->assertSame( 'test-image-1.png', wp_unique_filename( $testdir, 'test-image.png' ), 'Number not appended correctly' ); $this->assertFileDoesNotExist( $testdir . 'test-image-1.png' ); + // Check number is appended if file already exists. + $this->assertSame( 'test-image-1.png', wp_unique_filename( $testdir, 'test-image.png' ), 'File name not unique, number not appended.' ); + + // Check file with uppercase extension. + $this->assertSame( 'test-image-1.png', wp_unique_filename( $testdir, 'test-image.PNG' ), 'File name with uppercase extension not unique, number not appended.' ); + + // Check file name with already added number. + $this->assertSame( 'test-image-2-1.gif', wp_unique_filename( $testdir, 'test-image-2.gif' ), 'File name not unique, number not appended correctly.' ); + // Check special chars. $this->assertSame( 'testtest-image.png', wp_unique_filename( $testdir, 'testtést-imagé.png' ), 'Filename with special chars failed' ); @@ -221,6 +229,88 @@ class Tests_Functions extends WP_UnitTestCase { return $upload_dir; } + /** + * @ticket 53668 + */ + function test_wp_unique_filename_with_additional_image_extension() { + $testdir = DIR_TESTDATA . '/images/'; + + add_filter( 'upload_dir', array( $this, 'upload_dir_patch_basedir' ) ); + + // Set conversions for uploaded images. + add_filter( 'image_editor_output_format', array( $this, 'image_editor_output_format_handler' ) ); + + // Ensure the test images exist. + $this->assertFileExists( $testdir . 'test-image-1-100x100.jpg', 'test-image-1-100x100.jpg does not exist' ); + $this->assertFileExists( $testdir . 'test-image-2.gif', 'test-image-2.gif does not exist' ); + $this->assertFileExists( $testdir . 'test-image-3.jpg', 'test-image-3.jpg does not exist' ); + $this->assertFileExists( $testdir . 'test-image-4.png', 'test-image-4.png does not exist' ); + + // Standard test: file does not exist and there are no possible intersections with other files. + $this->assertSame( + 'abcdef.png', + wp_unique_filename( $testdir, 'abcdef.png' ), + 'The abcdef.png, abcdef.gif, and abcdef.jpg images do not exist. The file name should not be changed.' + ); + + // Actual clash recognized. + $this->assertSame( + 'canola-1.jpg', + wp_unique_filename( $testdir, 'canola.jpg' ), + 'The canola.jpg image exists. The file name should be unique.' + ); + + // Same name with different extension and the image will be converted. + $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.' + ); + + // Same name with different uppercase extension and the image will be converted. + $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.' + ); + + // Actual clash with several images with different extensions. + $this->assertSame( + 'test-image-5.png', + wp_unique_filename( $testdir, 'test-image.png' ), + 'The test-image.png, test-image-1-100x100.jpg, test-image-2.gif, test-image-3.jpg, and test-image-4.png images exist.' . + 'All of them may clash when creating sub-sizes or regenerating thumbnails in the future. The filename should be unique.' + ); + + // Possible clash with regenerated thumbnails in the future. + $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.' + ); + + remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_format_handler' ) ); + remove_filter( 'upload_dir', array( $this, 'upload_dir_patch_basedir' ) ); + } + + /** + * Changes the output format when editing images. When uploading a PNG file + * it will be converted to JPEG, GIF to JPEG, and PICT to BMP + * (if the image editor in PHP supports it). + * + * @param array $formats + * + * @return array + */ + public function image_editor_output_format_handler( $formats ) { + $formats['image/png'] = 'image/jpeg'; + $formats['image/gif'] = 'image/jpeg'; + $formats['image/pct'] = 'image/bmp'; + + return $formats; + } + /** * @dataProvider data_is_not_serialized */ @@ -1946,4 +2036,18 @@ class Tests_Functions extends WP_UnitTestCase { array( 'application/activity+json, application/nojson', true ), ); } + + /** + * @ticket 53668 + */ + public function test_wp_get_default_extension_for_mime_type() { + $this->assertEquals( 'jpg', wp_get_default_extension_for_mime_type( 'image/jpeg' ), 'jpg not returned as default extension for "image/jpeg"' ); + $this->assertNotEquals( 'jpeg', wp_get_default_extension_for_mime_type( 'image/jpeg' ), 'jpeg should not be returned as default extension for "image/jpeg"' ); + $this->assertEquals( 'png', wp_get_default_extension_for_mime_type( 'image/png' ), 'png not returned as default extension for "image/png"' ); + $this->assertFalse( wp_get_default_extension_for_mime_type( 'wibble/wobble' ), 'false not returned for unrecognized mime type' ); + $this->assertFalse( wp_get_default_extension_for_mime_type( '' ), 'false not returned when empty string as mime type supplied' ); + $this->assertFalse( wp_get_default_extension_for_mime_type( ' ' ), 'false not returned when empty string as mime type supplied' ); + $this->assertFalse( wp_get_default_extension_for_mime_type( 123 ), 'false not returned when int as mime type supplied' ); + $this->assertFalse( wp_get_default_extension_for_mime_type( null ), 'false not returned when null as mime type supplied' ); + } }