cb99f0b11bdeee5dfa76064d38b6410da0f4a709 max Thu Sep 10 00:55:21 2026 -0700 Centralize CGI Content-Type printing in one cgiPrintContentType() helper Around 90 places across the tree hand-rolled the CGI response header, each with its own spelling: "Content-Type:" or "Content-type:", \n or \r\n, and the terminating blank line written as part of the same string, as a separate puts("\n") (which emits two newlines, so a stray blank line led the body) or as printf("\r\n\r\n") (two blank lines). A handful forgot the terminator entirely and relied on a following header to supply it. cgiPrintContentType() in lib/cheapcgi.c now writes the Content-Type line and the blank line that ends the header. Header lines are not ordered, so the callers that also send Status, Set-Cookie, Content-Disposition, Content-Length or X-Sendfile write those first and call this last to close the header; that keeps it to a single helper rather than a print-the-line / end-the-header pair that a caller can half-use. cart.c's existing httpHeaders list already worked this way. Only the CGI response path is touched. The dyStringPrintf("Content-type: ...") calls that build outgoing HTTP *requests* (genomeSpace, oauthLogin, eapMetaSync, edwWebAuthLogin, ga4ghToBed) are unrelated and left alone. Also fills out the apiKey error message in botDelay.c to say where to create a key and that keys are server-specific. No behavior change on the wire beyond dropping those stray blank lines and adding the missing newline after Retry-After. diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c index c852a23a148..f38e4fb47ba 100644 --- src/hg/hgTrackUi/hgTrackUi.c +++ src/hg/hgTrackUi/hgTrackUi.c @@ -3855,44 +3855,47 @@ // NOTE: Currently only composite multi-view tracks because // reset relies upon all cart vars following naming convention: // {track}.{varName}... ( One exception supported: {track}_sel ). if (trackDbLocalSetting(tdb, "container")) { /* For the moment, be a composite... */ tdbMarkAsComposite(tdb); } if (ajax && cartOptionalString(cart, "descriptionOnly")) { char *liftDb = cloneString(trackDbSetting(tdb, "quickLiftDb")); if (liftDb) tdb->html = getTrackHtml(liftDb, tdb->table); + // resolve $hgsid, and for a hub the rest of its description page variables + hVarSubstTrackDbHtml(cart, tdb, database); //struct trackDb *tdbParent = tdbFillInAncestry(cartString(cart, "db"),tdb); if (tdb->html != NULL && tdb->html[0] != 0) { printRelatedTracks(database,trackHash,tdb,cart); puts(tdb->html); } else { struct trackDb *tdbParent = tdb->parent; for (;tdbParent && (tdbParent->html == NULL || tdbParent->html[0] == 0); tdbParent = tdbParent->parent ) ; // Get the first parent that has html if (tdbParent != NULL && tdbParent->html != NULL && tdbParent->html[0]) { + hVarSubstTrackDbHtml(cart, tdbParent, database); printf("

Retrieved from %s Track...

\n", COLOR_DARKGREEN,tdbParent->shortLabel); printRelatedTracks(database,trackHash,tdb,cart); puts(tdbParent->html); } else printf("

No description found for: %s.

",tdbParent?tdbParent->track:tdb->track); } cartRemove(cart,"descriptionOnly"); // This is a once only request and should be deleted return; } if (tdbIsContainer(tdb) || tdbIsSuperTrack(tdb)) { safef(setting,sizeof(setting),"%s.%s",tdb->track,RESET_TO_DEFAULTS); // NOTE: if you want track vis to not be reset, move to after vis dropdown @@ -4285,30 +4288,32 @@ char *genome = hGenome(database); char *desc = hFreezeDateOpt(database); printf("Assembly: %s %s
", genome, desc); /* Print lift information from trackDb, if any */ trackDbPrintOrigAssembly(tdb, database); printUpdateTime(database, tdb, NULL); } char *liftDb = cloneString(trackDbSetting(tdb, "quickLiftDb")); // quickLiftChain has static html if (liftDb && differentString(trackHubSkipHubName(tdb->track), "quickLiftChain")) tdb->html = getTrackHtml(liftDb, tdb->table); +// resolve $hgsid, and for a hub the rest of its description page variables +hVarSubstTrackDbHtml(cart, tdb, database); if (tdb->html != NULL && tdb->html[0] != 0) { char *browserVersion; if (btIE == cgiClientBrowser(&browserVersion, NULL, NULL) && *browserVersion < '8') htmlHorizontalLine(); else // Move line down, since

Description (in ->html) is proceded by too much space printf("
"); printf("
"); puts(""); // include anchor for Description link // Add pennantIcon printPennantIconNote(tdb); char *html = tdb->html; @@ -4487,60 +4492,60 @@ puts("Status: 400 Bad Request"); errAbort("Supplied fileUrl does not match any connected hubs or track settings."); } // By now we know that fileUrl points to something valid to fetch and return to the user. // Now we just have to fetch the file contents and retransmit it. int timeout = cartUsualInt(cart, "udcTimeout", 300); if (udcCacheTimeout() < timeout) udcSetCacheTimeout(timeout); struct udcFile *udc = udcFileMayOpen(fileUrl, NULL); if (udc == NULL) { puts("Status: 404 Not Found"); - puts("Content-Type: text/plain\n"); + cgiPrintContentType("text/plain"); printf("Error: could not open %s\n", fileUrl); freeMem(fileUrl); return; } char maxAge[1024]; safef(maxAge, sizeof(maxAge), "max-age=%d", timeout); printf("Cache-Control: %s\n", maxAge); // See if we're getting a "has it changed" request. // If so, return a 304 if nothing changed. char etag[1024]; time_t mtime = udcUpdateTime(udc); safef(etag, sizeof(etag), "\"%ld\"", mtime); printf("ETag: %s\n", etag); udcFileClose(&udc); char *ifNone = getenv("HTTP_IF_NONE_MATCH"); if (isNotEmpty(ifNone)) { if (sameStringN(etag, ifNone, strlen(etag)-1)) // Apache can add -gzip to etags during transmission { puts("Status: 304 Not Modified\n"); freeMem(fileUrl); return; } } -puts("Content-Type: text/plain\n"); +cgiPrintContentType("text/plain"); char *content = udcFileReadAll(fileUrl, NULL, 0, NULL); puts(content); freeMem(content); freeMem(fileUrl); } void doMiddle(struct cart *theCart) /* Write body of web page. */ { boolean isFileFetch = isNotEmpty(cartOptionalString(theCart, "fileUrl")); if (isFileFetch) { handleFileFetch(theCart); // file fetch workaround for CORS issues return;