ee89e6768015d512b5ca8db8a62e8a129dfd5c7f
braney
  Sun Aug 23 15:56:57 2026 -0700
hgSession, cartReset: send the content policy header on their own pages

Both CGIs write their own http header block, so neither reaches addHttpHeaders
in cart.c, the hook that carries the policy header.  hgSession lets each output
path write its own Content-Type, and cartReset gets its header from
htmShellWithHead.

Add the call ahead of each html Content-Type: the two main page functions in
hgSession.c, three more in backup.c, and the one in cartReset.  The json paths
and the tar download are left alone, since they print no html.

With the hg.conf option off this changes nothing.  With it on, both pages now
carry the header, and its nonce matches the one in the meta tag and on the
inline script tags.

diff --git src/hg/cartReset/cartReset.c src/hg/cartReset/cartReset.c
index 816a969180d..4999d78f151 100644
--- src/hg/cartReset/cartReset.c
+++ src/hg/cartReset/cartReset.c
@@ -1,76 +1,78 @@
 /* cartReset - Reset cart. */
 
 /* Copyright (C) 2013 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 #include "common.h"
 #include "linefile.h"
 #include "hash.h"
 #include "cheapcgi.h"
 #include "htmshell.h"
 #include "hui.h"
 #include "cart.h"
+#include "hCommon.h"
 
 
 
 static char *defaultDestination = "../cgi-bin/hgGateway";
 
 boolean problem = FALSE;
 char *destination = NULL;
 
 static void resetLocalStorage() 
 /* the cart is for configuration options that are relevant to a session. We are using more and more localStorage settings, these are
  * relevant to the particular web browser where the browser runs, e.g. notification settings, maybe one day font sizes and color schemes of the UI.
  * QA must be able to reset these, too, so do this here now */
 {
 jsInline("localStorage.clear();");
 }
 
 void doMiddle()
 /* cartReset - Reset cart. */
 {
 if (problem)
     {	
     warn("To stop Open Redirect abuse, only relative URLs are supported. "
 	   "Request for destination=[%s] rejected.\n", destination);
     }
 cartResetInDb(hUserCookie());
 resetLocalStorage();
 }
 
 int main(int argc, char *argv[])
 /* Process command line. */
 {
 long enteredMainTime = clock1000();
 struct dyString *headText = dyStringNew(512);
 destination = cgiUsualString("destination", defaultDestination);
 // Only allow relative URL that does not contain space or quote characters.
 if (strstr(destination, "//") // absolute URL
    || strchr(destination, '\'') // single quote
    || strchr(destination, '"') // double quote
    || strchr(destination, ' ') // space
    || sameString(destination, "") // empty string
     )
     {
     problem = TRUE;
     }
 
 char *csp = getCspMetaHeader();  // ContentSecurityPolicy stops XSS js in destination
 dyStringPrintf(headText, "%s",csp);
 
 if (!problem)
     {
     dyStringPrintf(headText, 
 		   "<META HTTP-EQUIV=\"REFRESH\" CONTENT=\"0;URL=%s\">"
 		   "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">"
 		   "<META HTTP-EQUIV=\"Expires\" CONTENT=\"-1\">"
 		   ,destination);
     }
 
+cspWriteResponseHeader();  // htmShellWithHead writes the http header itself, so the cart hook never runs
 htmShellWithHead("Reset Cart", headText->string, doMiddle, NULL);
 
 freeMem(csp);
 dyStringFree(&headText);
 
 cgiExitTime("cartReset", enteredMainTime);
 return 0;
 }