Skip to content

Commit

Permalink
Editor: Prevent font folder naive filtering causing infinite loops.
Browse files Browse the repository at this point in the history
This modifies the font directory API to more closely reflect the upload directory API to help account for naive filtering when uploading fonts.

This moves the protection of infinite loops to the new function `_wp_filter_font_directory()` to allow developers extending and maintaining the font library to apply the filter without the need for a closure.

These changes also ensure both the `upload_dir` and `font_dir` filter are applied consistently when both creating and deleting fonts faces. Prior to this commit the `upload_dir` filter was only fired when creating fonts faces via the REST API.

Applying the font directory filter to the `upload_dir` filter is now done by adding the `_wp_filter_font_directory` function rather than `wp_get_font_dir()`. Developers who have previously modified the font upload directory using the `font_dir` filter will NOT need to upload their code.

Extenders wishing to upload files to the font directory can do so via the code:

{{{#!php
<?php
add_filter( 'upload_dir', '_wp_filter_font_directory' );
// Your code to upload or sideload a font file.
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
}}}

Introduces:

* `wp_font_dir()`: Attempt to create and retrieve the font upload directory. The equivalent to `wp_upload_dir()`.
* `_wp_filter_font_directory()`: To run on the `upload_dir` filter, this sets the default destination of the fonts directory and fires the `font_dir` filter. 

`wp_get_font_dir()` has been modified to be a lightweight getter for the font directory. It returns the location without attempting to create it. The equivalent to `wp_get_upload_dir()`.

Follow up to [57740].

Props peterwilsoncc, mukesh27, mikachan, costdev, mmaattiiaass, swissspidy, youknowriad, dd32, grantmkin.
Fixes #60652.


git-svn-id: https://develop.svn.wordpress.org/trunk@57868 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
peterwilsoncc committed Mar 22, 2024
1 parent 30420b6 commit ca8d78e
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 24 deletions.
80 changes: 74 additions & 6 deletions src/wp-includes/fonts.php
Expand Up @@ -91,13 +91,31 @@ function wp_unregister_font_collection( string $slug ) {
return WP_Font_Library::get_instance()->unregister_font_collection( $slug );
}

/**
* Retrieves font uploads directory information.
*
* Same as wp_font_dir() but "light weight" as it doesn't attempt to create the font uploads directory.
* Intended for use in themes, when only 'basedir' and 'baseurl' are needed, generally in all cases
* when not uploading files.
*
* @since 6.5.0
*
* @see wp_font_dir()
*
* @return array See wp_font_dir() for description.
*/
function wp_get_font_dir() {
return wp_font_dir( false );
}

