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 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(""); p->cdwLocalFunction = &cdwHeadTagDependencies; slAddHead(&patcherList, p); AllocVar(p); p->match = cloneString(""); 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 * . * 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");