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/hgText/hgText.c src/hg/hgText/hgText.c
index a858e475c17..91043c78128 100644
--- src/hg/hgText/hgText.c
+++ src/hg/hgText/hgText.c
@@ -3111,31 +3111,31 @@
cgiMakeButton("phase", outputOptionsPhase);
printf("</FORM>\n");
webEnd();
}
void doTabSeparatedCT(boolean allFields)
{
struct bed *bedList;
char *table = getTableName();
struct customTrack *ct = lookupCt(table);
struct slName *chosenFields;
struct bedFilter *bf;
boolean gotResults;
-printf("Content-Type: text/plain\n\n");
+cgiPrintContentType("text/plain");
webStartText();
checkTableExists(fullTableName);
bf = constrainBedFields(NULL);
chosenFields = getChosenFields(allFields);
if (chosenFields == NULL)
{
printf("\n# Error: at least one field must be selected.\n\n");
return;
}
if (allGenome)
bedList = cloneBedList(ct->bedList);
else
bedList = bedFilterListInRange(ct->bedList, bf, chrom, winStart, winEnd);
@@ -3160,31 +3160,31 @@
char *constraints;
boolean gotResults;
checkUserKeys();
saveChooseTableState();
saveChooseFieldsState();
saveOutputOptionsState();
saveIntersectOptionsState();
if (sameString(customTrackPseudoDb, db))
{
doTabSeparatedCT(allFields);
return;
}
-printf("Content-Type: text/plain\n\n");
+cgiPrintContentType("text/plain");
webStartText();
checkTableExists(fullTableName);
hti = getHti(db, table);
constraints = constrainFields(NULL);
if (allGenome)
chromList = hAllChromNames();
else
chromList = newSlName(chrom);
dyStringClear(fieldSpec);
if (allFields)
dyStringAppend(fieldSpec, "*");
else
{
@@ -3355,31 +3355,31 @@
webEnd();
}
void doGetSequence()
/* Display FASTA sequence. */
{
struct hTableInfo *hti = getOutputHti();
struct bed *bedList;
int itemCount;
saveOutputOptionsState();
saveIntersectOptionsState();
saveSequenceOptionsState();
-printf("Content-Type: text/plain\n\n");
+cgiPrintContentType("text/plain");
webStartText();
bedList = getBedList(FALSE, NULL);
itemCount = hgSeqBedDb(database, hti, bedList);
bedFreeList(&bedList);
if (itemCount == 0)
printf("\n# No results returned from query.\n\n");
}
static void addGffLineFromBed(struct gffLine **pGffList, struct bed *bed,
char *source, char *feature,
int start, int end, char frame, char *txName)
/* Create a gffLine from a bed and line-specific parameters, add to list. */
{
struct gffLine *gff;
@@ -3604,31 +3604,31 @@
struct hTableInfo *hti = getOutputHti();
struct bed *bedList;
struct gffLine *gffList, *gffPtr;
char source[64];
char *db = getTableDb();
char *track = getTrackName();
int itemCount;
// Would be nice to allow user to select this, but I don't want to
// make an options page for just one param... any others?
// ? exon / CDS ?
boolean gtf2StopCodons = FALSE;
saveOutputOptionsState();
saveIntersectOptionsState();
-printf("Content-Type: text/plain\n\n");
+cgiPrintContentType("text/plain");
webStartText();
bedList = getBedList(FALSE, NULL);
if (sameString(customTrackPseudoDb, db))
snprintf(source, sizeof(source), "%s", track);
else
snprintf(source, sizeof(source), "%s_%s", db, track);
itemCount = 0;
gffList = bedToGffLines(bedList, hti, source, gtf2StopCodons);
bedFreeList(&bedList);
for (gffPtr = gffList; gffPtr != NULL; gffPtr = gffPtr->next)
{
gffTabOut(gffPtr, stdout);
itemCount++;
}
@@ -3767,31 +3767,31 @@
char *ctName = cgiUsualString("tbCtName", table);
char *ctDesc = cgiUsualString("tbCtDesc", table);
char *ctVis = cgiUsualString("tbCtVis", "dense");
char *ctUrl = cgiUsualString("tbCtUrl", "");
char *fbQual = fbOptionsToQualifier();
char fbTQ[128];
int fields;
boolean gotResults = FALSE;
saveOutputOptionsState();
saveIntersectOptionsState();
saveBedCtOptionsState();
if (! doCt)
{
- printf("Content-Type: text/plain\n\n");
+ cgiPrintContentType("text/plain");
webStartText();
}
bedList = getBedList(FALSE, NULL);
fields = hTableInfoBedFieldCount(hti);
if (doCtHdr && (bedList != NULL))
{
int visNum = (int) hTvFromStringNoAbort(ctVis);
if (visNum < 0)
visNum = 0;
if (doCt)
ctNew = newCT(ctName, ctDesc, visNum, ctUrl, fields);
else