/wp-includes/https-detection.php

https://github.com/livinglab/openlab · PHP · 227 lines · 99 code · 24 blank · 104 comment · 14 complexity · d4bdf7894bdc9185080c2bcc718f1c5f MD5 · raw file

  1. <?php
  2. /**
  3. * HTTPS detection functions.
  4. *
  5. * @package WordPress
  6. * @since 5.7.0
  7. */
  8. /**
  9. * Checks whether the website is using HTTPS.
  10. *
  11. * This is based on whether both the home and site URL are using HTTPS.
  12. *
  13. * @since 5.7.0
  14. * @see wp_is_home_url_using_https()
  15. * @see wp_is_site_url_using_https()
  16. *
  17. * @return bool True if using HTTPS, false otherwise.
  18. */
  19. function wp_is_using_https() {
  20. if ( ! wp_is_home_url_using_https() ) {
  21. return false;
  22. }
  23. return wp_is_site_url_using_https();
  24. }
  25. /**
  26. * Checks whether the current site URL is using HTTPS.
  27. *
  28. * @since 5.7.0
  29. * @see home_url()
  30. *
  31. * @return bool True if using HTTPS, false otherwise.
  32. */
  33. function wp_is_home_url_using_https() {
  34. return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME );
  35. }
  36. /**
  37. * Checks whether the current site's URL where WordPress is stored is using HTTPS.
  38. *
  39. * This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder)
  40. * are accessible.
  41. *
  42. * @since 5.7.0
  43. * @see site_url()
  44. *
  45. * @return bool True if using HTTPS, false otherwise.
  46. */
  47. function wp_is_site_url_using_https() {
  48. // Use direct option access for 'siteurl' and manually run the 'site_url'
  49. // filter because `site_url()` will adjust the scheme based on what the
  50. // current request is using.
  51. /** This filter is documented in wp-includes/link-template.php */
  52. $site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null );
  53. return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME );
  54. }
  55. /**
  56. * Checks whether HTTPS is supported for the server and domain.
  57. *
  58. * @since 5.7.0
  59. *
  60. * @return bool True if HTTPS is supported, false otherwise.
  61. */
  62. function wp_is_https_supported() {
  63. $https_detection_errors = get_option( 'https_detection_errors' );
  64. // If option has never been set by the Cron hook before, run it on-the-fly as fallback.
  65. if ( false === $https_detection_errors ) {
  66. wp_update_https_detection_errors();
  67. $https_detection_errors = get_option( 'https_detection_errors' );
  68. }
  69. // If there are no detection errors, HTTPS is supported.
  70. return empty( $https_detection_errors );
  71. }
  72. /**
  73. * Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors.
  74. *
  75. * This internal function is called by a regular Cron hook to ensure HTTPS support is detected and maintained.
  76. *
  77. * @since 5.7.0
  78. * @access private
  79. */
  80. function wp_update_https_detection_errors() {
  81. /**
  82. * Short-circuits the process of detecting errors related to HTTPS support.
  83. *
  84. * Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote
  85. * request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead.
  86. *
  87. * @since 5.7.0
  88. *
  89. * @param null|WP_Error $pre Error object to short-circuit detection,
  90. * or null to continue with the default behavior.
  91. */
  92. $support_errors = apply_filters( 'pre_wp_update_https_detection_errors', null );
  93. if ( is_wp_error( $support_errors ) ) {
  94. update_option( 'https_detection_errors', $support_errors->errors );
  95. return;
  96. }
  97. $support_errors = new WP_Error();
  98. $response = wp_remote_request(
  99. home_url( '/', 'https' ),
  100. array(
  101. 'headers' => array(
  102. 'Cache-Control' => 'no-cache',
  103. ),
  104. 'sslverify' => true,
  105. )
  106. );
  107. if ( is_wp_error( $response ) ) {
  108. $unverified_response = wp_remote_request(
  109. home_url( '/', 'https' ),
  110. array(
  111. 'headers' => array(
  112. 'Cache-Control' => 'no-cache',
  113. ),
  114. 'sslverify' => false,
  115. )
  116. );
  117. if ( is_wp_error( $unverified_response ) ) {
  118. $support_errors->add(
  119. 'https_request_failed',
  120. __( 'HTTPS request failed.' )
  121. );
  122. } else {
  123. $support_errors->add(
  124. 'ssl_verification_failed',
  125. __( 'SSL verification failed.' )
  126. );
  127. }
  128. $response = $unverified_response;
  129. }
  130. if ( ! is_wp_error( $response ) ) {
  131. if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
  132. $support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) );
  133. } elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) {
  134. $support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) );
  135. }
  136. }
  137. update_option( 'https_detection_errors', $support_errors->errors );
  138. }
  139. /**
  140. * Schedules the Cron hook for detecting HTTPS support.
  141. *
  142. * @since 5.7.0
  143. * @access private
  144. */
  145. function wp_schedule_https_detection() {
  146. if ( wp_installing() ) {
  147. return;
  148. }
  149. if ( ! wp_next_scheduled( 'wp_https_detection' ) ) {
  150. wp_schedule_event( time(), 'twicedaily', 'wp_https_detection' );
  151. }
  152. }
  153. /**
  154. * Disables SSL verification if the 'cron_request' arguments include an HTTPS URL.
  155. *
  156. * This prevents an issue if HTTPS breaks, where there would be a failed attempt to verify HTTPS.
  157. *
  158. * @since 5.7.0
  159. * @access private
  160. *
  161. * @param array $request The Cron request arguments.
  162. * @return array $request The filtered Cron request arguments.
  163. */
  164. function wp_cron_conditionally_prevent_sslverify( $request ) {
  165. if ( 'https' === wp_parse_url( $request['url'], PHP_URL_SCHEME ) ) {
  166. $request['args']['sslverify'] = false;
  167. }
  168. return $request;
  169. }
  170. /**
  171. * Checks whether a given HTML string is likely an output from this WordPress site.
  172. *
  173. * This function attempts to check for various common WordPress patterns whether they are included in the HTML string.
  174. * Since any of these actions may be disabled through third-party code, this function may also return null to indicate
  175. * that it was not possible to determine ownership.
  176. *
  177. * @since 5.7.0
  178. * @access private
  179. *
  180. * @param string $html Full HTML output string, e.g. from a HTTP response.
  181. * @return bool|null True/false for whether HTML was generated by this site, null if unable to determine.
  182. */
  183. function wp_is_local_html_output( $html ) {
  184. // 1. Check if HTML includes the site's Really Simple Discovery link.
  185. if ( has_action( 'wp_head', 'rsd_link' ) ) {
  186. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link().
  187. return false !== strpos( $html, $pattern );
  188. }
  189. // 2. Check if HTML includes the site's Windows Live Writer manifest link.
  190. if ( has_action( 'wp_head', 'wlwmanifest_link' ) ) {
  191. // Try both HTTPS and HTTP since the URL depends on context.
  192. $pattern = preg_replace( '#^https?:(?=//)#', '', includes_url( 'wlwmanifest.xml' ) ); // See wlwmanifest_link().
  193. return false !== strpos( $html, $pattern );
  194. }
  195. // 3. Check if HTML includes the site's REST API link.
  196. if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) {
  197. // Try both HTTPS and HTTP since the URL depends on context.
  198. $pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head().
  199. return false !== strpos( $html, $pattern );
  200. }
  201. // Otherwise the result cannot be determined.
  202. return null;
  203. }