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
25 changes: 25 additions & 0 deletions src/wp-includes/canonical.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,31 @@
* @since 2.3.0
*/

/**
* Redirects HTTP requests to HTTPS when SSL is forced for the site.
*
* @since x.x.x
* @access private
*/
function _wp_force_ssl_redirect() {
if ( ! force_ssl() || is_ssl() || ! isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
return;
}

if ( isset( $_SERVER['REQUEST_METHOD'] ) && ! in_array( strtoupper( $_SERVER['REQUEST_METHOD'] ), array( 'GET', 'HEAD' ), true ) ) {
return;
}

if ( str_starts_with( $_SERVER['REQUEST_URI'], 'http' ) ) {
$redirect = set_url_scheme( wp_unslash( $_SERVER['REQUEST_URI'] ), 'https' );
} else {
$redirect = 'https://' . wp_unslash( $_SERVER['HTTP_HOST'] ) . wp_unslash( $_SERVER['REQUEST_URI'] );
}

wp_safe_redirect( $redirect );
exit;
}

/**
* Redirects incoming links to the proper URL based on the site url.
*
Expand Down
6 changes: 4 additions & 2 deletions src/wp-includes/class-wp-recovery-mode-cookie-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ public function set_cookie() {

$expire = time() + $length;

setcookie( RECOVERY_MODE_COOKIE, $value, $expire, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
$secure = is_ssl() || force_ssl();

setcookie( RECOVERY_MODE_COOKIE, $value, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure, true );

if ( COOKIEPATH !== SITECOOKIEPATH ) {
setcookie( RECOVERY_MODE_COOKIE, $value, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
setcookie( RECOVERY_MODE_COOKIE, $value, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true );
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/wp-includes/default-constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,17 +347,25 @@ function wp_cookie_constants() {
* @since 3.0.0
*/
function wp_ssl_constants() {
/**
* @since x.x.x
*/
if ( ! defined( 'FORCE_SSL' ) ) {
define( 'FORCE_SSL', false );
}
force_ssl( FORCE_SSL );

/**
* @since 2.6.0
*/
if ( ! defined( 'FORCE_SSL_ADMIN' ) ) {
if ( 'https' === parse_url( get_option( 'siteurl' ), PHP_URL_SCHEME ) ) {
if ( force_ssl() || 'https' === parse_url( get_option( 'siteurl' ), PHP_URL_SCHEME ) ) {
define( 'FORCE_SSL_ADMIN', true );
} else {
define( 'FORCE_SSL_ADMIN', false );
}
}
force_ssl_admin( FORCE_SSL_ADMIN );
force_ssl_admin( force_ssl() || FORCE_SSL_ADMIN );

/**
* @since 2.6.0
Expand Down
3 changes: 3 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,9 @@
add_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 );

add_action( 'wp_default_styles', 'wp_default_styles' );
add_filter( 'script_loader_src', '_wp_force_ssl_url' );
add_filter( 'style_loader_src', 'wp_style_loader_src', 10, 2 );
add_filter( 'style_loader_src', '_wp_force_ssl_url', 11 );

add_action( 'wp_head', 'wp_enqueue_img_auto_sizes_contain_css_fix', 0 ); // Must run before wp_print_auto_sizes_contain_css_fix().
add_action( 'wp_head', 'wp_print_auto_sizes_contain_css_fix', 1 ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_img_auto_sizes_contain_css_fix().
Expand Down Expand Up @@ -687,6 +689,7 @@
add_action( 'change_locale', 'create_initial_taxonomies' );

// Canonical.
add_action( 'template_redirect', '_wp_force_ssl_redirect', 0 );
add_action( 'template_redirect', 'redirect_canonical' );
add_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 );

Expand Down
20 changes: 20 additions & 0 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6360,6 +6360,26 @@ function force_ssl_admin( $force = null ) {
return $forced;
}

/**
* Determines whether to force SSL for the entire site.
*
* @since x.x.x
*
* @param string|bool|null $force Optional. Whether to force SSL. Default null.
* @return bool True if forced, false if not forced.
*/
function force_ssl( $force = null ) {
static $forced = false;

if ( ! is_null( $force ) ) {
$old_forced = $forced;
$forced = (bool) $force;
return $old_forced;
}

return $forced;
}

/**
* Guesses the URL for the site.
*
Expand Down
4 changes: 2 additions & 2 deletions src/wp-includes/https-migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
* @return bool True if insecure URLs should replaced, false otherwise.
*/
function wp_should_replace_insecure_home_url() {
$should_replace_insecure_home_url = wp_is_using_https()
&& get_option( 'https_migration_required' )
$should_replace_insecure_home_url = ( force_ssl() || wp_is_using_https() )
&& ( force_ssl() || get_option( 'https_migration_required' ) )
// For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using
// the same domain.
&& wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST );
Expand Down
41 changes: 36 additions & 5 deletions src/wp-includes/link-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -3469,7 +3469,7 @@ function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
}

if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
if ( is_ssl() ) {
if ( is_ssl() || force_ssl() ) {
$scheme = 'https';
} else {
$scheme = parse_url( $url, PHP_URL_SCHEME );
Expand Down Expand Up @@ -3790,7 +3790,7 @@ function network_home_url( $path = '', $scheme = null ) {
$orig_scheme = $scheme;

if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ), true ) ) {
$scheme = is_ssl() ? 'https' : 'http';
$scheme = is_ssl() || force_ssl() ? 'https' : 'http';
}

if ( 'relative' === $scheme ) {
Expand Down Expand Up @@ -3931,11 +3931,11 @@ function set_url_scheme( $url, $scheme = null ) {
$orig_scheme = $scheme;

if ( ! $scheme ) {
$scheme = is_ssl() ? 'https' : 'http';
$scheme = is_ssl() || force_ssl() ? 'https' : 'http';
} elseif ( 'admin' === $scheme || 'login' === $scheme || 'login_post' === $scheme || 'rpc' === $scheme ) {
$scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
$scheme = is_ssl() || force_ssl() || force_ssl_admin() ? 'https' : 'http';
} elseif ( 'http' !== $scheme && 'https' !== $scheme && 'relative' !== $scheme ) {
$scheme = is_ssl() ? 'https' : 'http';
$scheme = is_ssl() || force_ssl() ? 'https' : 'http';
}

$url = trim( $url );
Expand Down Expand Up @@ -3965,6 +3965,37 @@ function set_url_scheme( $url, $scheme = null ) {
return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme );
}

/**
* Converts a local URL to HTTPS when SSL is forced.
*
* @since x.x.x
* @access private
*
* @param string $url URL to check.
* @return string HTTPS URL when local SSL is forced, otherwise the original URL.
*/
function _wp_force_ssl_url( $url ) {
if ( ! force_ssl() || ! is_string( $url ) || ! preg_match( '#^(https?:)?//#i', $url ) ) {
return $url;
}

$host = wp_parse_url( $url, PHP_URL_HOST );
if ( ! $host ) {
return $url;
}

$host = strtolower( $host );
$site_hosts = array_filter(
array(
wp_parse_url( home_url( '', 'http' ), PHP_URL_HOST ),
wp_parse_url( site_url( '', 'http' ), PHP_URL_HOST ),
)
);
$site_hosts = array_map( 'strtolower', $site_hosts );

return in_array( $host, $site_hosts, true ) ? set_url_scheme( $url, 'https' ) : $url;
}

/**
* Retrieves the URL to the user's dashboard.
*
Expand Down
6 changes: 3 additions & 3 deletions src/wp-includes/pluggable.php
Original file line number Diff line number Diff line change
Expand Up @@ -1093,11 +1093,11 @@ function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token =
}

if ( '' === $secure ) {
$secure = is_ssl();
$secure = is_ssl() || force_ssl();
}

// Front-end cookie is secure when the auth cookie is secure and the site's home URL uses HTTPS.
$secure_logged_in_cookie = $secure && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME );
$secure_logged_in_cookie = $secure && ( force_ssl() || 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME ) );

/**
* Filters whether the auth cookie should only be sent over HTTPS.
Expand Down Expand Up @@ -1278,7 +1278,7 @@ function is_user_logged_in() {
* @since 1.5.0
*/
function auth_redirect() {
$secure = ( is_ssl() || force_ssl_admin() );
$secure = ( is_ssl() || force_ssl() || force_ssl_admin() );

/**
* Filters whether to use a secure authentication redirect.
Expand Down
8 changes: 5 additions & 3 deletions src/wp-login.php
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,8 @@ function wp_login_viewport_meta() {
$secure = false;
}

$secure = $secure || force_ssl();

setcookie( 'wp-postpass_' . COOKIEHASH, $hasher->HashPassword( wp_unslash( $_POST['post_password'] ) ), $expire, COOKIEPATH, COOKIE_DOMAIN, $secure );

wp_safe_redirect( $redirect_to );
Expand Down Expand Up @@ -942,7 +944,7 @@ function wp_login_viewport_meta() {

if ( isset( $_GET['key'] ) && isset( $_GET['login'] ) ) {
$value = sprintf( '%s:%s', wp_unslash( $_GET['login'] ), wp_unslash( $_GET['key'] ) );
setcookie( $rp_cookie, $value, 0, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
setcookie( $rp_cookie, $value, 0, $rp_path, COOKIE_DOMAIN, is_ssl() || force_ssl(), true );

wp_safe_redirect( remove_query_arg( array( 'key', 'login' ) ) );
exit;
Expand All @@ -961,7 +963,7 @@ function wp_login_viewport_meta() {
}

if ( ! $user || is_wp_error( $user ) ) {
setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl() || force_ssl(), true );

if ( $user && $user->get_error_code() === 'expired_key' ) {
wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=expiredkey' ) );
Expand Down Expand Up @@ -1491,7 +1493,7 @@ function wp_login_viewport_meta() {
if ( isset( $_COOKIE[ $rp_cookie ] ) && is_string( $_COOKIE[ $rp_cookie ] ) ) {
$user_login = sanitize_user( strtok( wp_unslash( $_COOKIE[ $rp_cookie ] ), ':' ) );
list( $rp_path ) = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) );
setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl() || force_ssl(), true );
}

login_header( __( 'Log In' ), '', $errors );
Expand Down
25 changes: 23 additions & 2 deletions tests/phpunit/tests/functions/forceSslAdmin.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
<?php
/**
* Test cases for the `force_ssl_admin()` function.
* Test cases for the `force_ssl_admin()` and `force_ssl()` functions.
*
* @since 6.8.0
*
* @group functions
*
* @covers ::force_ssl_admin
* @covers ::force_ssl
*/
class Tests_Functions_ForceSslAdmin extends WP_UnitTestCase {

public function set_up() {
parent::set_up();
// Reset the `$forced` static variable before each test.
// Reset the `$forced` static variables before each test.
force_ssl_admin( false );
force_ssl( false );
}

/**
Expand Down Expand Up @@ -48,4 +50,23 @@ public function data_force_ssl_admin() {
'integer 0' => array( 0, false ),
);
}

/**
* Tests that force_ssl() returns expected values based on various inputs.
*
* @ticket 28521
*
* @dataProvider data_force_ssl_admin
*
* @param mixed $input The input value to test.
* @param bool $expected The expected result for subsequent calls.
*/
public function test_force_ssl( $input, $expected ) {
// The first call always returns the previous value.
$this->assertFalse( force_ssl( $input ), 'First call did not return the expected value' );

// Call again to check subsequent behavior.
$this->assertSame( $expected, force_ssl( $input ), 'Subsequent call did not return the expected value' );
$this->assertSame( $expected, force_ssl(), 'Getter did not return the expected value' );
}
}
10 changes: 10 additions & 0 deletions tests/phpunit/tests/https-migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
*/
class Tests_HTTPS_Migration extends WP_UnitTestCase {

public function tear_down() {
force_ssl( false );
parent::tear_down();
}

/**
* @ticket 51437
*/
Expand All @@ -25,6 +30,11 @@ public function test_wp_should_replace_insecure_home_url() {
update_option( 'https_migration_required', '1' );
$this->assertTrue( wp_should_replace_insecure_home_url() );

// Should return true when SSL is forced, even without the HTTPS migration flag.
update_option( 'https_migration_required', false );
force_ssl( true );
$this->assertTrue( wp_should_replace_insecure_home_url() );

// Should be overridable via filter.
add_filter( 'wp_should_replace_insecure_home_url', '__return_false' );
$this->assertFalse( wp_should_replace_insecure_home_url() );
Expand Down
42 changes: 42 additions & 0 deletions tests/phpunit/tests/url.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,48 @@ public function test_set_url_scheme() {
force_ssl_admin( $forced_admin );
}

/**
* @ticket 28521
*
* @covers ::set_url_scheme
* @covers ::get_home_url
* @covers ::_wp_force_ssl_url
*/
public function test_force_ssl_sets_local_url_schemes_to_https() {
$forced_ssl = force_ssl();
$forced_admin = force_ssl_admin();
$server_https = $_SERVER['HTTPS'] ?? null;
$_SERVER['HTTPS'] = 'off';
force_ssl_admin( false );
force_ssl( true );

try {
$http_home = home_url( '/foo/', 'http' );
$https_home = home_url( '/foo/', 'https' );

$this->assertSame( $https_home, set_url_scheme( $http_home ) );
$this->assertSame( $https_home, set_url_scheme( $http_home, 'admin' ) );
$this->assertSame( $https_home, home_url( '/foo/' ) );
$this->assertSame( $https_home, network_home_url( '/foo/' ) );
$this->assertSame( $http_home, home_url( '/foo/', 'http' ) );
$this->assertSame( $https_home, _wp_force_ssl_url( $http_home ) );
$this->assertSame( 'https://example.org/foo/', _wp_force_ssl_url( '//example.org/foo/' ) );
$this->assertSame( 'http://example.com/foo/', _wp_force_ssl_url( 'http://example.com/foo/' ) );
$this->assertSame( '/foo/', _wp_force_ssl_url( '/foo/' ) );

force_ssl( false );
$this->assertSame( $http_home, _wp_force_ssl_url( $http_home ) );
} finally {
force_ssl( $forced_ssl );
force_ssl_admin( $forced_admin );
if ( null === $server_https ) {
unset( $_SERVER['HTTPS'] );
} else {
$_SERVER['HTTPS'] = $server_https;
}
}
}

/**
* @covers ::get_adjacent_post
*/
Expand Down
Loading