/**
* Returns an array containing the current fonts upload directory's path and URL.
*
* @since 6.5.0
*
* @return array $defaults {
* Array of information about the upload directory.
* @param bool $create_dir Optional. Whether to check and create the font uploads directory. Default true.
* @return array {
* Array of information about the font upload directory.
*
* @type string $path Base directory and subdirectory or full path to the fonts upload directory.
* @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory.
Expand All @@ -107,13 +125,54 @@ function wp_unregister_font_collection( string $slug ) {
* @type string|false $error False or error message.
* }
*/
function wp_get_font_dir() {
function wp_font_dir( $create_dir = true ) {
/*
* Allow extenders to manipulate the font directory consistently.
*
* Ensures the upload_dir filter is fired both when calling this function
* directly and when the upload directory is filtered in the Font Face
* REST API endpoint.
*/
add_filter( 'upload_dir', '_wp_filter_font_directory' );
$font_dir = wp_upload_dir( null, $create_dir, false );
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
return $font_dir;
}

/**
* Returns the font directory for use by the font library.
*
* This function is a callback for the {@see 'upload_dir'} filter. It is not
* intended to be called directly. Use wp_get_font_dir() instead.
*
* The function can be used when extending the font library to modify the upload
* destination for font files via the upload_dir filter. The recommended way to
* do this is:
*
* ```php
* add_filter( 'upload_dir', '_wp_filter_font_directory' );
* // Your code to upload or sideload a font file.
* remove_filter( 'upload_dir', '_wp_filter_font_directory' );
* ```
*
* @since 6.5.0
* @access private
*
* @param string $font_dir The font directory.
* @return string The modified font directory.
*/
function _wp_filter_font_directory( $font_dir ) {
if ( doing_filter( 'font_dir' ) ) {
// Avoid an infinite loop.
return $font_dir;
}

$site_path = '';
if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) {
$site_path = '/sites/' . get_current_blog_id();
}

$defaults = array(
$font_dir = array(
'path' => path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path,
'url' => untrailingslashit( content_url( 'fonts' ) ) . $site_path,
'subdir' => '',
Expand All @@ -129,9 +188,18 @@ function wp_get_font_dir() {
*
* @since 6.5.0
*
* @param array $defaults The original fonts directory data.
* @param array $font_dir {
* Array of information about the font upload directory.
*
* @type string $path Base directory and subdirectory or full path to the fonts upload directory.
* @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory.
* @type string $subdir Subdirectory
* @type string $basedir Path without subdir.
* @type string $baseurl URL path without subdir.
* @type string|false $error False or error message.
* }
*/
return apply_filters( 'font_dir', $defaults );
return apply_filters( 'font_dir', $font_dir );
}

/**
Expand Down
Expand Up @@ -856,21 +856,8 @@ protected function sanitize_src( $value ) {
*/
protected function handle_font_file_upload( $file ) {
add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );

/*
* Set the upload directory to the fonts directory.
*
* wp_get_font_dir() contains the 'font_dir' hook, whose callbacks are
* likely to call wp_get_upload_dir().
*
* To avoid an infinite loop, don't hook wp_get_font_dir() to 'upload_dir'.
* Instead, just pass its return value to the 'upload_dir' callback.
*/
$font_dir = wp_get_font_dir();
$set_upload_dir = function () use ( $font_dir ) {
return $font_dir;
};
add_filter( 'upload_dir', $set_upload_dir );
// Filter the upload directory to return the fonts directory.
add_filter( 'upload_dir', '_wp_filter_font_directory' );

$overrides = array(
'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ),
Expand All @@ -887,7 +874,7 @@ protected function handle_font_file_upload( $file ) {

$uploaded_file = wp_handle_upload( $file, $overrides );

remove_filter( 'upload_dir', $set_upload_dir );
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );

return $uploaded_file;
Expand Down
4 changes: 2 additions & 2 deletions tests/phpunit/tests/fonts/font-library/fontLibraryHooks.php
Expand Up @@ -73,13 +73,13 @@ protected function upload_font_file( $font_filename ) {
$font_file_path = DIR_TESTDATA . '/fonts/' . $font_filename;

add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );
add_filter( 'upload_dir', 'wp_get_font_dir' );
add_filter( 'upload_dir', '_wp_filter_font_directory' );
$font_file = wp_upload_bits(
$font_filename,
null,
file_get_contents( $font_file_path )
);
remove_filter( 'upload_dir', 'wp_get_font_dir' );
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );

return $font_file;
Expand Down
40 changes: 40 additions & 0 deletions tests/phpunit/tests/fonts/font-library/wpFontsDir.php
Expand Up @@ -69,4 +69,44 @@ function set_new_values( $defaults ) {

$this->assertSame( static::$dir_defaults, $font_dir, 'The wp_get_font_dir() method should return the default values.' );
}

/**
* @ticket 60652
*/
public function test_fonts_dir_filters_do_not_trigger_infinite_loop() {
/*
* Naive filtering of uploads directory to return font directory.
*
* This emulates the approach a plugin developer may take to
* add the filter when extending the font library functionality.
*/
add_filter( 'upload_dir', '_wp_filter_font_directory' );

add_filter(
'upload_dir',
function ( $upload_dir ) {
static $count = 0;
++$count;
// The filter may be applied a couple of times, at five iterations assume an infinite loop.
if ( $count >= 5 ) {
$this->fail( 'Filtering the uploads directory triggered an infinite loop.' );
}
return $upload_dir;
},
5
);

/*
* Filter the font directory to return the uploads directory.
*
* This emulates moving font files back to the uploads directory due
* to file system structure.
*/
add_filter( 'font_dir', 'wp_get_upload_dir' );

wp_get_upload_dir();

// This will never be hit if an infinite loop is triggered.
$this->assertTrue( true );
}
}

0 comments on commit ca8d78e

Please sign in to comment.