<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[49904] trunk: Security, Site Health: Detect HTTPS support and encourage switching.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/49904">49904</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/49904","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-12-23 19:11:20 +0000 (Wed, 23 Dec 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Security, Site Health: Detect HTTPS support and encourage switching.

This changeset modifies the Site Health panel for HTTPS to provide more accurate recommendations based on whether the environment is already set up for HTTPS.

* Introduces `wp_is_using_https()` to check whether the site is configured to use HTTPS (via its Site Address and WordPress Address).
* Introduces `wp_is_https_supported()` to check whether the environment supports HTTPS. This relies on a cron job which periodically checks support using a loopback request.

Props Clorith, flixos90, miinasikk, westonruter.
Fixes <a href="https://core.trac.wordpress.org/ticket/47577">#47577</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminincludesclasswpsitehealthphp">trunk/src/wp-admin/includes/class-wp-site-health.php</a></li>
<li><a href="#trunksrcwpincludesdefaultfiltersphp">trunk/src/wp-includes/default-filters.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludeshttpsdetectionphp">trunk/src/wp-includes/https-detection.php</a></li>
<li><a href="#trunktestsphpunittestshttpsdetectionphp">trunk/tests/phpunit/tests/https-detection.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminincludesclasswpsitehealthphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/class-wp-site-health.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/class-wp-site-health.php      2020-12-23 18:17:51 UTC (rev 49903)
+++ trunk/src/wp-admin/includes/class-wp-site-health.php        2020-12-23 19:11:20 UTC (rev 49904)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1493,12 +1493,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * enabled, but only if you visit the right site address.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.2.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 5.7.0 Updated to rely on {@see wp_is_using_https()} and {@see wp_is_https_supported()}.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array The test results.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_test_https_status() {
</span><span class="cx" style="display: block; padding: 0 10px">                $result = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'label'       => __( 'Your website is using an active HTTPS connection.' ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'label'       => __( 'Your website is using an active HTTPS connection' ),
</ins><span class="cx" style="display: block; padding: 0 10px">                         'status'      => 'good',
</span><span class="cx" style="display: block; padding: 0 10px">                        'badge'       => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'label' => __( 'Security' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1519,15 +1520,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'test'        => 'https_status',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( is_ssl() ) {
-                       $wp_url   = get_bloginfo( 'wpurl' );
-                       $site_url = get_bloginfo( 'url' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! wp_is_using_https() ) {
+                       $result['status'] = 'critical';
+                       $result['label']  = __( 'Your website does not use HTTPS' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( 'https' !== substr( $wp_url, 0, 5 ) || 'https' !== substr( $site_url, 0, 5 ) ) {
-                               $result['status'] = 'recommended';
-
-                               $result['label'] = __( 'Only parts of your site are using HTTPS' );
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( is_ssl() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $result['description'] = sprintf(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '<p>%s</p>',
</span><span class="cx" style="display: block; padding: 0 10px">                                        sprintf(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1536,17 +1533,34 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                                esc_url( admin_url( 'options-general.php' ) )
</span><span class="cx" style="display: block; padding: 0 10px">                                        )
</span><span class="cx" style="display: block; padding: 0 10px">                                );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        } else {
+                               $result['description'] = sprintf(
+                                       '<p>%s</p>',
+                                       sprintf(
+                                               /* translators: %s: URL to General Settings screen. */
+                                               __( 'Your <a href="%s">WordPress Address</a> is not set up to use HTTPS.' ),
+                                               esc_url( admin_url( 'options-general.php' ) )
+                                       )
+                               );
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $result['actions'] .= sprintf(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( wp_is_https_supported() ) {
+                               $result['description'] .= sprintf(
+                                       '<p>%s</p>',
+                                       __( 'HTTPS is already supported for your website.' )
+                               );
+
+                               $result['actions'] = sprintf(
</ins><span class="cx" style="display: block; padding: 0 10px">                                         '<p><a href="%s">%s</a></p>',
</span><span class="cx" style="display: block; padding: 0 10px">                                        esc_url( admin_url( 'options-general.php' ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        __( 'Update your site addresses' )
</span><span class="cx" style="display: block; padding: 0 10px">                                );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        } else {
+                               $result['description'] .= sprintf(
+                                       '<p>%s</p>',
+                                       __( 'Talk to your web host about supporting HTTPS for your website.' )
+                               );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                } else {
-                       $result['status'] = 'recommended';
-
-                       $result['label'] = __( 'Your site does not use HTTPS' );
</del><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return $result;
</span></span></pre></div>
<a id="trunksrcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-filters.php 2020-12-23 18:17:51 UTC (rev 49903)
+++ trunk/src/wp-includes/default-filters.php   2020-12-23 19:11:20 UTC (rev 49904)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -337,6 +337,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">        add_action( 'init', 'wp_cron' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// HTTPS detection.
+add_action( 'init', 'wp_schedule_https_detection' );
+add_action( 'wp_https_detection', 'wp_update_https_detection_errors' );
+add_filter( 'cron_request', 'wp_cron_conditionally_prevent_sslverify', 9999 );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // 2 Actions 2 Furious.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'do_feed_rdf', 'do_feed_rdf', 10, 0 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'do_feed_rss', 'do_feed_rss', 10, 0 );
</span></span></pre></div>
<a id="trunksrcwpincludeshttpsdetectionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/https-detection.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/https-detection.php                         (rev 0)
+++ trunk/src/wp-includes/https-detection.php   2020-12-23 19:11:20 UTC (rev 49904)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,181 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * HTTPS detection functions.
+ *
+ * @package WordPress
+ * @since 5.7.0
+ */
+
+/**
+ * Checks whether the website is using HTTPS.
+ *
+ * This is based on whether the home and site URL are using HTTPS.
+ *
+ * @since 5.7.0
+ *
+ * @return bool True if using HTTPS, false otherwise.
+ */
+function wp_is_using_https() {
+       if ( 'https' !== wp_parse_url( home_url(), PHP_URL_SCHEME ) ) {
+               return false;
+       }
+
+       // Use direct option access for 'siteurl' and manually run the 'site_url'
+       // filter because site_url() will adjust the scheme based on what the
+       // current request is using.
+       /** This filter is documented in wp-includes/link-template.php */
+       $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null );
+
+       if ( 'https' !== wp_parse_url( $site_url, PHP_URL_SCHEME ) ) {
+               return false;
+       }
+
+       return true;
+}
+
+/**
+ * Checks whether HTTPS is supported for the server and domain.
+ *
+ * @since 5.7.0
+ *
+ * @return bool True if HTTPS is supported, false otherwise.
+ */
+function wp_is_https_supported() {
+       $https_detection_errors = get_option( 'https_detection_errors' );
+
+       // If option has never been set by the Cron hook before, run it on-the-fly as fallback.
+       if ( false === $https_detection_errors ) {
+               wp_update_https_detection_errors();
+
+               $https_detection_errors = get_option( 'https_detection_errors' );
+       }
+
+       // If there are no detection errors, HTTPS is supported.
+       return empty( $https_detection_errors );
+}
+
+/**
+ * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors.
+ *
+ * This internal function is called by a regular Cron hook to ensure HTTPS support is detected and maintained.
+ *
+ * @since 5.7.0
+ * @access private
+ */
+function wp_update_https_detection_errors() {
+       $support_errors = new WP_Error();
+
+       $response = wp_remote_request(
+               home_url( '/', 'https' ),
+               array(
+                       'headers'   => array(
+                               'Cache-Control' => 'no-cache',
+                       ),
+                       'sslverify' => true,
+               )
+       );
+
+       if ( is_wp_error( $response ) ) {
+               $unverified_response = wp_remote_request(
+                       home_url( '/', 'https' ),
+                       array(
+                               'headers'   => array(
+                                       'Cache-Control' => 'no-cache',
+                               ),
+                               'sslverify' => false,
+                       )
+               );
+
+               if ( is_wp_error( $unverified_response ) ) {
+                       $support_errors->add(
+                               $unverified_response->get_error_code(),
+                               $unverified_response->get_error_message()
+                       );
+               } else {
+                       $support_errors->add(
+                               'ssl_verification_failed',
+                               $response->get_error_message()
+                       );
+               }
+
+               $response = $unverified_response;
+       }
+
+       if ( ! is_wp_error( $response ) ) {
+               if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+                       $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) );
+               } elseif ( false === wp_is_owned_html_output( wp_remote_retrieve_body( $response ) ) ) {
+                       $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) );
+               }
+       }
+
+       update_option( 'https_detection_errors', $support_errors->errors );
+}
+
+/**
+ * Schedules the Cron hook for detecting HTTPS support.
+ *
+ * @since 5.7.0
+ * @access private
+ */
+function wp_schedule_https_detection() {
+       if ( ! wp_next_scheduled( 'wp_https_detection' ) ) {
+               wp_schedule_event( time(), 'twicedaily', 'wp_https_detection' );
+       }
+}
+
+/**
+ * Disables SSL verification if the 'cron_request' arguments include an HTTPS URL.
+ *
+ * This prevents an issue if HTTPS breaks, where there would be a failed attempt to verify HTTPS.
+ *
+ * @since 5.7.0
+ * @access private
+ *
+ * @param array $request The Cron request arguments.
+ * @return array $request The filtered Cron request arguments.
+ */
+function wp_cron_conditionally_prevent_sslverify( $request ) {
+       if ( 'https' === wp_parse_url( $request['url'], PHP_URL_SCHEME ) ) {
+               $request['args']['sslverify'] = false;
+       }
+       return $request;
+}
+
+/**
+ * Checks whether a given HTML string is likely an output from this WordPress site.
+ *
+ * This function attempts to check for various common WordPress patterns whether they are included in the HTML string.
+ * Since any of these actions may be disabled through third-party code, this function may also return null to indicate
+ * that it was not possible to determine ownership.
+ *
+ * @since 5.7.0
+ * @access private
+ *
+ * @param string $html Full HTML output string, e.g. from a HTTP response.
+ * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine.
+ */
+function wp_is_owned_html_output( $html ) {
+       // 1. Check if HTML includes the site's Really Simple Discovery link.
+       if ( has_action( 'wp_head', 'rsd_link' ) ) {
+               $pattern = esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ); // See rsd_link().
+               return false !== strpos( $html, $pattern );
+       }
+
+       // 2. Check if HTML includes the site's Windows Live Writer manifest link.
+       if ( has_action( 'wp_head', 'wlwmanifest_link' ) ) {
+               // Try both HTTPS and HTTP since the URL depends on context.
+               $pattern = preg_replace( '#^https?:(?=//)#', '', includes_url( 'wlwmanifest.xml' ) ); // See wlwmanifest_link().
+               return false !== strpos( $html, $pattern );
+       }
+
+       // 3. Check if HTML includes the site's REST API link.
+       if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) {
+               // Try both HTTPS and HTTP since the URL depends on context.
+               $pattern = esc_url( preg_replace( '#^https?:(?=//)#', '', get_rest_url() ) ); // See rest_output_link_wp_head().
+               return false !== strpos( $html, $pattern );
+       }
+
+       // Otherwise the result cannot be determined.
+       return null;
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/wp-includes/https-detection.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2020-12-23 18:17:51 UTC (rev 49903)
+++ trunk/src/wp-settings.php   2020-12-23 19:11:20 UTC (rev 49904)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -171,6 +171,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/theme.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-theme.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/template.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require ABSPATH . WPINC . '/https-detection.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-user-request.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/user.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require ABSPATH . WPINC . '/class-wp-user-query.php';
</span></span></pre></div>
<a id="trunktestsphpunittestshttpsdetectionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/https-detection.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/https-detection.php                             (rev 0)
+++ trunk/tests/phpunit/tests/https-detection.php       2020-12-23 19:11:20 UTC (rev 49904)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,313 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * @group https-detection
+ */
+class Tests_HTTPS_Detection extends WP_UnitTestCase {
+
+       private $last_request_url;
+
+       public function setUp() {
+               parent::setUp();
+
+               remove_all_filters( 'option_home' );
+               remove_all_filters( 'option_siteurl' );
+               remove_all_filters( 'home_url' );
+               remove_all_filters( 'site_url' );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_is_using_https() {
+               update_option( 'home', 'http://example.com/' );
+               update_option( 'siteurl', 'http://example.com/' );
+               $this->assertFalse( wp_is_using_https() );
+
+               // Expect false if only one of the two relevant URLs is HTTPS.
+               update_option( 'siteurl', 'https://example.com/' );
+               $this->assertFalse( wp_is_using_https() );
+
+               update_option( 'home', 'https://example.com/' );
+               $this->assertTrue( wp_is_using_https() );
+
+               // Test that the manually included 'site_url' filter works as expected
+               // by using it to set the URL to use HTTP.
+               add_filter( 'site_url', $this->filter_set_url_scheme( 'http' ) );
+               $this->assertFalse( wp_is_using_https() );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_is_https_supported() {
+               // The function works with cached errors, so only test that here.
+               $wp_error = new WP_Error();
+
+               // No errors, so HTTPS is supported.
+               update_option( 'https_detection_errors', $wp_error->errors );
+               $this->assertTrue( wp_is_https_supported() );
+
+               // Errors, so HTTPS is not supported.
+               $wp_error->add( 'ssl_verification_failed', 'SSL verification failed.' );
+               update_option( 'https_detection_errors', $wp_error->errors );
+               $this->assertFalse( wp_is_https_supported() );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_update_https_detection_errors() {
+               // Set HTTP URL, the request below should use its HTTPS version.
+               update_option( 'home', 'http://example.com/' );
+               add_filter( 'pre_http_request', array( $this, 'record_request_url' ), 10, 3 );
+
+               // If initial request succeeds, all good.
+               add_filter( 'pre_http_request', array( $this, 'mock_success_with_sslverify' ), 10, 2 );
+               wp_update_https_detection_errors();
+               $this->assertEquals( array(), get_option( 'https_detection_errors' ) );
+
+               // If initial request fails and request without SSL verification succeeds,
+               // return error with 'ssl_verification_failed' error code.
+               add_filter( 'pre_http_request', array( $this, 'mock_error_with_sslverify' ), 10, 2 );
+               add_filter( 'pre_http_request', array( $this, 'mock_success_without_sslverify' ), 10, 2 );
+               wp_update_https_detection_errors();
+               $this->assertEquals(
+                       array( 'ssl_verification_failed' => array( 'Bad SSL certificate.' ) ),
+                       get_option( 'https_detection_errors' )
+               );
+
+               // If both initial request and request without SSL verification fail,
+               // return actual error from request.
+               add_filter( 'pre_http_request', array( $this, 'mock_error_with_sslverify' ), 10, 2 );
+               add_filter( 'pre_http_request', array( $this, 'mock_error_without_sslverify' ), 10, 2 );
+               wp_update_https_detection_errors();
+               $this->assertEquals(
+                       array( 'bad_ssl_certificate' => array( 'Bad SSL certificate.' ) ),
+                       get_option( 'https_detection_errors' )
+               );
+
+               // If request succeeds, but response is not 200, return error with
+               // 'bad_response_code' error code.
+               add_filter( 'pre_http_request', array( $this, 'mock_not_found' ), 10, 2 );
+               wp_update_https_detection_errors();
+               $this->assertEquals(
+                       array( 'bad_response_code' => array( 'Not Found' ) ),
+                       get_option( 'https_detection_errors' )
+               );
+
+               // If request succeeds, but response was not generated by this
+               // WordPress site, return error with 'bad_response_source' error code.
+               add_filter( 'pre_http_request', array( $this, 'mock_bad_source' ), 10, 2 );
+               wp_update_https_detection_errors();
+               $this->assertEquals(
+                       array( 'bad_response_source' => array( 'It looks like the response did not come from this site.' ) ),
+                       get_option( 'https_detection_errors' )
+               );
+
+               // Check that the requests are made to the correct URL.
+               $this->assertEquals( 'https://example.com/', $this->last_request_url );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_schedule_https_detection() {
+               wp_schedule_https_detection();
+               $this->assertEquals( 'twicedaily', wp_get_schedule( 'wp_https_detection' ) );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_cron_conditionally_prevent_sslverify() {
+               // If URL is not using HTTPS, don't set 'sslverify' to false.
+               $request = array(
+                       'url'  => 'http://example.com/',
+                       'args' => array( 'sslverify' => true ),
+               );
+               $this->assertEquals( $request, wp_cron_conditionally_prevent_sslverify( $request ) );
+
+               // If URL is using HTTPS, set 'sslverify' to false.
+               $request                       = array(
+                       'url'  => 'https://example.com/',
+                       'args' => array( 'sslverify' => true ),
+               );
+               $expected                      = $request;
+               $expected['args']['sslverify'] = false;
+               $this->assertEquals( $expected, wp_cron_conditionally_prevent_sslverify( $request ) );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_is_owned_html_output_via_rsd_link() {
+               // HTML includes RSD link.
+               $head_tag = get_echo( 'rsd_link' );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML includes modified RSD link but same URL.
+               $head_tag = str_replace( ' />', '>', get_echo( 'rsd_link' ) );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML does not include RSD link.
+               $html = $this->get_sample_html_string();
+               $this->assertFalse( wp_is_owned_html_output( $html ) );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_is_owned_html_output_via_wlwmanifest_link() {
+               remove_action( 'wp_head', 'rsd_link' );
+
+               // HTML includes WLW manifest link.
+               $head_tag = get_echo( 'wlwmanifest_link' );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML includes modified WLW manifest link but same URL.
+               $head_tag = str_replace( ' />', '>', get_echo( 'wlwmanifest_link' ) );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML includes WLW manifest link with alternative URL scheme.
+               $head_tag = get_echo( 'wlwmanifest_link' );
+               $head_tag = false !== strpos( $head_tag, 'https://' ) ? str_replace( 'https://', 'http://', $head_tag ) : str_replace( 'http://', 'https://', $head_tag );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML does not include WLW manifest link.
+               $html = $this->get_sample_html_string();
+               $this->assertFalse( wp_is_owned_html_output( $html ) );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_is_owned_html_output_via_rest_link() {
+               remove_action( 'wp_head', 'rsd_link' );
+               remove_action( 'wp_head', 'wlwmanifest_link' );
+
+               // HTML includes REST API link.
+               $head_tag = get_echo( 'rest_output_link_wp_head' );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML includes modified REST API link but same URL.
+               $head_tag = str_replace( ' />', '>', get_echo( 'rest_output_link_wp_head' ) );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML includes REST API link with alternative URL scheme.
+               $head_tag = get_echo( 'rest_output_link_wp_head' );
+               $head_tag = false !== strpos( $head_tag, 'https://' ) ? str_replace( 'https://', 'http://', $head_tag ) : str_replace( 'http://', 'https://', $head_tag );
+               $html     = $this->get_sample_html_string( $head_tag );
+               $this->assertTrue( wp_is_owned_html_output( $html ) );
+
+               // HTML does not include REST API link.
+               $html = $this->get_sample_html_string();
+               $this->assertFalse( wp_is_owned_html_output( $html ) );
+       }
+
+       /**
+        * @ticket 47577
+        */
+       public function test_wp_is_owned_html_output_cannot_determine() {
+               remove_action( 'wp_head', 'rsd_link' );
+               remove_action( 'wp_head', 'wlwmanifest_link' );
+               remove_action( 'wp_head', 'rest_output_link_wp_head' );
+
+               // The HTML here doesn't matter because all hooks are removed.
+               $html = $this->get_sample_html_string();
+               $this->assertNull( wp_is_owned_html_output( $html ) );
+       }
+
+       public function record_request_url( $preempt, $parsed_args, $url ) {
+               $this->last_request_url = $url;
+               return $preempt;
+       }
+
+       public function mock_success_with_sslverify( $preempt, $parsed_args ) {
+               if ( ! empty( $parsed_args['sslverify'] ) ) {
+                       return $this->mock_success();
+               }
+               return $preempt;
+       }
+
+       public function mock_error_with_sslverify( $preempt, $parsed_args ) {
+               if ( ! empty( $parsed_args['sslverify'] ) ) {
+                       return $this->mock_error();
+               }
+               return $preempt;
+       }
+
+       public function mock_success_without_sslverify( $preempt, $parsed_args ) {
+               if ( empty( $parsed_args['sslverify'] ) ) {
+                       return $this->mock_success();
+               }
+               return $preempt;
+       }
+
+       public function mock_error_without_sslverify( $preempt, $parsed_args ) {
+               if ( empty( $parsed_args['sslverify'] ) ) {
+                       return $this->mock_error();
+               }
+               return $preempt;
+       }
+
+       public function mock_not_found() {
+               return array(
+                       'body'     => '<!DOCTYPE html><html><head><title>404</title></head><body>Not Found</body></html>',
+                       'response' => array(
+                               'code'    => 404,
+                               'message' => 'Not Found',
+                       ),
+               );
+       }
+
+       public function mock_bad_source() {
+               // Looks like a success response, but is not generated by WordPress (e.g. missing RSD link).
+               return array(
+                       'body'     => $this->get_sample_html_string(),
+                       'response' => array(
+                               'code'    => 200,
+                               'message' => 'OK',
+                       ),
+               );
+       }
+
+       private function mock_success() {
+               // Success response containing RSD link.
+               return array(
+                       'body'     => $this->get_sample_html_string( get_echo( 'rsd_link' ) ),
+                       'response' => array(
+                               'code'    => 200,
+                               'message' => 'OK',
+                       ),
+               );
+       }
+
+       private function mock_error() {
+               return new WP_Error( 'bad_ssl_certificate', 'Bad SSL certificate.' );
+       }
+
+       private function get_sample_html_string( $head_tag = '' ) {
+               return '<!DOCTYPE html><html><head><title>Page Title</title>' . $head_tag . '</head><body>Page Content.</body></html>';
+       }
+
+       /**
+        * Returns a filter callback that expects a URL and will set the URL scheme
+        * to the provided $scheme.
+        *
+        * @param string $scheme URL scheme to set.
+        * @return callable Filter callback.
+        */
+       private function filter_set_url_scheme( $scheme ) {
+               return function( $url ) use ( $scheme ) {
+                       return set_url_scheme( $url, $scheme );
+               };
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/https-detection.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>