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/inc/cheapcgi.h src/inc/cheapcgi.h index a2400f88e4c..8c49a65ff8d 100644 --- src/inc/cheapcgi.h +++ src/inc/cheapcgi.h @@ -112,35 +112,45 @@ /* Free up resources associated with dictionary. */ void cgiDictionaryFreeList(struct cgiDictionary **pList); /* Free up a whole list of cgiDictionaries */ struct cgiDictionary *cgiDictionaryFromEncodedString(char *encodedString); /* Giving a this=that&this=that string, return cgiDictionary parsed out from it. * This does *not* destroy input like the lower level cgiParse functions do. */ char *findCookieData(char *varName); /* Get the string associated with varName from the cookie string. */ void dumpCookieList(); /* Print out the cookie list. */ +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. */ + +boolean cgiDidContentType(); +/* Return TRUE if the CGI response header has already been written. */ + 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. */ boolean cgiIsOnWeb(); /* Return TRUE if looks like we're being run as a CGI. */ char *cgiRequestMethod(); /* Return CGI REQUEST_METHOD (such as 'GET/POST/PUT/DELETE/HEAD') */ char *cgiRequestUri(); /* Return CGI REQUEST_URI */ char *cgiRequestContentLength(); /* Return HTTP REQUEST CONTENT_LENGTH if available*/ char *cgiScriptName(); /* Return name of script so libs can do context-sensitive stuff. */