cf9f4cb7f55c7beb8ad5f11118656a60770a71a5
max
  Thu Sep 10 01:02:36 2026 -0700
Move the extra-HTTP-header list into cheapcgi, and write the header only once

Follow-on to the cgiPrintContentType() refactor.

cart.c owned the mechanism for adding headers ahead of the content type: a
global slPair list plus addHttpHeaders() to print it.  That put it in hg/lib,
out of reach of the CGIs and library code that do not use a cart, even though
nothing about it is cart-specific.  It now lives next to cgiPrintContentType()
in lib/cheapcgi.c, behind cgiAddHttpHeader(name, value) instead of a bare
global, and cgiPrintContentType() writes the queued headers itself.  The one
caller, hgTracks/mainMain.c, reads the same but no longer reaches into cart.h
for it.  cspWriteResponseHeader() stays in hg/lib where it belongs, since it
needs hg.conf; cartWriteHeaderAndCont() calls it directly now, the way the
other ten callers already do.

cgiPrintContentType() also writes at most once per process now.  A second
content type cannot reach the browser as a header - it lands in the page body
as text - so the later caller is always the mistaken one.  cart.c had a private
cartDidContentType flag for exactly this, covering only the flows that went
through the cart; the guard is now in the one function every flow shares, and
cartDidContentType is gone.  Its public equivalent, cgiDidContentType(), is
what cartWriteHeaderAndCont() checks so it does not write a second cookie.

Verified: make libs, make cgi and the lib test suite are clean, hgTracks still
emits Cache-Control: no-store, and hgTracks, hgc and hgTables each emit exactly
one Content-Type on both their html and their text paths.

diff --git src/hg/inc/cart.h src/hg/inc/cart.h
index 4400a6c77eb..72ff27cc422 100644
--- src/hg/inc/cart.h
+++ src/hg/inc/cart.h
@@ -12,33 +12,30 @@
 
 #include "jksql.h"
 #include "errAbort.h"
 #include "dystring.h"
 #include "linefile.h"
 #include "trackDb.h"
 
 #define namedSessionDbTableConfVariable    "namedSessionDbName"
 #define defaultNamedSessionDb              "namedSessionDb"
 
 // If cgi set as CART_VAR_EMPTY, then removed from cart
 // If If cgi created new and oldVars are stored, then will be CART_VAR_EMPTY in old vars
 #define CART_VAR_EMPTY "[]"
 #define IS_CART_VAR_EMPTY(var) ((var) == NULL || sameString(var,CART_VAR_EMPTY))
 
-// A list of headers each CGI can use to control their own HTTP headers
-extern struct slPair *httpHeaders;
-
 typedef struct sqlConnection *(*DbConnector)();
 /* funtion type used to get a connection to database */
 
 typedef void (*DbDisconnect)(struct sqlConnection **pConn);
 /* function type used to cleanup a connection from database */
 
 
 struct cart
 /* A cart of settings that persist. */
    {
    struct cart *next;	/* Next in list. */
    char *userId;	/* User ID in database. */
    char *sessionId;	/* Session ID in database. */
    struct hash *hash;	/* String valued hash. */
    struct hash *exclude;	/* Null valued hash of variables not to save. */