b50a995e326f37cb2248e9a04628c15fb1b58215 max Mon Sep 14 05:40:04 2026 -0700 My Sessions no longer hides a user's own "__" sessions, refs #38313 Both My Sessions listings decided a row was a share token by testing the session name for the "__" prefix, so they also hid sessions a user had named that way themselves - eleven of them on the RR, across four accounts, and their owners could no longer rename, describe, reshare or delete them. The prefix is a naming convention we follow, not a namespace we own. The authoritative mark is the "snapshotType " line saveSnapshotSession() already writes into the settings column, so test that instead. Both queries already select settings, so no query changes. snapshotTypeFromSettings() walks the settings lines rather than calling raFromString(): it runs once per listed row, and a hash there costs ~600ns and three allocations for every session that has a description, against ~30ns and none for the walk. Rows with empty settings, the common case, short-circuit in both. diff --git src/hg/lib/snapshotSession.c src/hg/lib/snapshotSession.c index d7cec8668dd..c2a2aa728c8 100644 --- src/hg/lib/snapshotSession.c +++ src/hg/lib/snapshotSession.c @@ -47,30 +47,71 @@ boolean snapshotHasRequired(struct snapshotType *type, struct cart *cart) /* Return FALSE when type declares a requiredVar that is missing/empty in cart. */ { if (type == NULL || isEmpty(type->requiredVar)) return TRUE; return isNotEmpty(cartOptionalString(cart, type->requiredVar)); } boolean snapshotIsSnapshotName(char *sessionName) /* Return TRUE if sessionName is a snapshot name (starts with the "__" prefix). */ { return sessionName != NULL && startsWith(snapshotNamePrefix, sessionName); } +char *snapshotTypeFromSettings(char *settings) +/* See snapshotSession.h. Reads the one tag by walking the lines rather than calling + * raFromString(): this runs once per row in the My Sessions listings, and building a hash there + * costs ~600ns and three allocations per session that has any settings at all, against ~30ns and + * none for the walk. Same line semantics as raFromString - leading blanks skipped, tag is the + * first word, value is the rest of the line. */ +{ +if (isEmpty(settings)) + return NULL; +int tagLen = strlen(snapshotTypeSetting); +char *s = settings; +while (s != NULL && s[0] != '\0') + { + s = skipLeadingSpaces(s); + if (s == NULL || s[0] == '\0') + break; + if (startsWith(snapshotTypeSetting, s) && (s[tagLen] == ' ' || s[tagLen] == '\t')) + { + char *val = skipLeadingSpaces(s + tagLen); + char *lineEnd = strchr(val, '\n'); + char *type = (lineEnd != NULL) ? cloneStringZ(val, lineEnd - val) : cloneString(val); + if (isEmpty(type)) + freez(&type); + return type; + } + s = strchr(s, '\n'); + if (s != NULL) + s++; + } +return NULL; +} + +boolean snapshotIsSnapshotSettings(char *settings) +/* See snapshotSession.h. */ +{ +char *type = snapshotTypeFromSettings(settings); +boolean isSnapshot = (type != NULL); +freez(&type); +return isSnapshot; +} + static char *snapshotSessionDirHashLen(char *sessionDataDir, char *encUserName, char *encSessionName, int hashLen) /* Alloc and return the durable data directory for one snapshot, or NULL if sessionDataDir is empty. * Like sessionData's sessionDirFromNames but with two extra hash levels drawn from the session name, * so a single high-volume user - the anonymous "l", which owns most snapshots - never accumulates * millions of entries in one directory. Layout: * sessionDataDir / <2hex md5(user)> / / <2hex sess> / <2hex sess> / * The two fan-out levels are a prefix of the same hash, so they spread the entries out without * adding uniqueness: it is hashLen alone that decides how likely two snapshots are to land in the * same directory, which is why it is sessionDirHashLen and not 8. hashLen is passed in only so * that the cleaner can also name a directory written before it was widened. * Snapshots use their own layout (not sessionDirFromNames), so this never affects normal sessions. */ { if (isEmpty(sessionDataDir)) return NULL; @@ -194,33 +235,36 @@ namedSessionTable, encUserName, encSessionName); struct sqlResult *sr = sqlGetResult(conn, dy->string); char **row; if ((row = sqlNextRow(sr)) != NULL) { firstUse = cloneString(row[0]); useCount = atoi(row[1]) + 1; } sqlFreeResult(&sr); dyStringClear(dy); sqlDyStringPrintf(dy, "DELETE FROM %s WHERE userName='%s' AND sessionName='%s'", namedSessionTable, encUserName, encSessionName); sqlUpdate(conn, dy->string); -/* settings records the snapshot type, so the row is self-describing for reconstruction/debugging. */ +/* settings records the snapshot type, so the row is self-describing for reconstruction/debugging. + * This is also what marks the row as a share token rather than a session the user saved: the + * "__" name prefix cannot say that, because a user may have named a session of their own that way + * (refs #38313). */ char settings[256]; -safef(settings, sizeof settings, "snapshotType %s\n", type->name); +safef(settings, sizeof settings, "%s %s\n", snapshotTypeSetting, type->name); dyStringClear(dy); sqlDyStringPrintf(dy, "INSERT INTO %s (userName, sessionName, contents, shared, firstUse, lastUse, useCount", namedSessionTable); if (gotSettings) sqlDyStringPrintf(dy, ", settings"); sqlDyStringPrintf(dy, ") VALUES ('%s', '%s', '", encUserName, encSessionName); sqlDyAppendEscaped(dy, contents); sqlDyStringPrintf(dy, "', 1, "); /* shared = 1 (shareable by link) */ if (firstUse) sqlDyStringPrintf(dy, "'%s', ", firstUse); else sqlDyStringPrintf(dy, "now(), "); sqlDyStringPrintf(dy, "now(), %d", useCount);