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");