Skip to content

Commit 502c19b

Browse files
committed
Sitemaps: Don't 404 valid sitemaps on sites with no posts.
`WP::handle_404()` sets a 404 when the main query matches no posts and no exception applies. Sitemap requests were never among those exceptions; they were shielded only incidentally, by falling through to `is_home`, and in r62664 that fallthrough was removed. A site with no published posts therefore served a complete, valid sitemap under a 404 status, which search engines discard. Exempt sitemap and stylesheet routes there, alongside the existing admin, robots and favicon exceptions. Since `handle_404()` no longer decides the status for these requests, every sitemap 404 now has to be issued by `WP_Sitemaps::render_sitemaps()` instead: an unregistered provider, an unrecognized stylesheet type, and a route whose query vars do not survive `sanitize_text_field()` would each otherwise be served as a 200 on an arbitrary URL. These share a `send_404()` helper, which also sends the no-cache headers `handle_404()` was previously contributing, so an intermediary does not retain a 404 for a route that becomes valid once the site has more content. Sitemaps disabled via the `wp_sitemaps_enabled` filter, and providers with an empty URL list, keep the status they already had; whether the latter should render an empty sitemap instead is #61293. Developed in WordPress/wordpress-develop#13247. Follow-up to r48072, r48523, r62664. Reviewed by adamsilverstein. Merges r63570 to the 7.0 branch. Props iamchitti, westonruter, fernandot, wildworks, harishtewari, l1onofjudah, luksusspokoju, abrahamfariaz, andreasca, siliconforks, adamsilverstein, audrasjb, ocean90, mrkenobi. See #39157, #61293. Fixes #65945. Built from https://develop.svn.wordpress.org/branches/7.0@63573 git-svn-id: http://core.svn.wordpress.org/branches/7.0@62749 1a063a9b-81f0-0310-95a4-ce76da25c4cd
1 parent 7bdc443 commit 502c19b

2 files changed

Lines changed: 47 additions & 10 deletions

File tree

wp-includes/class-wp.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,8 +746,9 @@ public function handle_404() {
746746

747747
$set_404 = true;
748748

749-
// Never 404 for the admin, robots, or favicon.
750-
if ( is_admin() || is_robots() || is_favicon() ) {
749+
// Never 404 here for the admin, robots, favicon, or sitemaps.
750+
// Sitemap routes send their own status in WP_Sitemaps::render_sitemaps().
751+
if ( is_admin() || is_robots() || is_favicon() || is_sitemap() || get_query_var( 'sitemap-stylesheet' ) ) {
751752
$set_404 = false;
752753

753754
// If posts were found, check for paged content.

wp-includes/sitemaps/class-wp-sitemaps.php

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,30 +157,45 @@ public function register_rewrites() {
157157
* Renders sitemap templates based on rewrite rules.
158158
*
159159
* @since 5.5.0
160-
*
161-
* @global WP_Query $wp_query WordPress Query object.
162160
*/
163161
public function render_sitemaps() {
164-
global $wp_query;
162+
/*
163+
* Bail early if this isn't a sitemap or stylesheet route.
164+
*
165+
* This runs on every front-end request, so it comes before any
166+
* sanitizing. The raw query vars are tested here, matching
167+
* WP::handle_404(), which exempts sitemap requests from its own 404 on
168+
* the same basis. Testing the sanitized values instead would let a
169+
* request that handle_404() exempted fall through both, leaving it a 200.
170+
*/
171+
if ( ! get_query_var( 'sitemap' ) && ! get_query_var( 'sitemap-stylesheet' ) ) {
172+
return;
173+
}
165174

166175
$sitemap = sanitize_text_field( get_query_var( 'sitemap' ) );
167176
$object_subtype = sanitize_text_field( get_query_var( 'sitemap-subtype' ) );
168177
$stylesheet_type = sanitize_text_field( get_query_var( 'sitemap-stylesheet' ) );
169178
$paged = absint( get_query_var( 'paged' ) );
170179

171-
// Bail early if this isn't a sitemap or stylesheet route.
180+
// Force a 404 and bail early if the route did not survive sanitizing.
172181
if ( ! ( $sitemap || $stylesheet_type ) ) {
182+
$this->send_404();
173183
return;
174184
}
175185

176186
if ( ! $this->sitemaps_enabled() ) {
177-
$wp_query->set_404();
178-
status_header( 404 );
187+
$this->send_404();
179188
return;
180189
}
181190

182191
// Render stylesheet if this is stylesheet route.
183192
if ( $stylesheet_type ) {
193+
// Force a 404 and bail early if the stylesheet type is not recognized.
194+
if ( ! in_array( $stylesheet_type, array( 'sitemap', 'index' ), true ) ) {
195+
$this->send_404();
196+
return;
197+
}
198+
184199
$stylesheet = new WP_Sitemaps_Stylesheet();
185200

186201
$stylesheet->render_stylesheet( $stylesheet_type );
@@ -197,7 +212,9 @@ public function render_sitemaps() {
197212

198213
$provider = $this->registry->get_provider( $sitemap );
199214

215+
// Force a 404 and bail early if the requested provider is not registered.
200216
if ( ! $provider ) {
217+
$this->send_404();
201218
return;
202219
}
203220

@@ -209,15 +226,34 @@ public function render_sitemaps() {
209226

210227
// Force a 404 and bail early if no URLs are present.
211228
if ( empty( $url_list ) ) {
212-
$wp_query->set_404();
213-
status_header( 404 );
229+
$this->send_404();
214230
return;
215231
}
216232

217233
$this->renderer->render_sitemap( $url_list );
218234
exit;
219235
}
220236

237+
/**
238+
* Sends a 404 for a sitemap route that cannot be served.
239+
*
240+
* WP::handle_404() exempts sitemap requests, so every sitemap 404 is issued
241+
* here instead. That includes the no-cache headers handle_404() sends with
242+
* its own 404, so an intermediary does not retain a 404 for a route that
243+
* becomes valid once the site has more content.
244+
*
245+
* @since 7.1.1
246+
*
247+
* @global WP_Query $wp_query WordPress Query object.
248+
*/
249+
private function send_404(): void {
250+
global $wp_query;
251+
252+
$wp_query->set_404();
253+
status_header( 404 );
254+
nocache_headers();
255+
}
256+
221257
/**
222258
* Redirects a URL to the wp-sitemap.xml
223259
*

0 commit comments

Comments
 (0)