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/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c index 09bf1f9faa7..9c1893a16d8 100644 --- src/hg/hgTracks/hgTracks.c +++ src/hg/hgTracks/hgTracks.c @@ -6137,63 +6137,66 @@ hvGfxClose(&hvg); if (measureTiming) measureTime("Time completed writing trash hgt png image file"); #ifdef SUPPORT_CONTENT_TYPE char *type = cartUsualString(cart, "hgt.contentType", "html"); if(sameString(type, "jsonp")) { struct jsonElement *json = newJsonObject(newHash(8)); char *jsonp = cartString(cart, "jsonp"); // This path only ever emits a wrapped response, so there is no bare form to // fall back to: reject an invalid callback name outright. if (!isValidJsonpCallback(jsonp)) errAbort("invalid callback"); - printf("Content-Type: application/json\n\n"); + cgiPrintContentType("application/json"); errAbortSetDoContentType(FALSE); jsonObjectAdd(json, "track", newJsonString(cartString(cart, "hgt.trackNameFilter"))); jsonObjectAdd(json, "height", newJsonNumber(pixHeight)); jsonObjectAdd(json, "width", newJsonNumber(pixWidth)); jsonObjectAdd(json, "img", newJsonString(pngTn.forHtml)); printf("%s(", jsonp); hPrintEnable(); jsonPrint((struct jsonElement *) json, NULL, 0); hPrintDisable(); printf(")\n"); return; } else if(sameString(type, "png") || sameString(type, "pdf") || sameString(type, "eps")) { // following code bypasses html and return png's directly - see redmine 4888 // NB: Pretty sure the pdf and eps options here are never invoked. I don't see any // calls that would activate eps output, and pdf is locked behind an unused ifdef char *file; if(sameString(type, "pdf")) { - printf("Content-Disposition: filename=hgTracks.pdf\nContent-Type: application/pdf\n\n"); + printf("Content-Disposition: filename=hgTracks.pdf\n"); + cgiPrintContentType("application/pdf"); file = convertEpsToPdf(psOutput); unlink(psOutput); } else if(sameString(type, "eps")) { - printf("Content-Disposition: filename=hgTracks.eps\nContent-Type: application/eps\n\n"); + printf("Content-Disposition: filename=hgTracks.eps\n"); + cgiPrintContentType("application/eps"); file = psOutput; } else { - printf("Content-Disposition: filename=hgTracks.png\nContent-Type: image/png\n\n"); + printf("Content-Disposition: filename=hgTracks.png\n"); + cgiPrintContentType("image/png"); file = pngTn.forCgi; } char buf[4096]; FILE *fd = fopen(file, "r"); if(fd == NULL) // fail some other way (e.g. HTTP 500)? errAbort("Couldn't open png for reading"); while (TRUE) { size_t n = fread(buf, 1, sizeof(buf), fd); if(n) fwrite(buf, 1, n, stdout); else break; @@ -9306,31 +9309,31 @@ if (hTrackOnChrom(track->tdb, w->chromName)) hideIt = FALSE; } if (hideIt) { track->limitedVis = tvHide; track->limitedVisSet = TRUE; } } if (cartUsualBoolean(cart, "dumpTracks", FALSE)) { struct dyString *dy = dyStringNew(1024); logTrackList(dy, trackList); - printf("Content-type: text/html\n\n"); + cgiPrintContentType("text/html"); printf("%s\n", dy->string); exit(0); } if (sameString(cfgOptionDefault("trackLog", "off"), "on")) logTrackVisibilities(cartSessionId(cart), trackList, position); struct track *visibleTracks = getVisibleTracks(trackList); if (visibleTracks) { // add these tracks to the special 'visible' group struct group *group, *visibleGroup = NULL; for (group = groupList; group != NULL; group = group->next) { if (sameString(group->name, "visible"))