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("<h2 style='color:%s'>Retrieved from %s Track...</h2>\n",
COLOR_DARKGREEN,tdbParent->shortLabel);
printRelatedTracks(database,trackHash,tdb,cart);
puts(tdbParent->html);
}
else
printf("<h2>No description found for: %s.</h2>",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("<b>Assembly:</b> %s %s<br>", 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 <H2>Description (in ->html) is proceded by too much space
printf("<HR ALIGN='bottom' style='position:relative; top:1em;'>");
printf("<table class='windowSize'><tr valign='top'><td rowspan=2>");
puts("<A NAME='TRACK_HTML'></A>"); // 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;