├── changelog.md ├── css ├── settings.css └── tests.css ├── images └── ajax-loader.gif ├── includes ├── class.SSLInsecureContentFixer.php ├── class.SSLInsecureContentFixerAdmin.php └── nonces.php ├── js ├── admin-settings.js ├── admin-settings.min.js ├── admin-tests.js └── admin-tests.min.js ├── nowp ├── .htaccess └── ajax.php ├── readme.md ├── readme.txt ├── ssl-insecure-content-fixer.php └── views ├── requires-extensions.php ├── requires-pcre.php ├── script-force-https.php ├── settings-fields-common.php ├── settings-form-network.php ├── settings-form.php └── ssl-tests.php /changelog.md: -------------------------------------------------------------------------------- 1 | # SSL Insecure Content Fixer 2 | 3 | ## Changelog 4 | 5 | ### 2.7.2, 2018-12-04 6 | 7 | * fixed: was missing some hard-coded link elements (e.g. stylesheets) when href was the first attribute 8 | 9 | ### 2.7.1, 2018-11-21 10 | 11 | * tested: WordPress 5.0 12 | 13 | ### 2.7.0, 2018-06-30 14 | 15 | * added: fix for responsive images loaded by JavaScript from image data attributes 16 | * fixed: call to undefined function `hash_equals()` on environments with obsolete PHP versions (i.e. < 5.6) 17 | * fixed: don't run the fixer when a WooCommerce download request is detected 18 | 19 | ### 2.6.0, 2018-05-08 20 | 21 | * added: new filter `ssl_insecure_content_pcre_version_permissive` allowing sites that can't update PCRE beyond 7.2 to function 22 | * added: fix for plugins / themes overriding avatars and breaking them with insecure content 23 | * changed: no longer sets a cookie on test or settings pages 24 | 25 | ### 2.5.0, 2017-11-23 26 | 27 | * changed: .htaccess rules file for non-WP test script now supports Apache v2.4; thanks, [Andreas Schneider](https://github.com/cryptomilk)! 28 | * added: option to only fix content resource links for the current website; thanks, [Luke Driscoll](https://github.com/ldriscoll)! 29 | * added: support for KeyCDN https detection via the X-Forwarded-Scheme header 30 | 31 | ### 2.4.0, 2017-05-14 32 | 33 | * fixed: don't capture content on admin pages when mode is Capture or Capture All 34 | * added: filter `ssl_insecure_content_disable_capture` for disabling Capture mode on selected pages / scripts 35 | 36 | ### 2.3.0, 2017-05-01 37 | 38 | * added: support for Windows Azure with ARR 39 | * added: filter `ssl_insecure_content_domain_exclusions` for domains that can be excluded from content cleaning (ignored for enqueued scripts) 40 | 41 | ### 2.2.3, 2017-02-01 42 | 43 | * fixed: breaks Visual Composer back end editing due to a regular expression problem (now you have two!) 44 | * changed: Capture no longer captures AJAX requests; new mode Capture All introduced to capture AJAX requests too 45 | * added: prerequisites check, to ensure that plugin can run successfully 46 | 47 | ### 2.2.2, 2017-01-21 48 | 49 | * fixed: make protocol header tests case-insensitive (thanks, [waja](https://github.com/waja)!) 50 | * added: support for Amazon CloudFront `CloudFront-Forwarded-Proto` header (thanks, [gmazovec](https://github.com/gmazovec)!) 51 | * added: clean up responsive image srcset links to external images (WordPress already handles local images) 52 | 53 | ### 2.2.1, 2016-11-19 54 | 55 | * fixed: improve accessibility of admin pages 56 | * removed: update message display forced on multisite; just leave that for WordPress to handle (it does it so well) 57 | 58 | ### 2.2.0, 2016-09-09 59 | 60 | * added: stop WooCommerce cached widgets from http showing on https 61 | * added: fix Gravity Forms confirmation content 62 | 63 | ### 2.1.6, 2016-02-02 64 | 65 | * fixed: malware warning with GOTMLS vulnerability scanner 66 | 67 | ### 2.1.5, 2015-12-12 68 | 69 | * changed: remove some more clutter from server environment report in tests 70 | * removed: translations no longer in zip file; now delivered automatically as language packs when required 71 | 72 | ### 2.1.4, 2015-10-24 73 | 74 | * added: French translation (thanks, Houzepha Taheraly!) 75 | * added: can define `SSLFIX_PLUGIN_NO_HTTPS_DETECT` in wp-config.php to prevent the proxy fix, e.g. to overcome plugin conflicts 76 | * added: fix inline CSS background image rules, e.g. in Capture level 77 | * added: indicate whether WordPress HTTPS detection is successful with tick/cross 78 | 79 | ### 2.1.3, 2015-10-05 80 | 81 | * added: Chinese (simplified) translation (thanks, [漠伦](https://molun.net/)!) 82 | 83 | ### 2.1.2, 2015-09-05 84 | 85 | * fixed: HTTPS detection for host 123-reg 86 | 87 | ### 2.1.1, 2015-08-11 88 | 89 | * fixed: HTTPS detection doesn't work unless SSL Tests page was just visited 90 | * added: show update notice on plugin admin page 91 | 92 | ### 2.1.0, 2015-07-30 93 | 94 | * **SECURITY FIX**: restrict access to AJAX test script; don't disclose server environment with system information 95 | * changed: always show server environment on test results 96 | * added: Bulgarian translation (thanks, [Ivan Arnaudov](https://www.bvionline.eu/)!) 97 | * added: .htaccess file for AJAX SSL Tests, fixes conflict with some security plugins 98 | 99 | ### 2.0.0, 2015-07-26 100 | 101 | * changed: handle media loaded by calling `wp_get_attachment_image()`, `wp_get_attachment_image_src()`, etc. via AJAX 102 | * changed: in multisite, test tools (and settings) are only available to super admins 103 | * added: settings page for controlling behaviour 104 | * added: Simple, Content, Widgets, Capture, and Off modes for fixes 105 | * added: fix for [WooCommerce + Google Chrome HTTP_HTTPS bug](https://github.com/woothemes/woocommerce/issues/8479) (fixed in WooCommerce v2.3.13) 106 | * added: load translation (if anyone fancies [supplying some](https://translate.wordpress.org/projects/wp-plugins/ssl-insecure-content-fixer)!) 107 | 108 | ### 1.8.0, 2014-02-02 109 | 110 | * changed: use script/style source filters instead of iterating over script/style dependency objects 111 | * changed: only handle links for `wp_get_attachment_image()`, `wp_get_attachment_image_src()`, etc. on front end (i.e. not in admin) 112 | * changed: refactor for code simplification 113 | * added: fix data returned from `wp_upload_dir()` (fixes Contact Form 7 CAPTCHA images) 114 | * added: Tools menu link to `is_ssl()` test 115 | 116 | ### 1.7.1, 2013-03-13 117 | 118 | * fixed: is_ssl() test checks to ensure test page was actually loaded via HTTPS 119 | 120 | ### 1.7.0, 2013-03-13 121 | 122 | * added: simple test to see whether [is_ssl()](https://codex.wordpress.org/Function_Reference/is_ssl) is working, and try to diagnose when it isn't 123 | 124 | ### 1.6.0, 2013-01-05 125 | 126 | * added: handle images and other media loaded by calling `wp_get_attachment_image()`, `wp_get_attachment_image_src()`, etc. 127 | 128 | ### 1.5.0, 2012-11-09 129 | 130 | * added: handle properly enqueued admin stylesheets for admin over HTTPS 131 | 132 | ### 1.4.1, 2012-09-21 133 | 134 | * fixed: handle uppercase links properly (i.e. HTTP://) 135 | 136 | ### 1.4.0, 2012-09-13 137 | 138 | * added: fix for images loaded by [image-widget](https://wordpress.org/plugins/image-widget/) 139 | 140 | ### 1.3.0, 2012-07-22 141 | 142 | * removed: fix for links-shortcode (fixed in v1.3) 143 | 144 | ### 1.2.0, 2012-07-21 145 | 146 | * removed: fix for youtube-feeder (fixed in v2.0.0); NB: v2.0.0 of that plugin still loads Youtube videos over http, so you will still get insecure content errors on pages with embedded videos until plugin author applies a fix. 147 | 148 | ### 1.1.0, 2012-05-17 149 | 150 | * added: fix for youtube-feeder stylesheet 151 | 152 | ### 1.0.0, 2012-04-19 153 | 154 | * initial release 155 | -------------------------------------------------------------------------------- /css/settings.css: -------------------------------------------------------------------------------- 1 | #sslfix-levels label { 2 | font-weight: bold; 3 | } 4 | 5 | .sslfix-level-desc { 6 | padding: 0.5em 2em 0 2em; 7 | } 8 | 9 | .sslfix-bullets { 10 | list-style: disc; 11 | padding: 0.5em 3em; 12 | } 13 | 14 | .sslfix-recommended { 15 | font-weight: bold; 16 | } 17 | 18 | .sslfix-recommended span { 19 | padding: 0 2em; 20 | color: red; 21 | } 22 | 23 | #sslfix-https-detection.dashicons-yes { 24 | color: green; 25 | } 26 | 27 | #sslfix-https-detection.dashicons-no { 28 | color: red; 29 | } 30 | -------------------------------------------------------------------------------- /css/tests.css: -------------------------------------------------------------------------------- 1 | .sslfix-test-result, 2 | #sslfix-test-result-head { 3 | display: none; 4 | } 5 | 6 | #sslfix-https-detection.dashicons-yes { 7 | color: green; 8 | } 9 | 10 | #sslfix-https-detection.dashicons-no { 11 | color: red; 12 | } 13 | -------------------------------------------------------------------------------- /images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webaware/ssl-insecure-content-fixer/b1ab808c38775836c9c5ba4f563cbed884d0aae1/images/ajax-loader.gif -------------------------------------------------------------------------------- /includes/class.SSLInsecureContentFixer.php: -------------------------------------------------------------------------------- 1 | loadOptions(); 37 | $this->proxyFix(); 38 | $this->configureSiteOnly(); 39 | 40 | add_action('init', array($this, 'loadTranslations')); 41 | 42 | if ($this->allowFixer()) { 43 | add_action('init', array($this, 'runFilters'), 4); 44 | 45 | // filter script and stylesheet links 46 | add_filter('script_loader_src', 'ssl_insecure_content_fix_url'); 47 | add_filter('style_loader_src', 'ssl_insecure_content_fix_url'); 48 | 49 | // filter uploads dir so that plugins using it to determine upload URL also work 50 | add_filter('upload_dir', array(__CLASS__, 'uploadDir')); 51 | 52 | // catch plugins / themes overriding the user's avatar and breaking it 53 | add_filter('get_avatar', array($this, 'fixContent'), 9999); 54 | 55 | // filter image links on front end e.g. in calls to wp_get_attachment_image(), wp_get_attachment_image_src(), etc. 56 | if (!is_admin() || $this->isAjax()) { 57 | add_filter('wp_get_attachment_url', 'ssl_insecure_content_fix_url', 100); 58 | } 59 | 60 | switch ($this->options['fix_level']) { 61 | 62 | // handle Content fix level 63 | case 'content': 64 | add_filter('the_content', array($this, 'fixContent'), 9999); // also for fix_level 'widget' 65 | add_filter('widget_text', array($this, 'fixContent'), 9999); // not for fix_level 'widget' (no need to duplicate effort) 66 | break; 67 | 68 | // handle Widget fix level 69 | case 'widgets': 70 | add_filter('the_content', array($this, 'fixContent'), 9999); // also for fix_level 'content' 71 | add_action('dynamic_sidebar_before', array($this, 'fixWidgetsStart'), 9999, 2); 72 | add_action('dynamic_sidebar_after', array($this, 'fixWidgetsEnd'), 9999, 2); 73 | break; 74 | 75 | // handle Capture fix level (excludes AJAX calls) 76 | case 'capture': 77 | if (!is_admin() && !$this->isAjax()) { 78 | add_action('init', array($this, 'fixCaptureStart'), 5); 79 | } 80 | break; 81 | 82 | // handle Capture All fix level (even AJAX calls) 83 | case 'capture_all': 84 | if (!is_admin() || $this->isAjaxNotExcluded()) { 85 | add_action('init', array($this, 'fixCaptureStart'), 5); 86 | } 87 | break; 88 | 89 | } 90 | 91 | // handle some specific plugins 92 | if (!empty($this->options['fix_specific'])) { 93 | add_action('wp_print_styles', array($this, 'fixSpecific'), 100); 94 | } 95 | 96 | // filter WooCommerce cached widget ID if base site is not https 97 | if (stripos(get_option('home'), 'http://') === 0) { 98 | add_filter('woocommerce_cached_widget_id', array(__CLASS__, 'woocommerceWidgetID')); 99 | } 100 | 101 | // filter Gravity Forms confirmation content 102 | add_filter('gform_confirmation', array($this, 'fixContent')); 103 | 104 | // filter plugin Image Widget old-style image links 105 | add_filter('image_widget_image_url', 'ssl_insecure_content_fix_url'); 106 | } 107 | 108 | if (is_admin()) { 109 | require SSLFIX_PLUGIN_ROOT . 'includes/class.SSLInsecureContentFixerAdmin.php'; 110 | new SSLInsecureContentFixerAdmin(); 111 | } 112 | } 113 | 114 | /** 115 | * see whether the fixer should be run for this request 116 | * @return bool 117 | */ 118 | protected function allowFixer() { 119 | // do nothing if fixer is turned off 120 | if ($this->options['fix_level'] === 'off') { 121 | return false; 122 | } 123 | 124 | // don't mess with WooCommerce downloads 125 | if (isset($_GET['download_file']) && isset($_GET['order']) && (isset($_GET['email']) || isset($_GET['uid']))) { 126 | // but ensure that WooCommerce is active and will handle this request 127 | if ($this->isPluginActive('woocommerce/woocommerce.php')) { 128 | return false; 129 | } 130 | } 131 | 132 | return is_ssl(); 133 | } 134 | 135 | /** 136 | * test whether a plugin is active 137 | * @param string $plugin 138 | * @return bool 139 | */ 140 | protected function isPluginActive($plugin) { 141 | if (is_multisite()) { 142 | $plugins = (array) get_site_option('active_sitewide_plugins', array()); 143 | if (isset($plugins[$plugin])) { 144 | return true; 145 | } 146 | } 147 | 148 | return in_array($plugin, (array) get_option('active_plugins', array())); 149 | } 150 | 151 | /** 152 | * detect AJAX call 153 | * @return bool 154 | */ 155 | protected function isAjax() { 156 | if (function_exists('wp_doing_ajax')) { 157 | $is_ajax = wp_doing_ajax(); 158 | } 159 | else { 160 | $is_ajax = defined('DOING_AJAX') && DOING_AJAX; 161 | } 162 | 163 | return $is_ajax; 164 | } 165 | 166 | /** 167 | * exclude certain AJAX calls from capture_all 168 | * @return bool 169 | */ 170 | protected function isAjaxNotExcluded() { 171 | $is_ajax = $this->isAjax(); 172 | 173 | if ($is_ajax) { 174 | $exclude = false; 175 | 176 | if (!empty($_REQUEST['action'])) { 177 | $exclude = in_array($_REQUEST['action'], array( 178 | // some standard WordPress actions 179 | 'heartbeat', 180 | 181 | // this plugin 182 | 'sslfix-test-https', 183 | )); 184 | } 185 | 186 | $is_ajax = !apply_filters('ssl_insecure_content_ajax_exclude', $exclude); 187 | } 188 | 189 | return $is_ajax; 190 | } 191 | 192 | /** 193 | * run filters for plugins / themes that register domain exclusions 194 | */ 195 | public function runFilters() { 196 | $domains = apply_filters('ssl_insecure_content_domain_exclusions', array()); 197 | if (!empty($domains) && is_array($domains)) { 198 | $this->domain_exclusions = $domains; 199 | } 200 | } 201 | 202 | /** 203 | * load options for plugin 204 | */ 205 | protected function loadOptions() { 206 | $defaults = array( 207 | 'fix_level' => 'simple', 208 | 'proxy_fix' => 'normal', 209 | 'fix_specific' => array( 210 | 'woo_https' => 1 211 | ), 212 | ); 213 | 214 | if (is_multisite()) { 215 | $this->network_options = get_site_option(SSLFIX_PLUGIN_OPTIONS, $defaults); 216 | 217 | // use network-wide settings as default for individual sites 218 | $defaults = $this->network_options; 219 | } 220 | 221 | $this->options = get_option(SSLFIX_PLUGIN_OPTIONS, $defaults); 222 | } 223 | 224 | /** 225 | * check options for required proxy fix 226 | */ 227 | protected function proxyFix() { 228 | // failsafe: allow website owners to force the proxy fix off, in case of conflicts 229 | if (defined('SSLFIX_PLUGIN_NO_HTTPS_DETECT') && SSLFIX_PLUGIN_NO_HTTPS_DETECT) { 230 | return; 231 | } 232 | 233 | if (!empty($this->options['proxy_fix'])) { 234 | switch ($this->options['proxy_fix']) { 235 | 236 | case 'HTTP_X_FORWARDED_PROTO': 237 | if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') { 238 | $_SERVER['HTTPS'] = 'on'; 239 | } 240 | break; 241 | 242 | case 'HTTP_X_FORWARDED_SSL': 243 | if (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && (strtolower($_SERVER['HTTP_X_FORWARDED_SSL']) === 'on' || $_SERVER['HTTP_X_FORWARDED_SSL'] === '1')) { 244 | $_SERVER['HTTPS'] = 'on'; 245 | } 246 | break; 247 | 248 | case 'HTTP_CLOUDFRONT_FORWARDED_PROTO': 249 | if (isset($_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_CLOUDFRONT_FORWARDED_PROTO']) === 'https') { 250 | $_SERVER['HTTPS'] = 'on'; 251 | } 252 | break; 253 | 254 | case 'HTTP_CF_VISITOR': 255 | if (isset($_SERVER['HTTP_CF_VISITOR']) && strpos($_SERVER['HTTP_CF_VISITOR'], 'https') !== false) { 256 | $_SERVER['HTTPS'] = 'on'; 257 | } 258 | break; 259 | 260 | case 'HTTP_X_ARR_SSL': 261 | if (!empty($_SERVER['HTTP_X_ARR_SSL'])) { 262 | $_SERVER['HTTPS'] = 'on'; 263 | } 264 | break; 265 | 266 | case 'HTTP_X_FORWARDED_SCHEME': 267 | if (!empty($_SERVER['HTTP_X_FORWARDED_SCHEME']) && strtolower($_SERVER['HTTP_X_FORWARDED_SCHEME']) === 'https') { 268 | $_SERVER['HTTPS'] = 'on'; 269 | } 270 | break; 271 | 272 | case 'detect_fail': 273 | // only force-enable https if site is set to run fully on https 274 | if (stripos(get_option('siteurl'), 'https://') === 0) { 275 | $_SERVER['HTTPS'] = 'on'; 276 | 277 | // add JavaScript detection of page protocol, and pray! 278 | add_action('wp_print_scripts', array($this, 'scriptForceHTTPS')); 279 | } 280 | break; 281 | 282 | } 283 | } 284 | 285 | if (!empty($this->options['fix_specific']['woo_https'])) { 286 | // stop old WooCommerce versions from falsely detecting HTTPS from Google Chrome/Chromium 287 | // @link https://woocommerce.wordpress.com/2015/07/07/woocommerce-2-3-13-security-and-maintenance-release/ 288 | // @link https://github.com/woothemes/woocommerce/issues/8479 289 | // @link https://superuser.com/a/943989/473190 290 | unset($_SERVER['HTTP_HTTPS']); 291 | } 292 | } 293 | 294 | /** 295 | * if Ignore External Sites is selected, record the http site URL for this website 296 | */ 297 | protected function configureSiteOnly() { 298 | if (!empty($this->options['site_only'])) { 299 | $this->process_only_site = site_url('', 'http'); 300 | } 301 | } 302 | 303 | /** 304 | * load text translations 305 | */ 306 | public function loadTranslations() { 307 | load_plugin_textdomain('ssl-insecure-content-fixer'); 308 | } 309 | 310 | /** 311 | * fix images, embeds, iframes in content 312 | * @param string $content 313 | * @return string 314 | */ 315 | public function fixContent($content) { 316 | static $searches = array( 317 | '#<(?:img|iframe) .*?src=[\'"]\Khttp://[^\'"]+#i', // fix image and iframe elements 318 | '#]*href=[\'"]\Khttp://[^\'"]+#i', // fix link elements 319 | '# 12 | -------------------------------------------------------------------------------- /views/settings-fields-common.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
11 |
wp_enqueue_script()
', 'fix level settings', 'ssl-insecure-content-fixer'); ?>wp_enqueue_style()
', 'fix level settings', 'ssl-insecure-content-fixer'); ?>wp_get_attachment_image_src()
, etc.', 'fix level settings', 'ssl-insecure-content-fixer'); ?>79 |
96 |
109 |
_x('standard WordPress function', 'proxy settings', 'ssl-insecure-content-fixer'),
113 | 'HTTP_X_FORWARDED_PROTO' => _x('HTTP_X_FORWARDED_PROTO (e.g. load balancer, reverse proxy, NginX)', 'proxy settings', 'ssl-insecure-content-fixer'),
114 | 'HTTP_X_FORWARDED_SSL' => _x('HTTP_X_FORWARDED_SSL (e.g. reverse proxy)', 'proxy settings', 'ssl-insecure-content-fixer'),
115 | 'HTTP_CLOUDFRONT_FORWARDED_PROTO' => _x('HTTP_CLOUDFRONT_FORWARDED_PROTO (Amazon CloudFront HTTPS cached content)', 'proxy settings', 'ssl-insecure-content-fixer'),
116 | 'HTTP_X_FORWARDED_SCHEME' => _x('HTTP_X_FORWARDED_SCHEME (e.g. KeyCDN)', 'proxy settings', 'ssl-insecure-content-fixer'),
117 | 'HTTP_X_ARR_SSL' => _x('HTTP_X_ARR_SSL (Windows Azure ARR)', 'proxy settings', 'ssl-insecure-content-fixer'),
118 | 'HTTP_CF_VISITOR' => _x('HTTP_CF_VISITOR (Cloudflare Flexible SSL); deprecated, since Cloudflare sends HTTP_X_FORWARDED_PROTO now', 'proxy settings', 'ssl-insecure-content-fixer'),
119 | 'detect_fail' => _x('unable to detect HTTPS', 'proxy settings', 'ssl-insecure-content-fixer'),
120 | );
121 |
122 | foreach ($proxies as $value => $label) {
123 | $id = "proxy_fix_{$value}";
124 |
125 | ?> />
127 |
17 |
19 | 18 |