992880730e0f4acdc4ba941906059b0dc2e3ce1d
braney
Mon Aug 31 14:50:10 2026 -0700
hgSession: take the session description back out of the cart, refs #38205
The Sessions page has a Description box. The text stayed in the cart after
the page was done with it, so every session the user saved from then on kept
a copy, including sessions they never described. On hgwdev 41 saved sessions
carry a description that belongs to a different session.
cleanHgSessionFromCart() now removes hgsNewSessionDescription and
hgsNewSessionName. That function already runs in the two places that matter:
at the end of hgSession(), so the user's own cart is clean, and inside
saveCartAsSession() just before the cart is encoded, so no saved session holds
them. Adding the names to excludeVars[] instead would not work, because
doSessionChange() reads the description back out of the cart in the same
request that posts it.
hgsNewSessionShare has the same problem but is left alone. It is also the
only memory of the user's "Allow this session to be loaded by others" choice,
and showSavingOptions() defaults that box to checked, so removing it would
quietly re-check the box for someone who keeps their sessions private.
doNewSession() and doReSaveSession() now clone the session name before use.
Both read it straight out of the cart's hash, and the new cartRemove inside
saveCartAsSession() frees that copy while they still hold it.
diff --git src/hg/hgSession/hgSession.c src/hg/hgSession/hgSession.c
index 56c7a75a81d..8d9a2f6f8a8 100644
--- src/hg/hgSession/hgSession.c
+++ src/hg/hgSession/hgSession.c
@@ -790,30 +790,38 @@
cartRemovePrefix(cart, varName);
cartRemovePrefix(cart, hgsSharePrefix);
safef(varName, sizeof(varName), "%s%s", cgiBooleanShadowPrefix(), hgsGalleryPrefix);
cartRemovePrefix(cart, varName);
cartRemovePrefix(cart, hgsGalleryPrefix);
cartRemovePrefix(cart, hgsLoadPrefix);
cartRemovePrefix(cart, hgsEditPrefix);
cartRemovePrefix(cart, hgsLoadLocalFileName);
cartRemovePrefix(cart, hgsDeletePrefix);
cartRemovePrefix(cart, hgsShowDownloadPrefix);
cartRemovePrefix(cart, hgsMakeDownloadPrefix);
cartRemovePrefix(cart, hgsDoDownloadPrefix);
cartRemovePrefix(cart, hgsDo);
cartRemove(cart, hgsOldSessionName);
cartRemove(cart, hgsCancel);
+/* Two of the Save form's own inputs. If they stay in the cart they are stored inside every
+ * session saved afterwards, so one session's description travels in sessions that never had
+ * one. hgsNewSessionShare is left alone on purpose: it has the same problem, but it is also
+ * the only memory of the user's "Allow this session to be loaded by others" choice, and
+ * showSavingOptions() defaults that box to checked. Removing it here would quietly re-check
+ * the box for someone who keeps their sessions private. */
+cartRemove(cart, hgsNewSessionName);
+cartRemove(cart, hgsNewSessionDescription);
}
static void outIfNotPresent(struct cart *cart, struct dyString *dy, char *track, int tdbVis)
/* Output default trackDb visibility if it's not mentioned in the cart. */
{
char *cartVis = cartOptionalString(cart, track);
if (cartVis == NULL)
{
if (dy)
dyStringPrintf(dy,"&%s=%s", track, hStringFromTv(tdbVis));
else
printf("%s %s\n", track, hStringFromTv(tdbVis));
}
}
@@ -968,31 +976,32 @@
sqlUpdate(conn, dy->string);
dyStringFree(&dy);
/* Prevent modification of custom track collections or quickLifts just saved to namedSessionDb: */
cartCopyLocalHubs(cart);
return useCount;
}
char *doNewSession(char *userName)
/* Save current settings in a new named session.
* Return a message confirming what we did. */
{
if (userName == NULL)
return "Unable to save session -- please log in and try again.";
struct dyString *dyMessage = dyStringNew(2048);
-char *sessionName = trimSpaces(cartString(cart, hgsNewSessionName));
+/* Clone: saveCartAsSession() removes this cart variable, which frees the cart's own copy. */
+char *sessionName = trimSpaces(cloneString(cartString(cart, hgsNewSessionName)));
if (isEmpty(sessionName))
return "Error: Unable to save a session without a name. Please add one and try again.";
char *encSessionName = cgiEncodeFull(sessionName);
boolean shareSession = cartBoolean(cart, hgsNewSessionShare);
char *encUserName = cgiEncodeFull(userName);
struct sqlConnection *conn = hConnectCentral();
if (sqlTableExists(conn, namedSessionTable))
{
int useCount = saveCartAsSession(conn, encUserName, encSessionName, shareSession);
if (useCount > INITIAL_USE_COUNT)
dyStringPrintf(dyMessage,
"Overwrote the contents of session %s "
"(that %s be shared with other users). "
@@ -1048,32 +1057,33 @@
void doSaveSessionJson(char *userName)
/* AJAX endpoint behind the "Share a link" menu button. Save the current cart as a named session
* and print JSON {"name": , "url": }. When the user is not logged
* in (or hgsShareAnon is set), save under the reserved anonymous user "l" with a random token
* name. When logged in with no name given, generate a short random name. Saved shared by link so
* the link works for anyone. Reuses saveCartAsSession() and addSessionLink(). */
{
struct sqlConnection *conn = hConnectCentral();
if (!sqlTableExists(conn, namedSessionTable))
{
saveSessionJsonError(conn, "Required session table does not exist in the central database.");
return;
}
boolean anon = isEmpty(userName) || cgiBoolean(hgsShareAnon);
-// Read the requested name from the request, not the cart (hgSession's Save form leaves a sticky
-// value in the cart under this same variable that would otherwise shadow ours).
+// Read the requested name from the request, not the cart. cleanHgSessionFromCart() now takes
+// this variable back out, but carts written before that still hold a sticky value from
+// hgSession's Save form, and it would otherwise shadow ours.
char *sessionName = trimSpaces(cloneString(cgiUsualString(hgsNewSessionName, "")));
/* Keep our control variables out of the saved session contents and the user's own cart. */
cartRemove(cart, hgsDoSaveSessionJson);
cartRemove(cart, hgsShareAnon);
cartRemove(cart, hgsNewSessionName);
cartRemove(cart, hgsNewSessionShare);
char *encUserName = NULL;
char *encSessionName = NULL;
if (anon)
{
encUserName = "l"; /* reserved anonymous user -> short link /s/l/ */
sessionName = makeRandomKey(96); /* 16 URL-safe alphanumeric chars; no encoding needed */
encSessionName = sessionName;
@@ -1886,31 +1896,32 @@
errAbort("Unable to find session for userName='%s' and sessionName='%s'; no result from query '%s'",
encUserName, encSessionName, query);
return atoi(sharedStr);
}
char *doReSaveSession(char *userName, char *actionVar)
/* Load a session (which may have old trash and customTrash references) and re-save it
* so that customTrash tables will be moved to customData* databases and trash paths
* will be replaced with userdata (hg.conf sessionDataDir) paths.
* NOTE: this is not intended to be reachable by the UI; it is for a script to update
* old sessions to use the new sessionData locations. */
{
if (userName == NULL)
return "Unable to re-save session -- please log in and try again.";
struct sqlConnection *conn = hConnectCentral();
-char *sessionName = trimSpaces(cartString(cart, hgsNewSessionName));
+/* Clone: cartLoadUserSession() and saveCartAsSession() both free the cart's own copy. */
+char *sessionName = trimSpaces(cloneString(cartString(cart, hgsNewSessionName)));
if (isEmpty(sessionName))
return "Error: Unable to save a session without a name. Please add one and try again.";
char *encUserName = cgiEncodeFull(userName);
char *encSessionName = cgiEncodeFull(sessionName);
int sharingLevel = getSharingLevel(conn, encUserName, encSessionName);
cartLoadUserSession(conn, userName, sessionName, cart, NULL, actionVar);
// Don't cartCopyLocalHubs because we're not going to make any track collection changes
hubConnectLoadHubs(cart);
// Some old sessions reference databases that are no longer present, and that triggers an errAbort
// when cartHideDefaultTracks calls hgTrackDb. Don't let that stop the process of updating other
// stuff in the session.
struct errCatch *errCatch = errCatchNew();
if (errCatchStart(errCatch))
cartHideDefaultTracks(cart);