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/hgMenubar/hgMenubar.c src/hg/hgMenubar/hgMenubar.c index 4e2a59c770e..32ed85457c2 100644 --- src/hg/hgMenubar/hgMenubar.c +++ src/hg/hgMenubar/hgMenubar.c @@ -1,204 +1,204 @@ /* * This CGI is used by static html pages to show a menu bar. * On an Apache with activated SSI, a html statement like * * will include the menu bar into a static page. */ #include "common.h" #include "cheapcgi.h" #include "dystring.h" #include "filePath.h" #include "linefile.h" #include "jsHelper.h" #include "wikiLink.h" #include "portable.h" #define CGI_NAME "cgi-bin/hgMenubar" #define NAVBAR_INC_PATH "/inc/globalNavBar.inc" #define NAVBAR_INC_DIR "/inc/" /* the only directory an incFile value may name, refs #38052 */ #define OLD_HREF "href=\"../" char* errMessage; static char *pageReturnUrl(char *pagePath) /* Return the CGI-encoded URL of the static page this menu bar is included into, to hand to * hgLogin as its returnto so login and logout come back to that page (refs #38192). NULL if * we cannot build one hgLogin would accept, and then the links keep their old hgSession * target. pagePath is the path part, from REDIRECT_URL or DOCUMENT_URI. */ { if (isEmpty(pagePath)) return NULL; struct dyString *dy = dyStringNew(256); dyStringPrintf(dy, "http%s://%s%s", cgiAppendSForHttps(), cgiServerNamePort(), pagePath); char *encoded = wikiLinkEncodePageReturnUrl(dy->string); dyStringFree(&dy); return encoded; } char *loginLinkHtml(char *pagePath) /* Return HTML
  • for the top-right Login menu item, or "" if no login system is configured. * Static-page variant: all of the URLs come from wikiLink and are absolute, so the caller's * OLD_HREF substitution leaves them alone. topLinks.js turns the logged-in item into a * dialog. */ { if (!(loginSystemEnabled() || wikiLinkEnabled())) return cloneString(""); struct dyString *dy = dyStringNew(512); char *userName = wikiLinkUserName(); // There is no hgsid on a static page, so the return URL carries none either char *retEnc = pageReturnUrl(pagePath); if (userName == NULL) { // Link straight to the login page (absolute URL from wikiLink), not through hgSession. char *loginUrl = retEnc ? wikiLinkUserLoginUrlReturning("", retEnc) : wikiLinkUserLoginUrl(""); dyStringPrintf(dy, "Login", loginUrl); } else { char *logoutUrl = retEnc ? wikiLinkUserLogoutUrlReturning("", retEnc) : wikiLinkUserLogoutUrl(""); char *changePwUrl = retEnc ? wikiLinkChangePasswordUrlReturning("", retEnc) : wikiLinkChangePasswordUrl(""); char *changeEmailUrl = retEnc ? wikiLinkChangeEmailUrlReturning("", retEnc) : wikiLinkChangeEmailUrl(""); char *changeRecovEmailUrl = retEnc ? wikiLinkChangeRecovEmailUrlReturning("", retEnc) : wikiLinkChangeRecovEmailUrl(""); dyStringPrintf(dy, "%s", userName, logoutUrl, changePwUrl ? changePwUrl : "", changeEmailUrl ? changeEmailUrl : "", changeRecovEmailUrl ? changeRecovEmailUrl : "", userName); } freez(&retEnc); return dyStringCannibalize(&dy); } char *incFilePath(char *cgiPath, char *filePath, char *docRoot) /* Replace CGI_NAME in cgiPath with docRoot/filePath. filePath must begin with "/" eg "/inc/..." */ { char *incPath = replaceChars(cgiPath, "/"CGI_NAME, filePath); return catTwoStrings(docRoot, incPath); } void printIncludes(char* baseDir, char *docRoot) { // Cache-buster for the menu-bar CSS/JS: append ?v= so browsers refetch these when // they change instead of serving a stale cached copy (the CGIs get this from // webTimeStampedLinkToResource, but that emits its own "../"-relative URL which is wrong for the // arbitrary-depth static pages this menu bar is included into, so we reuse just its mtime idea). // fileExists guards fileModTime, which would otherwise abort the menu bar on every static page if // a resource were missing. char jsPath[PATH_LEN], cssPath[PATH_LEN]; safef(jsPath, sizeof jsPath, "%s/js/topLinks.js", docRoot); safef(cssPath, sizeof cssPath, "%s/style/nice_menu.css", docRoot); long jsVer = fileExists(jsPath) ? (long)fileModTime(jsPath) : 0; long cssVer = fileExists(cssPath) ? (long)fileModTime(cssPath) : 0; printf ("\n"); printf ("\n", baseDir); printf ("\n", baseDir); printf("\n", baseDir); printf("\n", baseDir, jsVer); printf ("\n", baseDir, cssVer); } void printMenuBar(char *cgiPath, char *docRoot, char *pagePath, char *filePath) { char *navBarLoc = incFilePath(cgiPath, filePath, docRoot); struct lineFile *menuFile = lineFileOpen(navBarLoc, TRUE); char* oldLine = NULL; int lineSize = 0; char *cgiContainerPath = replaceChars(cgiPath, CGI_NAME, ""); char *newPath = makeRelativePath(pagePath, cgiContainerPath); char *newHref = catTwoStrings("href=\"", newPath); -printf ("Content-type: text/html\r\n\r\n"); +cgiPrintContentType("text/html"); if (sameString(filePath, NAVBAR_INC_PATH)) printIncludes(newPath, docRoot); while (lineFileNext(menuFile, &oldLine, &lineSize)) { // Not quite as robust as perl search and replace - no variable whitespace handling // Also lots of memory leakage - every line is reallocated and forgotten char *line = oldLine; // Fill the top-right link placeholders. Login shows the user or a link to hgSession; // the Share-a-link button is browser-only, so it is dropped on static pages. if (stringIn("", line)) line = replaceChars(line, "", loginLinkHtml(pagePath)); if (stringIn("", line)) line = replaceChars(line, "", ""); char *newLine = replaceChars(line, OLD_HREF, newHref); printf("%s\n", newLine); } lineFileClose(&menuFile); // links to hgTracks need to use the web browser width and set the hgTracks image // size in pixels correctly to match the hgGateway "GO" button jsInline("$(\"#tools1 ul li a\").each( function (a) {\n" " if (this.href && this.href.indexOf(\"hgTracks\") !== -1) {\n" " var obj = this;\n" " obj.onclick = function(e) {\n" " var pix = calculateHgTracksWidth();\n" " e.currentTarget.href += \"&pix=\" + pix;\n" " }\n" " }\n" "});\n"); // if the user has previously searched for assemblies, add them to the "Genomes" menu heading, // above the "other" assemblies link jsInline("addRecentGenomesToMenuBar();\n"); jsInlineFinish(); } void parseEnvOrDie (char **cgiPath, char** docRoot, char** pagePath) { *cgiPath = getenv("SCRIPT_NAME"); *docRoot = getenv("DOCUMENT_ROOT"); *pagePath = getenv("REDIRECT_URL"); if (*pagePath == NULL) *pagePath = getenv("DOCUMENT_URI"); if (*pagePath == NULL) { *pagePath = cloneString("/inc/"); errMessage = "Error: hgMenubar was run without the REDIRECT_URL or DOCUMENT_URI variable set. Looks like it wasn't run from an SSI statement. Defaulting to the 'inc/' directory, avoids errors in the Apache error log."; } if ( (*cgiPath == NULL) || (*docRoot == NULL) || (*pagePath == NULL) ) { fprintf (stderr, "Error: bad invocation of menubar\n"); exit (1); } } int main(int argc, char *argv[]) /* Process command line. */ { char *cgiPath, *docRoot, *pagePath; parseEnvOrDie(&cgiPath, &docRoot, &pagePath); cgiSpoof(&argc, argv); char *incFile = cgiUsualString("incFile", NAVBAR_INC_PATH); /* SECURITY (refs #38052): incFile names a server-side include that we open and print * line by line, so a request must not be able to point it wherever it likes. The path * is built as docRoot + incFile with no traversal stripping, so a value like * "/../../../../etc/passwd" would echo any file the server can read. Require the known * include directory and no "..". Fall back to the normal menu bar on anything else * rather than aborting, because this CGI is included into every static page and an * abort would break the page instead of just ignoring a bad parameter. */ if (!startsWith(NAVBAR_INC_DIR, incFile) || stringIn("..", incFile) != NULL) { fprintf(stderr, "hgMenubar: ignoring unexpected incFile value [%s]\n", incFile); incFile = NAVBAR_INC_PATH; } printMenuBar(cgiPath, docRoot, pagePath, incFile); if (errMessage) puts(errMessage); return 0; }