Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion src/wp-includes/pluggable.php
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,8 @@ function wp_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) {
header( "X-Redirect-By: $x_redirect_by" );
}

wp_add_preload_header( $location );

header( "Location: $location", true, $status );

return true;
Expand Down Expand Up @@ -1592,6 +1594,81 @@ function _wp_sanitize_utf8_in_redirect( $matches ) {
}
endif;

if ( ! function_exists( 'wp_is_internal_url' ) ) :
/**
* Checks whether a URL is internal to the site.
*
* @since 6.9.0
*
* @param string $url The URL to check.
* @return bool True if the URL is relative or matches the site URL, false otherwise.
*/
function wp_is_internal_url( $url ) {
if ( '/' === substr( $url, 0, 1 ) ) {
return true;
}

if ( ! is_blog_installed() ) {
return false;
}

$site_url = site_url();

return substr( $url, 0, strlen( $site_url ) ) === $site_url;
}
endif;

if ( ! function_exists( 'wp_create_preload_header' ) ) :
/**
* Creates a preload Link header string for the given URL.
*
* @since 6.9.0
*
* @param string $url The URL to preload.
* @param string $type Optional. The preload type. Default 'document'.
* @return string The Link header string, or an empty string if not applicable.
*/
function wp_create_preload_header( $url, $type = 'document' ) {
if ( ! wp_is_internal_url( $url ) ) {
return '';
}

$parts = parse_url( $url );
$path = isset( $parts['path'] ) ? $parts['path'] : '';

if ( '/' !== substr( $path, -1 ) &&
'.php' !== substr( $path, -4 ) &&
'.html' !== substr( $path, -5 ) ) {
return '';
}

$link = $path;
if ( isset( $parts['query'] ) ) {
$link .= '?' . $parts['query'];
}

return "Link: <{$link}>; rel=preload; as={$type}";
}
endif;

if ( ! function_exists( 'wp_add_preload_header' ) ) :
/**
* Sends a preload Link header for the given URL if it is an internal URL.
*
* @since 6.9.0
*
* @param string $url The URL to preload.
* @param string $type Optional. The preload type. Default 'document'.
*/
function wp_add_preload_header( $url, $type = 'document' ) {
$header = wp_create_preload_header( $url, $type );

if ( $header ) {
header( $header, false );
}
}
endif;

if ( ! function_exists( 'wp_safe_redirect' ) ) :
/**
* Performs a safe (local) redirect, using wp_redirect().
Expand Down Expand Up @@ -2372,7 +2449,7 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' )

$switched_locale = switch_to_user_locale( $user_id );

$message = __( 'To set your password, visit the following address:' ) . "\r\n\r\n";
$message = __( 'To set your password, visit the following address:' ) . "\r\n\r\n";

/*
* Since some user login names end in a period, this could produce ambiguous URLs that
Expand Down
48 changes: 48 additions & 0 deletions tests/phpunit/tests/formatting/redirect.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,52 @@ public function data_wp_validate_redirect_relative_url() {
),
);
}

/**
* @ticket 39695
*
* @dataProvider data_wp_create_preload_header
*
* @covers ::wp_create_preload_header
*
* @param string $url The URL to preload.
* @param string $expected Expected header string.
*/
public function test_wp_create_preload_header( $url, $expected ) {
$this->assertSame( $expected, wp_create_preload_header( $url ) );
}

/**
* Data provider for test_wp_create_preload_header().
*
* @return array[]
*/
public function data_wp_create_preload_header() {
return array(
'relative path ending in slash' => array(
'/wp-admin/',
'Link: </wp-admin/>; rel=preload; as=document',
),
'relative path ending in .php' => array(
'/wp-login.php',
'Link: </wp-login.php>; rel=preload; as=document',
),
'relative path with query string' => array(
'/wp-login.php?redirect_to=%2Fwp-admin%2F&reauth=1',
'Link: </wp-login.php?redirect_to=%2Fwp-admin%2F&reauth=1>; rel=preload; as=document',
),
'same-site absolute URL' => array(
site_url( '/wp-login.php' ),
'Link: </wp-login.php>; rel=preload; as=document',
),
'external URL returns empty string' => array(
'https://external.example.com/wp-login.php',
'',
),
'non-document extension returns empty string' => array(
'/wp-content/uploads/file.zip',
'',
),
);
}
}
9 changes: 9 additions & 0 deletions tests/phpunit/tests/pluggable/signatures.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ public function get_pluggable_function_signatures() {
),
'wp_sanitize_redirect' => array( 'location' ),
'_wp_sanitize_utf8_in_redirect' => array( 'matches' ),
'wp_is_internal_url' => array( 'url' ),
'wp_create_preload_header' => array(
'url',
'type' => 'document',
),
'wp_add_preload_header' => array(
'url',
'type' => 'document',
),
'wp_safe_redirect' => array(
'location',
'status' => 302,
Expand Down
Loading