Skip to content
25 changes: 20 additions & 5 deletions src/wp-includes/canonical.php
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,10 @@ function redirect_canonical( $requested_url = null, $do_redirect = true ) {
// www.example.com vs. example.com
$user_home = parse_url( home_url() );

if ( ! empty( $user_home['scheme'] ) ) {
$redirect['scheme'] = $user_home['scheme'];
}

if ( ! empty( $user_home['host'] ) ) {
$redirect['host'] = $user_home['host'];
}
Expand Down Expand Up @@ -717,13 +721,24 @@ function redirect_canonical( $requested_url = null, $do_redirect = true ) {
$original_host_low = strtolower( $original['host'] );
$redirect_host_low = strtolower( $redirect['host'] );

$original_port = isset( $original['port'] ) ? strtolower( $original['port'] ) : '80';
$redirect_port = isset( $redirect['port'] ) ? strtolower( $redirect['port'] ) : '80';

/*
* Ignore differences in host capitalization, as this can lead to infinite redirects.
* Only redirect no-www <=> yes-www.
* Preserve original host casing if the hostnames are identical (ignoring case),
* or differ in a way that is not just a www <=> non-www variation,
* and the port is the same.
*
* This prevents unnecessary redirects and avoids redirect loops due to host casing,
* but still allows canonical redirects between www and non-www variants.
*/
if ( $original_host_low === $redirect_host_low
|| ( 'www.' . $original_host_low !== $redirect_host_low
&& 'www.' . $redirect_host_low !== $original_host_low )
if (
$original_host_low === $redirect_host_low
|| (
'www.' . $original_host_low !== $redirect_host_low
&& 'www.' . $redirect_host_low !== $original_host_low
&& $original_port === $redirect_port
)
) {
$redirect['host'] = $original['host'];
}
Expand Down
88 changes: 88 additions & 0 deletions tests/phpunit/tests/canonical.php
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,92 @@ public function data_canonical_attachment_page_redirect_with_option_disabled() {
),
);
}

/**
* Test domain and port redirections.
*
* @ticket 33821
* @dataProvider data_domain_and_port_redirections
*
* @covers ::redirect_canonical
*
* @param string $home_url The home URL to set.
* @param string $site_url The site URL to set.
* @param string $request_url The URL to test redirection for.
* @param string $expected_url The expected redirect URL, or null if no redirect.
* @param string $failure_msg The message to display on test failure.
*/
public function test_domain_and_port_redirections( $home_url, $site_url, $request_url, $expected_url, $failure_msg ) {
update_option( 'home', $home_url );
update_option( 'siteurl', $site_url );

$redirect = redirect_canonical( $request_url, false );

$this->assertSame( $expected_url, $redirect, $failure_msg );
}

/**
* Data provider for test_domain_and_port_redirections().
*
* @return array[] {
* @type string $home_url The home URL to set.
* @type string $site_url The site URL to set.
* @type string $request_url The URL to test redirection for.
* @type string $expected_url The expected redirect URL, or null if no redirect.
* @type string $failure_msg The message to display on test failure.
* }
*/
public function data_domain_and_port_redirections() {
return array(
'non-standard localhost port to canonical domain' => array(
'home_url' => 'http://example.com',
'site_url' => 'http://example.com',
'request_url' => 'http://localhost:10020/',
'expected_url' => 'http://example.com/',
'failure_msg' => 'Failed to redirect non-standard localhost port to canonical domain',
),
'non-standard localhost port to canonical domain with SSL' => array(
'home_url' => 'https://example.com',
'site_url' => 'https://example.com',
'request_url' => 'http://localhost:10020/',
'expected_url' => 'https://example.com/',
'failure_msg' => 'Failed to redirect non-standard localhost port to canonical domain with SSL',
),
'different host casing do not redirect' => array(
'home_url' => 'http://example.com',
'site_url' => 'http://example.com',
'request_url' => 'http://Example.com/',
'expected_url' => null,
'failure_msg' => 'Should not redirect when only host casing differs',
),
'different host casing with port redirect without host change' => array(
'home_url' => 'http://example.com:8080',
'site_url' => 'http://example.com:8080',
'request_url' => 'http://Example.com:10200/',
'expected_url' => 'http://Example.com:8080/',
'failure_msg' => 'Failed to redirect to correct port while preserving host casing',
),
'www to non-www' => array(
'home_url' => 'http://example.com',
'site_url' => 'http://example.com',
'request_url' => 'http://www.example.com/',
'expected_url' => 'http://example.com/',
'failure_msg' => 'Failed to redirect www to non-www domain',
),
'non-www to www' => array(
'home_url' => 'http://www.example.com',
'site_url' => 'http://www.example.com',
'request_url' => 'http://example.com/',
'expected_url' => 'http://www.example.com/',
'failure_msg' => 'Failed to redirect non-www to www domain',
),
'port for same host' => array(
'home_url' => 'http://example.com:8080',
'site_url' => 'http://example.com:8080',
'request_url' => 'http://example.com:10200/',
'expected_url' => 'http://example.com:8080/',
'failure_msg' => 'Failed to redirect to correct port for same host',
),
);
}
}
Loading