Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent auto-reloading on error pages in response to POST requests #763

Merged
merged 3 commits into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 6 additions & 22 deletions tests/test-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Tests for template.php
*/
class Test_Template extends TestCase {

/**
* Test if function adding script on GET method, is_offline() or is_500 is true
*
Expand All @@ -21,8 +22,7 @@ public function test_wp_service_worker_offline_page_reload() {
$this->assertEquals( 10, has_action( 'error_footer', 'wp_service_worker_offline_page_reload' ) );

// Check when method is GET but not offline or 500.
$actual_script = wp_service_worker_offline_page_reload();
$this->assertEquals( $_SERVER['REQUEST_METHOD'], 'GET' );
$actual_script = get_echo( 'wp_service_worker_offline_page_reload' );
$this->assertFalse( is_offline() );
$this->assertFalse( is_500() );
$this->assertEmpty( $actual_script );
Expand All @@ -31,36 +31,20 @@ public function test_wp_service_worker_offline_page_reload() {
$error_template_url = add_query_arg( 'wp_error_template', 'offline', home_url( '/', 'relative' ) );
$this->go_to( $error_template_url );

ob_start();
wp_service_worker_offline_page_reload();
$actual_script = ob_get_clean();
$this->assertEquals( $_SERVER['REQUEST_METHOD'], 'GET' );
$actual_script = get_echo( 'wp_service_worker_offline_page_reload' );
$this->assertTrue( is_offline() );
$this->assertFalse( is_500() );
$this->assertStringContainsString( '<script type="module">', $actual_script );
$this->assertStringContainsString( 'await fetch(location.href, {method: \'HEAD\'})', $actual_script );
$this->assertStringContainsString( 'await fetch', $actual_script );

// Check if script is added when 500.
$error_template_url = add_query_arg( 'wp_error_template', '500', home_url( '/', 'relative' ) );
$this->go_to( $error_template_url );

ob_start();
wp_service_worker_offline_page_reload();
$actual_script = ob_get_clean();
$this->assertEquals( $_SERVER['REQUEST_METHOD'], 'GET' );
$actual_script = get_echo( 'wp_service_worker_offline_page_reload' );
$this->assertFalse( is_offline() );
$this->assertTrue( is_500() );
$this->assertStringContainsString( '<script type="module">', $actual_script );
$this->assertStringContainsString( 'await fetch(location.href, {method: \'HEAD\'})', $actual_script );

$this->go_to( home_url( '/', 'relative' ) );

// Check when method is not GET.
$_SERVER['REQUEST_METHOD'] = 'POST';
$actual_script = wp_service_worker_offline_page_reload();
$this->assertEquals( $_SERVER['REQUEST_METHOD'], 'POST' );
$this->assertFalse( is_offline() );
$this->assertFalse( is_500() );
$this->assertEmpty( $actual_script );
$this->assertStringContainsString( 'await fetch', $actual_script );
}
}
33 changes: 32 additions & 1 deletion wp-includes/js/service-worker-navigation-routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ ERROR_OFFLINE_URL, ERROR_500_URL, NAVIGATION_DENYLIST_PATTERNS, ERROR_MESSAGES *
const errorMessages = ERROR_MESSAGES;
const navigationRouteEntry = NAVIGATION_ROUTE_ENTRY;

/**
* Inject navigation request properties.
*
* @param {string} body
* @param {Request} request
* @param {Response} response
* @return {string} Modified body.
*/
const injectNavigationRequestProperties = (body, request, response) => {
return body.replace(
'{{{WP_NAVIGATION_REQUEST_PROPERTIES}}}',
JSON.stringify({
method: request.method,
status: response.status,
})
);
};

// Configure navigation preload.
if (false !== navigationPreload) {
if (typeof navigationPreload === 'string') {
Expand Down Expand Up @@ -71,6 +89,13 @@ ERROR_OFFLINE_URL, ERROR_500_URL, NAVIGATION_DENYLIST_PATTERNS, ERROR_MESSAGES *
'{{{WP_SERVICE_WORKER_ERROR_MESSAGE}}}',
errorMessages.error
);

body = injectNavigationRequestProperties(
body,
event.request,
response
);

body = body.replace(
/({{{WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN}}})((?:.|\n)+?)({{{WP_SERVICE_WORKER_ERROR_TEMPLATE_END}}})/,
(details) => {
Expand Down Expand Up @@ -134,13 +159,19 @@ ERROR_OFFLINE_URL, ERROR_500_URL, NAVIGATION_DENYLIST_PATTERNS, ERROR_MESSAGES *
headers: response.headers,
};

const body = text.replace(
let body = text.replace(
'{{{WP_SERVICE_WORKER_ERROR_MESSAGE}}}',
navigator.onLine
? errorMessages.serverOffline
: errorMessages.clientOffline
);

body = injectNavigationRequestProperties(
body,
event.request,
response
);

return new Response(body, init);
});
});
Expand Down
33 changes: 32 additions & 1 deletion wp-includes/js/service-worker-offline-post-request-handling.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
(() => {
const errorMessages = ERROR_MESSAGES;

/**
* Inject navigation request properties.
*
* @param {string} body
* @param {Request} request
* @param {Response} response
* @return {string} Modified body.
*/
const injectNavigationRequestProperties = (body, request, response) => {
return body.replace(
'{{{WP_NAVIGATION_REQUEST_PROPERTIES}}}',
JSON.stringify({
method: request.method,
status: response.status,
})
);
};

const offlinePostRequestHandler = ({ event }) => {
return fetch(event.request)
.then((response) => {
Expand Down Expand Up @@ -34,6 +52,13 @@
'{{{WP_SERVICE_WORKER_ERROR_MESSAGE}}}',
`${errorMessages.error} <strong>${errorMessages.submissionFailure}</strong>`
);

body = injectNavigationRequestProperties(
body,
event.request,
response
);

body = body.replace(
/({{{WP_SERVICE_WORKER_ERROR_TEMPLATE_BEGIN}}})((?:.|\n)+?)({{{WP_SERVICE_WORKER_ERROR_TEMPLATE_END}}})/,
(details) => {
Expand Down Expand Up @@ -102,11 +127,17 @@
? errorMessages.serverOffline
: errorMessages.clientOffline;

const body = text.replace(
let body = text.replace(
'{{{WP_SERVICE_WORKER_ERROR_MESSAGE}}}',
`${connectionMessage} <strong>${errorMessages.submissionFailure}</strong>`
);

body = injectNavigationRequestProperties(
body,
event.request,
response
);

thelovekesh marked this conversation as resolved.
Show resolved Hide resolved
return new Response(body, init);
});
});
Expand Down
48 changes: 35 additions & 13 deletions wp-includes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,37 @@ function wp_service_worker_error_message_placeholder() {
* @since 0.7
*/
function wp_service_worker_offline_page_reload() {
if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
return;
}

if ( ! is_offline() && ! is_500() ) {
return;
}

?>
<script id="wp-navigation-request-properties" type="application/json">{{{WP_NAVIGATION_REQUEST_PROPERTIES}}}</script><?php // phpcs:ignore WordPressVIPMinimum.Security.Mustache.OutputNotation ?>
<script type="module">
if (!new URLSearchParams(location.search.substr(1)).has("wp_error_template")) {
const shouldRetry = () => {
if (
new URLSearchParams(location.search.substring(1)).has(
'wp_error_template'
)
) {
return false;
}

const navigationRequestProperties = JSON.parse(
document.getElementById('wp-navigation-request-properties').text
);
if ('GET' !== navigationRequestProperties.method) {
return false;
}

return true;
};

if (shouldRetry()) {
/**
* Listen to changes in the network state, reload when online.
* This handles the case when the device is completely offline.
*/
* Listen to changes in the network state, reload when online.
* This handles the case when the device is completely offline.
*/
window.addEventListener('online', () => {
window.location.reload();
});
Expand All @@ -201,12 +217,14 @@ function wp_service_worker_offline_page_reload() {
let count = 0;

/**
* Check if the server is responding and reload the page if it is.
* This handles the case when the device is online, but the server is offline or misbehaving.
*/
* Check if the server is responding and reload the page if it is.
* This handles the case when the device is online, but the server is offline or misbehaving.
*/
async function checkNetworkAndReload() {
try {
const response = await fetch(location.href, {method: 'HEAD'});
const response = await fetch(location.href, {
method: 'HEAD',
});
// Verify we get a valid response from the server
if (response.status >= 200 && response.status < 500) {
window.location.reload();
Expand All @@ -215,8 +233,12 @@ function wp_service_worker_offline_page_reload() {
} catch {
// Unable to connect so do nothing.
}
window.setTimeout(checkNetworkAndReload, Math.pow(2, count++) * 2500);
window.setTimeout(
checkNetworkAndReload,
Math.pow(2, count++) * 2500
);
}

checkNetworkAndReload();
}
</script>
Expand Down