diff --git a/includes/admin/class-wc-admin-post-types.php b/includes/admin/class-wc-admin-post-types.php index 21430ad4fcd..89dca248256 100644 --- a/includes/admin/class-wc-admin-post-types.php +++ b/includes/admin/class-wc-admin-post-types.php @@ -54,6 +54,7 @@ class WC_Admin_Post_Types { // Uploads. add_filter( 'upload_dir', array( $this, 'upload_dir' ) ); + add_filter( 'wp_unique_filename', array( $this, 'wc_unique_filename' ), 10, 3 ); add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) ); // Hide template for CPT archive. @@ -818,6 +819,61 @@ class WC_Admin_Post_Types { return $pathdata; } + /** + * Change filename for WooCommerce uploads and prepend unique chars for security. + * + * @param string $full_filename Original filename. + * @param string $ext Extension of file. + * @param string $dir Directory path. + * + * @return string New filename with unique hash. + * @since 3.10.0 + */ + public function wc_unique_filename( $full_filename, $ext, $dir ) { + if ( ! isset( $_POST['type'] ) || ! 'downloadable_product' === $_POST['type'] ) { // WPCS: CSRF ok, input var ok. + return $full_filename; + } + + if ( ! strpos( $dir, 'woocommerce_uploads' ) ) { + return $full_filename; + } + + return $this->unique_filename( $full_filename, $ext ); + } + + /** + * Change filename to append random text. + * + * @param string $full_filename Original filename with extension. + * @param string $ext Extension. + * + * @return string Modified filename. + */ + public function unique_filename( $full_filename, $ext ) { + $ideal_random_char_length = 32; + $max_filename_length = 255; // Max file name length for most file systems. + $length_to_prepend = min( $ideal_random_char_length, $max_filename_length - strlen( $full_filename ) - 1 ); + + if ( 1 > $length_to_prepend ) { + return $full_filename; + } + + $suffix = strtolower( wp_generate_password( $length_to_prepend, false, false ) ); + $filename = $full_filename; + + if ( strlen( $ext ) > 0 ) { + $filename = substr( $filename, 0, strlen( $filename ) - strlen( $ext ) ); + } + + $full_filename = str_replace( + $filename, + "$filename-$suffix", + $full_filename + ); + + return $full_filename; + } + /** * Run a filter when uploading a downloadable product. */ diff --git a/tests/unit-tests/core/post-types-admin.php b/tests/unit-tests/core/post-types-admin.php new file mode 100644 index 00000000000..96ec62dd4f3 --- /dev/null +++ b/tests/unit-tests/core/post-types-admin.php @@ -0,0 +1,50 @@ +wc_cpt = new WC_Admin_Post_Types(); + } + + /** + * Check if filename is extended and extension is preserved. + */ + public function test_unique_filename() { + $full_filename = 'dummy_filename.csv'; + $ext = '.csv'; + + $unique_filename = $this->wc_cpt->unique_filename( $full_filename, $ext ); + $this->assertEquals( strlen( $full_filename ) + 32 + 1, strlen( $unique_filename ) ); + $this->assertEquals( $ext, substr( $unique_filename, -4 ) ); + } + + /** + * Check if filename is extended properly when its very long. + */ + public function test_unique_filename_for_large_name() { + $full_filename = str_repeat( 'w', 250 ) . '.csv'; + $ext = '.csv'; + $unique_filename = $this->wc_cpt->unique_filename( $full_filename, $ext ); + $this->assertEquals( 254, strlen( $unique_filename ) ); + $this->assertEquals( $ext, substr( $unique_filename, - 4 ) ); + } +}