35c5facfb9e64030b6eeb70aadd2026bd9296c21 braney Wed Sep 30 17:11:59 2026 -0700 hgSession: a Save request without the share flag or the name no longer ends in a stack dump, refs #38456 doNewSession() read the session name with cartString() and the share flag with cartBoolean(), and both abort when the variable is missing. The Save form always sends both, so only a hand-made request reached this. doReSaveSession() read the name the same way, and outDefaultTracks() read db the same way, which a brand-new cart does not have. The name and db now have defaults, and a missing name gets the existing "without a name" message. For the share flag the code asks the request, not the cart, because the cart keeps hgS_newSessionShare on purpose. When the request carries neither the flag nor the checkbox's shadow, an existing session keeps its own sharing level and a new one is saved private. diff --git src/hg/hgSession/hgSession.c src/hg/hgSession/hgSession.c index 86f6b239a04..59cc28fda19 100644 --- src/hg/hgSession/hgSession.c +++ src/hg/hgSession/hgSession.c @@ -858,31 +858,31 @@ for(; statusList; statusList = statusList->next) { if (dy) dyStringPrintf(dy,"%d=%s ", statusList->id, cgiEncode(statusList->hubUrl)); else printf("%d=%s ", statusList->id, statusList->hubUrl); } if (dy == NULL) printf("\n"); } static void outDefaultTracks(struct cart *cart, struct dyString *dy) /* Output the default trackDb visibility for all tracks * in trackDb if the track is not mentioned in the cart. */ { -database = cartString(cart, "db"); +database = cartUsualString(cart, "db", hDefaultDb()); struct trackDb *tdb = NULL; // Some old sessions reference databases that are no longer present, and that triggers an errAbort // when calling hgTrackDb. Just move on instead of errAborting. struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) tdb = hTrackDb(database); errCatchEnd(errCatch); if (errCatch->gotError) { fprintf(stderr, "outDefaultTracks: Error from hTrackDb: '%s'; Continuing...", errCatch->message->string); tdb = NULL; } errCatchFree(&errCatch); @@ -997,42 +997,58 @@ /* Prevent modification of the custom track collection just saved to namedSessionDb. Under * copy-on-write hgCollection asks for its own trash copy before it writes, so this does * nothing. refs #38273 */ cartCopyLocalHubsOnSessionLoad(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); /* Clone: saveCartAsSession() removes this cart variable, which frees the cart's own copy. */ -char *sessionName = trimSpaces(cloneString(cartString(cart, hgsNewSessionName))); +char *sessionName = trimSpaces(cloneString(cartUsualString(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); + int sharingLevel; + /* Ask the request, not the cart: the cart keeps hgsNewSessionShare on purpose (see + * cleanHgSessionFromCart), so it is there long after the form that set it. */ + if (cgiBooleanDefined(hgsNewSessionShare) || cgiVarExists(hgsNewSessionShare)) + sharingLevel = cartBoolean(cart, hgsNewSessionShare); + else + { + /* The Save form always sends the checkbox's shadow, so this request did not come from it. + * Do not widen who can load the session: keep an existing session's level, and make a new + * one private (sqlQuickNum returns 0 when there is no row). */ + struct dyString *query = sqlDyStringCreate( + "select shared from %s where userName = '%s' and sessionName = '%s'", + namedSessionTable, encUserName, encSessionName); + sharingLevel = sqlQuickNum(conn, query->string); + dyStringFree(&query); + } + boolean shareSession = (sharingLevel > 0); + int useCount = saveCartAsSession(conn, encUserName, encSessionName, sharingLevel); if (useCount > INITIAL_USE_COUNT) dyStringPrintf(dyMessage, "Overwrote the contents of session %s " "(that %s be shared with other users). " "%s %s", htmlEncode(sessionName), (shareSession ? "may" : "may not"), getSessionLink(encUserName, encSessionName), getSessionEmailLink(encUserName, encSessionName)); else dyStringPrintf(dyMessage, "Added a new session %s that %s be shared with other users. " "%s %s", htmlEncode(sessionName), (shareSession ? "may" : "may not"), getSessionLink(encUserName, encSessionName), getSessionEmailLink(encUserName, encSessionName)); @@ -2072,31 +2088,31 @@ 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(); /* Clone: cartLoadUserSession() and saveCartAsSession() both free the cart's own copy. */ -char *sessionName = trimSpaces(cloneString(cartString(cart, hgsNewSessionName))); +char *sessionName = trimSpaces(cloneString(cartUsualString(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); // No cartCopyLocalHubsOnSessionLoad 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);