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/lib/cart.c src/hg/lib/cart.c index 1af1867ceef..f631631b6a1 100644 --- src/hg/lib/cart.c +++ src/hg/lib/cart.c @@ -39,33 +39,30 @@ #include "quickLift.h" #include "pcrResult.h" #include "botDelay.h" #include "curlWrap.h" #include "hubSpaceKeys.h" #include "myVariantsShare.h" #include "customTrack.h" #include "dupTrack.h" #include "myVariants.h" static char *sessionVar = "hgsid"; /* Name of cgi variable session is stored in. */ static char *positionCgiName = "position"; DbConnector cartDefaultConnector = hConnectCart; DbDisconnect cartDefaultDisconnector = hDisconnectCart; -static boolean cartDidContentType = FALSE; - -struct slPair *httpHeaders = NULL; // A list of headers to output before the content-type static void hashUpdateDynamicVal(struct hash *hash, char *name, void *val) /* Val is a dynamically allocated (freeMem-able) entity to put * in hash. Override existing hash item with that name if any. * Otherwise make new hash item. */ { struct hashEl *hel = hashLookup(hash, name); if (hel == NULL) hashAdd(hash, name, val); else { freeMem(hel->val); hel->val = val; } } @@ -3033,77 +3030,61 @@ char *timeStr; if ( (timeStr = cgiOptionalString("_dumpCart")) != NULL) { dumpCartWithTime(cart, timeStr); exit(0); } // activate optional debuging output for CGIs verboseCgi(cgiUsualString("verbose", NULL)); cartExclude(cart, "verbose"); return cart; } -static void addHttpHeaders() -/* CGIs can initialize the global variable httpHeaders to control their own HTTP - * headers. This allows, for example, to prevent web browser caching of hgTracks - * responses, but implicitly allow web browser caching everywhere else */ -{ -struct slPair *h; -for (h = httpHeaders; h != NULL; h = h->next) - { - printf("%s: %s\n", h->name, (char *)h->val); - } -cspWriteResponseHeader(); -} - void cartWriteHeaderAndCont(struct cart* cart, char *cookieName, char *contType) /* write http headers including cookie and content type line. * contType defaults to text/html when NULL. * cookieName defaults to hUserCookie() when NULL */ { -/* The CGI header must be written exactly once; a second write lands in the page body. Some flows - * (e.g. hgc) emit it early via cartAndCookieWithHtml before a later webStart also asks for it, so - * guard here rather than trusting every caller to check cartDidContentType first. */ -if (cartDidContentType) +/* cgiPrintContentType() writes the header only once per process, so the flows that reach here + * twice (e.g. hgc emitting it early via cartAndCookieWithHtml, then webStart asking again) do + * not need to check first. Return early anyway, so we do not write a second cookie either. */ +if (cgiDidContentType()) return; -if (!contType) - contType = "text/html"; if (!cookieName) cookieName = hUserCookie(); -addHttpHeaders(); +cspWriteResponseHeader(); cartWriteCookie(cart, cookieName); cgiPrintContentType(contType); -cartDidContentType = TRUE; } struct cart *cartAndCookieWithHtml(char *cookieName, char **exclude, struct hash *oldVars, boolean doContentType) /* Load cart from cookie and session cgi variable. Write cookie * and optionally content-type part HTTP preamble to web page. Don't * write any HTML though. */ { // Note: early abort works fine but early warn does not htmlPushEarlyHandlers(); struct cart *cart = cartForSession(cookieName, exclude, oldVars); popWarnHandler(); popAbortHandler(); -if (doContentType && !cartDidContentType) +if (doContentType) cartWriteHeaderAndCont(cart, cookieName, NULL); return cart; } struct cart *cartAndCookie(char *cookieName, char **exclude, struct hash *oldVars) /* Load cart from cookie and session cgi variable. Write cookie and * content-type part HTTP preamble to web page. Don't write any HTML though. */ { return cartAndCookieWithHtml(cookieName, exclude, oldVars, TRUE); } struct cart *cartAndCookieNoContent(char *cookieName, char **exclude, struct hash *oldVars) @@ -3123,36 +3104,32 @@ if (status == 0) { doMiddle(cart); } hDumpStackPopAbortHandler(); popAbortHandler(); } void cartEarlyWarningHandler(char *format, va_list args) /* Write an error message so user can see it before page is really started. */ { static boolean initted = FALSE; va_list argscp; va_copy(argscp, args); if (!initted && !cgiOptionalString("ajax")) - { - if (!cartDidContentType) { cgiPrintContentType("text/html"); - cartDidContentType = TRUE; - } htmStart(stdout, "Early Error"); initted = TRUE; } printf("%s", htmlWarnStartPattern()); htmlVaEncodeErrorText(format,args); printf("%s", htmlWarnEndPattern()); /* write warning/error message to stderr so they get logged. */ logCgiToStderr(); vfprintf(stderr, format, argscp); va_end(argscp); putc('\n', stderr); fflush(stderr); }