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/phyloPng/phyloPng.c src/hg/phyloPng/phyloPng.c
index a7fb6cdaff9..38080002361 100644
--- src/hg/phyloPng/phyloPng.c
+++ src/hg/phyloPng/phyloPng.c
@@ -368,35 +368,31 @@
     /* this will cause it to kick out the set-cookie: http response header line */
     cart = cartAndCookieNoContent(hUserCookie(), excludeVars, oldVars);
     }
 else    
     {
     if (!cgiOptionalString("phyloPng_tree"))
     	usage("-phyloPng_tree is a required 'option' or cgi variable.");
     }
 
 useCart = (!cgiOptionalString("phyloPng_tree") || cgiVarExists("phyloPng_restore"));
 
 htmlPageWrapper = cgiVarExists("phyloPng_htmlPage"); /* wrap output in a page */
 
 if (onWeb && sameString(getenv("REQUEST_METHOD"),"HEAD"))
     { /* tell browser it's static just so it can save it */
-    if (htmlPageWrapper)
-    	printf("Content-type: text/html\r\n");
-    else
-    	printf("Content-type: image/png\r\n");
-    printf("\r\n");
+    cgiPrintContentType(htmlPageWrapper ? "text/html" : "image/png");
     return 0;
     }
 
 if (useCart)
     {
     width = cartUsualInt(cart,"phyloPng_width",width);    
     height = cartUsualInt(cart,"phyloPng_height",height);    
     phyloData = cloneString(cartOptionalString(cart,"phyloPng_tree"));
     branchLengths = cartVarExists(cart,"phyloPng_branchLengths");
     lengthLegend = cartVarExists(cart,"phyloPng_lengthLegend");
     branchLabels = cartVarExists(cart,"phyloPng_branchLabels");
     branchDecimals = cartUsualInt(cart,"phyloPng_branchDecimals", branchDecimals);
     branchMultiplier = cartUsualInt(cart,"phyloPng_branchMultiplier", branchMultiplier);
     stripUnderscoreSuff = cartVarExists(cart,"phyloPng_undersuff_strip");
     dashToSpace = cartVarExists(cart,"phyloPng_dash_to_space");
@@ -411,32 +407,31 @@
     branchLengths = cgiVarExists("phyloPng_branchLengths");
     lengthLegend = cgiVarExists("phyloPng_lengthLegend");
     branchLabels = cgiVarExists("phyloPng_branchLabels");
     branchDecimals = cgiUsualInt("phyloPng_branchDecimals", branchDecimals);
     branchMultiplier = cgiUsualInt("phyloPng_branchMultiplier", branchMultiplier);
     stripUnderscoreSuff = cgiVarExists("phyloPng_undersuff_strip");
     dashToSpace = cgiVarExists("phyloPng_dash_to_space");
     underToSpace = cgiVarExists("phyloPng_under_to_space");
     monospace = cgiVarExists("phyloPng_monospace");
     }
     
 if (useCart)
     {
     if (onWeb)
 	{
-    	printf("Content-type: text/html\r\n");
-	printf("\r\n");
+    	cgiPrintContentType("text/html");
 	cartWebStart(cart, NULL, "%s", "phyloPng Interactive Phylogenetic Tree Png Maker");
 
 	if (isMSIE)  /* cannot handle long urls */
 	    puts("<form method=\"POST\" action=\"phyloPng\" name=\"mainForm\">");
 	else
 	    puts("<form method=\"GET\" action=\"phyloPng\" name=\"mainForm\">");
 
 	cartSaveSession(cart);
 	puts("<table>");
 	puts("<tr><td>Width:</td><td>"); cartMakeIntVar(cart, "phyloPng_width", width, 4); puts("</td></tr>");
 	puts("<tr><td>Height:</td><td>"); cartMakeIntVar(cart, "phyloPng_height", height, 4); puts("</td></tr>");
 	puts("<tr><td>Use branch lengths?</td><td>"); cartMakeCheckBox(cart, "phyloPng_branchLengths", branchLengths); puts("</td></tr>");
 	puts("<tr><td>&nbsp; Show length ruler?</td><td>"); cartMakeCheckBox(cart, "phyloPng_lengthLegend", lengthLegend); puts("</td></tr>");
 	puts("<tr><td>&nbsp; Show length values?</td><td>"); cartMakeCheckBox(cart, "phyloPng_branchLabels", branchLabels); puts("</td></tr>");
 	puts("<tr><td>&nbsp; How many decimal places?</td><td>"); cartMakeIntVar(cart, "phyloPng_branchDecimals", branchDecimals,1); puts("</td></tr>");
@@ -537,32 +532,31 @@
 "to two or three figures.<br>\n"
 "<br>\n"
 "6. Wrap-in-html is useful when the browser automatically shrinks a large image.\n"
 "This option keeps the image view full in the browser automatically.\n"
 "<br>"
 	    );
 	cartWebEnd();
 	return 0;
 	}
     else	    
     	usage("-phyloPng_tree is a required 'option' or cgi variable.");
     }
 
 if (htmlPageWrapper)
     {
-    printf("Content-type: text/html\r\n");
-    printf("\r\n");
+    cgiPrintContentType("text/html");
     printf("<html><head>\n%s<title>Phylogenetic Tree</title></head><body>",getCspMetaHeader());
     printf("<IMAGE SRC=\"http://%s%s"
 	    "?phyloPng_width=%d"
 	    "&phyloPng_height=%d"
 	    "&phyloPng_tree=%s"
 	,getenv("SERVER_NAME"),getenv("SCRIPT_NAME"),width,height,cgiEncode(phyloData)); // tree into URL, cgiEncode (XSS)
     if (branchLengths)
 	printf("&phyloPng_branchLengths=1");
     if (lengthLegend)
 	printf("&phyloPng_lengthLegend=1");
     if (branchLabels)
 	printf("&phyloPng_branchLabels=1");
     printf("&phyloPng_branchDecimals=%d",branchDecimals);
     printf("&phyloPng_branchMultipliers=%d",branchMultiplier);
     if (stripUnderscoreSuff)
@@ -616,32 +610,31 @@
 char *errMsg = NULL;
 if (errCatchStart(errCatch))
     {
     phyloTree = phyloParseString(phyloData);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     errMsg = cloneString(errCatch->message->string);
     }
 errCatchFree(&errCatch);
 if (errMsg)
     {
     if (onWeb)
         {
-        printf("Content-type: text/html\r\n");
-        printf("\r\n");
+        cgiPrintContentType("text/html");
         printf("<html><head>\n%s<title>PhyloTree parse error</title></head><body><pre>",getCspMetaHeader());
         /* we dont think the specific error message coming back are correct or useful
         * so supply a generic err msg */
         htmlPrintf("Original input tree:\n[%s]\n\n",cgiString("phyloPng_tree"));
         htmlPrintf("Input tree as passed to parser:\n[%s]\n\n",phyloData);
         printf("Parser syntax error:\n%s",errMsg);
         puts("</pre></body></html>");
         }
     else
 	{
 	warn("%s", errMsg);
 	}
     freez(&errMsg);    
     freez(&phyloData);
     return 0;
@@ -663,32 +656,31 @@
     mgClearPixels(mg);
 
     lengthLegend = lengthLegend && branchLengths;  /* moot without lengths */
     if (lengthLegend)
 	{
 	int fHeight = mgFontPixelHeight(font);
 	height -= (MARGIN+2*fHeight);
 	}
 
     phyloTreeLayoutBL(phyloTree, &maxDepth, &numLeafs, 0, font, &maxLabelWidth, width, &minMaxFactor, 0.0);
     
     if (layoutErrMsg[0] != 0)
 	{
 	if (onWeb)
 	    {
-	    printf("Content-type: text/html\r\n");
-	    printf("\r\n");
+	    cgiPrintContentType("text/html");
 	    printf("<html><head>\n%s<title>PhyloTree error</title></head><body><pre>",getCspMetaHeader());
 	    printf("input tree: [%s]\n\n%s",cgiString("phyloPng_tree"),layoutErrMsg);
 	    puts("</pre></body></html>");
 	    }
 	else
 	    {
 	    warn("%s", layoutErrMsg);
 	    }
 	freez(&phyloData);
 	mgFree(&mg);
 	return 0;
 	}
 
     if (branchLengths)
         phyloTreePngBL(phyloTree, maxDepth, numLeafs, maxLabelWidth, width, height, 
@@ -726,32 +718,31 @@
 		{
 		dh = fHeight / 4;
 		}
 	    mgDrawLine(mg, MARGIN+x, height+fHeight/2-dh, 
 	     		   MARGIN+x, height+fHeight/2+dh, MG_BLACK);
 	    ++i;
 	    }
 
 	
 	}
 
     }
 
 if (onWeb)
     {
-    printf("Content-type: image/png\r\n");
-    printf("\r\n");
+    cgiPrintContentType("image/png");
     }
 
 if (!mgSaveToPng(stdout, mg, FALSE))
     {
     errAbort("Couldn't save png to stdout");
     }
 
 if (cgiOptionalString("phyloPng_submit"))
     cartCheckout(&cart);
 
 /* there's no code for freeing the phyloTree yet in phyloTree.c */
 
 mgFree(&mg);
 freez(&phyloData);