PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.11.12
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.11.12
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | includes/class-https-enforcer.php +67 -26 2.9.42.11.12 View file →
@@ -40,8 +40,17 @@
40 40 */
41 41 private $ob_started = false;
42 42
43 43 /**
44 + * Nesting level of the buffer opened by this class.
45 + *
46 + * Recorded so shutdown can tell whether the buffer on top is still ours.
47 + *
48 + * @var int
49 + */
50 + private $ob_level = 0;
51 +
52 + /**
44 53 * Constructor
45 54 *
46 55 * @param Vigilante_Settings $settings Settings instance.
47 56 */
@@ -68,10 +77,13 @@
68 77 add_filter( 'wp_get_attachment_url', array( $this, 'fix_url_scheme' ), 10, 1 );
69 78 add_filter( 'the_content', array( $this, 'fix_content_urls' ), 999 );
70 79 add_filter( 'widget_text', array( $this, 'fix_content_urls' ), 999 );
71 80
72 - // The rewriters above only cover same-domain URLs; external
73 - // http:// references need the browser-side CSP directive.
81 + }
82 +
83 + // Its own setting since 2.9.8: the rewriters above are same-domain and
84 + // harmless, this one governs everybody else's resources too.
85 + if ( ! empty( $this->options['upgrade_insecure_requests'] ) ) {
74 86 add_action( 'send_headers', array( $this, 'emit_upgrade_insecure_requests' ) );
75 87 }
76 88 }
77 89
@@ -83,8 +95,22 @@
83 95 if ( is_ssl() ) {
84 96 return;
85 97 }
86 98
99 + /*
100 + * Only redirect when the site itself declares HTTPS. A site whose home
101 + * URL is still http:// has not moved to HTTPS, and sending every request
102 + * to an address that may not answer takes it offline outright. Read from
103 + * the home option (which honours the WP_HOME constant through the
104 + * option_home filter) rather than home_url(), so the answer is the
105 + * address the site declares and not one derived from the current
106 + * request. A site already on HTTPS has an https home URL and keeps
107 + * redirecting exactly as before.
108 + */
109 + if ( 0 !== strpos( (string) get_option( 'home' ), 'https://' ) ) {
110 + return;
111 + }
112 +
87 113 // Skip CLI
88 114 if ( defined( 'WP_CLI' ) && WP_CLI ) {
89 115 return;
90 116 }
@@ -141,13 +167,46 @@
141 167 if ( ! is_ssl() ) {
142 168 return;
143 169 }
144 170
171 + // The rewriter only touches complete HTML documents (fix_output_buffer()
172 + // bails on anything without <html or <!DOCTYPE), so buffering the admin,
173 + // AJAX and REST responses pays for a buffer and a callback that can never
174 + // do any work. On a WooCommerce site the cart-fragments endpoint alone is
175 + // dozens of those per visitor.
176 + if ( is_admin() || wp_doing_ajax() || $this->is_rest_request() ) {
177 + return;
178 + }
179 +
145 180 ob_start( array( $this, 'fix_output_buffer' ) );
146 181 $this->ob_started = true;
182 + $this->ob_level = ob_get_level();
147 183 }
148 184
149 185 /**
186 + * Whether the current request is a REST API request.
187 + *
188 + * REST_REQUEST is only defined once the request is being served, which is
189 + * after wp_loaded, so the REST route prefix is checked as well.
190 + *
191 + * @return bool
192 + */
193 + private function is_rest_request() {
194 + if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
195 + return true;
196 + }
197 +
198 + if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
199 + return false;
200 + }
201 +
202 + $path = wp_parse_url( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), PHP_URL_PATH );
203 + $prefix = '/' . trim( rest_get_url_prefix(), '/' ) . '/';
204 +
205 + return is_string( $path ) && 0 === strpos( $path, $prefix );
206 + }
207 +
208 + /**
150 209 * Explicitly close the output buffer on shutdown.
151 210 *
152 211 * Ensures the buffer opened by start_output_buffer() is always
153 212 * properly closed within the same logical flow.
@@ -152,9 +211,14 @@
152 211 * Ensures the buffer opened by start_output_buffer() is always
153 212 * properly closed within the same logical flow.
154 213 */
155 214 public function end_output_buffer() {
156 - if ( $this->ob_started && ob_get_level() > 0 ) {
215 + // Only flush when the buffer on top is exactly the one we opened. Testing
216 + // for "is there any buffer at all" would close somebody else's buffer when
217 + // another plugin opened one after ours and had not closed it yet, leaving
218 + // ours open on top of that. When the levels do not match, doing nothing is
219 + // the safe move: PHP flushes what is left at the end of the request.
220 + if ( $this->ob_started && ob_get_level() === $this->ob_level ) {
157 221 ob_end_flush();
158 222 $this->ob_started = false;
159 223 }
160 224 }
@@ -245,31 +309,8 @@
245 309 return $content;
246 310 }
247 311
248 312 return $this->replace_http_with_https( $content );
249 - }
250 -
251 - /**
252 - * Update WordPress site URLs to HTTPS
253 - *
254 - * @return bool
255 - */
256 - public function update_site_urls() {
257 - $siteurl = get_option( 'siteurl' );
258 - $home = get_option( 'home' );
259 - $updated = false;
260 -
261 - if ( strpos( $siteurl, 'http://' ) === 0 ) {
262 - update_option( 'siteurl', str_replace( 'http://', 'https://', $siteurl ) );
263 - $updated = true;
264 - }
265 -
266 - if ( strpos( $home, 'http://' ) === 0 ) {
267 - update_option( 'home', str_replace( 'http://', 'https://', $home ) );
268 - $updated = true;
269 - }
270 -
271 - return $updated;
272 313 }
273 314
274 315 /**
275 316 * Check if site is properly configured for HTTPS