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/cirm/cdw/cdwGetFile/cdwGetFile.c src/hg/cirm/cdw/cdwGetFile/cdwGetFile.c
index 83dbb6c7eea..6c9d03be516 100644
--- src/hg/cirm/cdw/cdwGetFile/cdwGetFile.c
+++ src/hg/cirm/cdw/cdwGetFile/cdwGetFile.c
@@ -29,31 +29,31 @@
void errExitExt(char *msg, char *field, char *status)
/* print http header + message and exit. msg can contain %s */
{
if (status)
{
printf("Status: %s\n", status);
}
else
{
// provide a generic error code when status not specified
// this will signal an error to the caller.
printf("Status: %s\n", "400 BAD REQUEST");
}
-printf("Content-Type: text/html\n\n");
+cgiPrintContentType("text/html");
puts("ERROR: ");
if (!field)
puts(msg);
else
printf(msg, field);
exit(0);
}
void errExit(char *msg, char *field)
/* print http header + message and exit. msg can contain %s */
{
errExitExt(msg, field, NULL);
}
void mustHaveAccess(struct sqlConnection *conn, struct cdwFile *ef)
@@ -72,31 +72,31 @@
}
struct patcher
/* deal with patching replacement bits in html */
{
struct patcher *next;
char *match; // string to match
int size; // string size
int count; // match count
char *(*cdwLocalFunction)(struct cart *cart, boolean makeAbsolute); // function to make
};
static void printFileReplaceVar(char *filePath)
/* dump a text file to stdout with the html header, replace <!--menuBar--> with the menubar */
{
-printf("Content-Type: text/html\n\n");
+cgiPrintContentType("text/html");
int c;
FILE *file = fopen(filePath, "r");
if (file == 0)
errExit("Cannot open file %s", filePath);
struct patcher *patcherList = NULL, *p = NULL;
AllocVar(p);
p->match = cloneString("<!--headDependencies-->");
p->cdwLocalFunction = &cdwHeadTagDependencies;
slAddHead(&patcherList, p);
AllocVar(p);
p->match = cloneString("<!--pageHeader-->");
p->cdwLocalFunction = &cdwPageHeader;
@@ -145,49 +145,48 @@
splitPath(filePath, NULL, NULL, ext);
if (strlen(ext)>1)
{
format = cloneString(ext);
format++; // skip over . character
}
}
// html files are not sent via X-Sendfile as we need to replace one special variable
if (sameWord(format, "html"))
{
printFileReplaceVar(filePath);
return;
}
// pdf, jpeg files are shown directly in the internet browser, not downloaded
-else if (sameWord(format, "jpg"))
- printf("Content-Type: image/jpeg\n");
+char *contentType = "application/octet-stream";
+if (sameWord(format, "jpg"))
+ contentType = "image/jpeg";
else if (sameWord(format, "pdf"))
- printf("Content-Type: application/pdf\n");
+ contentType = "application/pdf";
else if (sameWord(format, "png"))
- printf("Content-Type: image/png\n");
+ contentType = "image/png";
else if (sameWord(format, "json"))
- printf("Content-Type: application/json\n");
+ contentType = "application/json";
else if (sameWord(format, "text"))
- printf("Content-Type: text/plain\n");
+ contentType = "text/plain";
else
- {
printf("Content-Disposition: attachment; filename=%s\n", suggestFileName);
- printf("Content-Type: application/octet-stream\n");
- }
/* send pseudo-HTTP header to tell Apache to transfer filePath ( will honor byte range ) */
printf("Content-Length: %lld\n", (long long)fileSize(filePath));
-printf("X-Sendfile: %s\n\n", filePath);
+printf("X-Sendfile: %s\n", filePath);
+cgiPrintContentType(contentType);
}
void sendFileByAcc(struct sqlConnection *conn, char* acc, boolean useSubmitFname, char *addExt)
/* send file identified by acc (=cdwValidFile.licensePlate), suggests a canonical filename of the format
* <licensePlate>.<originalExtension>
* Example URL: http://hgwdev.soe.ucsc.edu/cgi-bin/cdwGetFile?acc=SCH000FSW *
* if useSubmitFname is TRUE, suggest the submission filename, not a canonical name.
* if addExt is not NULL, will not retrieve the file identified by accession but rather its index file,
* with the given extension (.tbi or .bai) */
{
struct cdwValidFile *vf = cdwValidFileFromLicensePlate(conn, acc);
if (vf==NULL)
errExitExt("%s is not a valid accession in the CDW.", acc, "404 Not Found");