<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[55968] trunk: Administration: Add the `no-store` and `private` directives to the `Cache-Control` header when preventing caching for logged in users.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/55968">55968</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/55968","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>johnbillion</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-06-21 18:25:40 +0000 (Wed, 21 Jun 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Administration: Add the `no-store` and `private` directives to the `Cache-Control` header when preventing caching for logged in users.

The intention behind this change is to prevent sensitive data in responses for logged in users being cached and available to others, for example via the browser history after the user logs out.

The `no-store` directive instructs caches in the browser or within proxies not to store the response in the cache. This is subtly different from the `no-cache` directive which means the response can be cached but must be revalidated before re-use. WordPress does not use ETag headers by default therefore this does not achieve the same result.

The `private` directive complements the `no-store` directive by specifying that the response contains private information that should not be stored in a public cache. Som
e proxy caches may ignore the `no-store` directive but respect the `private` directive, thus it is included.

The existing `Cache-Control` header for users who are not logged in remains unchanged, and the existing cache prevention directives remain in place for backwards compatib
ility.

Props soulseekah, luehrsen, Dharm1025, markdoliner, rutviksavsani, ayeshrajans, paulkevan, clorith, andy786, johnbillion

Fixes <a href="https://core.trac.wordpress.org/ticket/21938">#21938</a>, Fixes <a href="https://core.trac.wordpress.org/ticket/57627">#57627</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesfunctionsphp">trunk/src/wp-includes/functions.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestse2especscachecontrolheadersdirectivestestjs">trunk/tests/e2e/specs/cache-control-headers-directives.test.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/functions.php       2023-06-21 17:44:40 UTC (rev 55967)
+++ trunk/src/wp-includes/functions.php 2023-06-21 18:25:40 UTC (rev 55968)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1477,24 +1477,30 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Gets the header information to prevent caching.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Gets the HTTP header information to prevent caching.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * The several different headers cover the different ways cache prevention
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * is handled by different browsers
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * is handled by different browsers.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.8.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.3.0 The `Cache-Control` header for logged in users now includes the
+ *              `no-store` and `private` directives.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @return array The associative array of header names and field values.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function wp_get_nocache_headers() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $cache_control = ( function_exists( 'is_user_logged_in' ) && is_user_logged_in() )
+               ? 'no-cache, must-revalidate, max-age=0, no-store, private'
+               : 'no-cache, must-revalidate, max-age=0';
+
</ins><span class="cx" style="display: block; padding: 0 10px">         $headers = array(
</span><span class="cx" style="display: block; padding: 0 10px">                'Expires'       => 'Wed, 11 Jan 1984 05:00:00 GMT',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                'Cache-Control' => 'no-cache, must-revalidate, max-age=0',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         'Cache-Control' => $cache_control,
</ins><span class="cx" style="display: block; padding: 0 10px">         );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( function_exists( 'apply_filters' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * Filters the cache-controlling headers.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * Filters the cache-controlling HTTP headers that are used to prevent caching.
</ins><span class="cx" style="display: block; padding: 0 10px">                  *
</span><span class="cx" style="display: block; padding: 0 10px">                 * @since 2.8.0
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1509,7 +1515,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Sets the headers to prevent caching for the different browsers.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Sets the HTTP headers to prevent caching for the different browsers.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * Different browsers support different nocache headers, so several
</span><span class="cx" style="display: block; padding: 0 10px">  * headers must be sent so that all of them get the point that no
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1536,7 +1542,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Sets the headers for caching for 10 days with JavaScript content type.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Sets the HTTP headers for caching for 10 days with JavaScript content type.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.1.0
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span></span></pre></div>
<a id="trunktestse2especscachecontrolheadersdirectivestestjs"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/e2e/specs/cache-control-headers-directives.test.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/e2e/specs/cache-control-headers-directives.test.js                            (rev 0)
+++ trunk/tests/e2e/specs/cache-control-headers-directives.test.js      2023-06-21 18:25:40 UTC (rev 55968)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,39 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+import {
+       visitAdminPage,
+       createNewPost,
+       trashAllPosts,
+       createURL,
+       logout,
+} from "@wordpress/e2e-test-utils";
+
+describe( 'Cache Control header directives', () => {
+
+       beforeEach( async () => {
+               await trashAllPosts();
+       } );
+
+       it( 'No private directive present in cache control when user not logged in.', async () => {
+               await createNewPost( {
+                       title: 'Hello World',
+                       post_status: 'publish',
+               } );
+               await logout();
+
+               const response = await page.goto( createURL( '/hello-world/' ) );
+               const cacheControl = response.headers();
+
+               expect( cacheControl[ 'cache-control' ] ).not.toContain( 'no-store' );
+               expect( cacheControl[ 'cache-control' ] ).not.toContain( 'private' );
+       } );
+
+       it( 'Private directive header present in cache control when logged in.', async () => {
+               await visitAdminPage( '/wp-admin' );
+
+               const response = await page.goto( createURL( '/wp-admin' ) );
+               const cacheControl = response.headers();
+
+               expect( cacheControl[ 'cache-control' ] ).toContain( 'no-store' );
+               expect( cacheControl[ 'cache-control' ] ).toContain( 'private' );
+       } );
+
+} );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/e2e/specs/cache-control-headers-directives.test.js
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>