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/lib/cheapcgi.c src/lib/cheapcgi.c index 715d16f803a..eb8f48a7e38 100644 --- src/lib/cheapcgi.c +++ src/lib/cheapcgi.c @@ -320,36 +320,65 @@ void dumpCookieList() /* Print out the cookie list. */ { struct cgiVar *v; for (v=cookieList; v != NULL; v = v->next) printf("%s=%s (%d)\n", v->name, v->val, v->saved); } void useTempFile() /* tell cheapcgi to use temp files */ { doUseTempFile = TRUE; } +static struct slPair *cgiExtraHeaders = NULL; /* Written ahead of the content type. */ +static boolean didContentType = FALSE; /* Has the response header been written? */ + +void cgiAddHttpHeader(char *name, char *value) +/* Add an HTTP header for cgiPrintContentType() to write ahead of the Content-Type + * line, e.g. cgiAddHttpHeader("Cache-Control", "no-store"). Both strings are + * cloned. Has no effect once the header has been written. */ +{ +slPairAdd(&cgiExtraHeaders, name, cloneString(value)); +} + +boolean cgiDidContentType() +/* Return TRUE if the CGI response header has already been written. */ +{ +return didContentType; +} + void cgiPrintContentType(char *contentType) -/* Write the CGI response header: a Content-Type line and the blank line that - * ends the header. contentType NULL means "text/html". Header lines are not - * ordered, so a CGI that also sends Status, Set-Cookie, Content-Disposition or - * the like writes those first and calls this last to close the header. */ +/* Write the CGI response header: any headers added with cgiAddHttpHeader(), a + * Content-Type line, and the blank line that ends the header. contentType NULL + * means "text/html". Header lines are not ordered, so a CGI that also sends + * Status, Set-Cookie, Content-Disposition or the like writes those first and + * calls this last to close the header. + * + * Only the first call in a process writes anything. A second header cannot + * reach the browser as a header - it lands in the page body as text - so a + * later caller is always the mistaken one, and several flows (hgc emitting the + * header early, then webStart asking again) reach here twice by design. */ { +if (didContentType) + return; +didContentType = TRUE; +struct slPair *h; +for (h = cgiExtraHeaders; h != NULL; h = h->next) + printf("%s: %s\n", h->name, (char *)h->val); if (contentType == NULL) contentType = "text/html"; printf("Content-Type: %s\n\n", contentType); } boolean cgiIsOnWeb() /* Return TRUE if looks like we're being run as a CGI. * You cannot use this in your own CGIs to determine if you're run from the command line, * as cgiFromCommandLine() will set this parameter.*/ { return getenv("REQUEST_METHOD") != NULL; } char *cgiRequestMethod()