75593a4e317eac40b781c83718961f0eb7f84f3a
max
  Thu Aug 27 06:32:34 2026 -0700
Blue bar login and logout now return to the page they were clicked on

#Preview2 week - bugs introduced now will need a build patch to fix
The Login link in the blue bar sent everyone to the My Sessions page after
they signed in, and the Sign out and account links in the logged-in dialog
did the same, so a visitor reading a help page or looking at an item details
page lost their place. Only hgTracks was handled, as a special case.

Adds wikiLinkEncodeCurrentPageReturnUrl(), which builds a returnto for the
page the CGI is currently serving, including its query string, since the
track and item parameters of pages like hgTrackUi and hgc are not all kept in
the cart. hgTracks keeps its old behaviour of returning to hgTracks?hgsid=,
because its state is in the cart and its query string can hold a one-shot
zoom or drag. hgMenubar does the same for the static pages it is included
into, using the page path from the SSI environment.

The two checks hgLogin runs on an incoming returnto (URL shape, and the
optional login.approvedReturn host list) move to wikiLink.c so that the CGIs
building a link apply the same rules before writing it. A URL hgLogin would
refuse now becomes an ordinary login link instead of an error page: an
over-long or oddly-quoted query string costs the query string, and a host
that login.approvedReturn does not cover falls back to the old My Sessions
target. Note that with login.approvedReturn set, static-page returns need
the bare host added to the list.

Also converts the fixed 2 kB buffers in the wikiLink URL builders to
dyStrings, since a cgi-encoded return URL can nearly triple in length and
safef would have aborted.

refs #38192

diff --git src/hg/hgMenubar/hgMenubar.c src/hg/hgMenubar/hgMenubar.c
index f19796e412e..7312685f8c5 100644
--- src/hg/hgMenubar/hgMenubar.c
+++ src/hg/hgMenubar/hgMenubar.c
@@ -8,60 +8,81 @@
 #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;
 
-char *loginLinkHtml()
+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 <li> for the top-right Login menu item, or "" if no login system is configured.
- * Static-page variant: the logged-out link uses href="../cgi-bin/hgSession" so the caller's
- * OLD_HREF substitution rewrites it to a page-relative path; logged-in account-dialog URLs come
- * from wikiLink and are absolute.  topLinks.js turns the logged-in item into a dialog. */
+ * 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 = wikiLinkUserLoginUrl("");
+    char *loginUrl = retEnc ? wikiLinkUserLoginUrlReturning("", retEnc)
+                            : wikiLinkUserLoginUrl("");
     dyStringPrintf(dy, "<a class='topRightLink' href=\"%s\" id='loginLink' "
         "title='Log in to save and share sessions'>Login</a>", loginUrl);
     }
 else
     {
-    // No hgsid available on static pages; the logout return URL simply omits it.
-    char *logoutUrl = wikiLinkUserLogoutUrl("");
-    char *changePwUrl = wikiLinkChangePasswordUrl("");
-    char *changeEmailUrl = wikiLinkChangeEmailUrl("");
+    char *logoutUrl = retEnc ? wikiLinkUserLogoutUrlReturning("", retEnc)
+                             : wikiLinkUserLogoutUrl("");
+    char *changePwUrl = retEnc ? wikiLinkChangePasswordUrlReturning("", retEnc)
+                               : wikiLinkChangePasswordUrl("");
+    char *changeEmailUrl = retEnc ? wikiLinkChangeEmailUrlReturning("", retEnc)
+                                  : wikiLinkChangeEmailUrl("");
     dyStringPrintf(dy, "<a class='topRightLink' href='#' id='loginLink' "
         "title='Account info and sign out' "
         "data-username=\"%s\" data-logouturl=\"%s\" data-changepwurl=\"%s\" "
         "data-changeemailurl=\"%s\">%s</a>",
         userName, logoutUrl, changePwUrl ? changePwUrl : "",
         changeEmailUrl ? changeEmailUrl : "", 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=<file mtime> 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
@@ -96,31 +117,31 @@
 char *newHref = catTwoStrings("href=\"", newPath);
 
 printf ("Content-type: text/html\r\n\r\n");
 
 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("<!-- LOGIN_LINK -->", line))
-        line = replaceChars(line, "<!-- LOGIN_LINK -->", loginLinkHtml());
+        line = replaceChars(line, "<!-- LOGIN_LINK -->", loginLinkHtml(pagePath));
     if (stringIn("<!-- SHARE_LINK -->", line))
         line = replaceChars(line, "<!-- SHARE_LINK -->", "");
     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"