bd0d63cb1a33174b6f43b32a48fddad7850d0acc chmalee Mon May 4 14:05:56 2026 -0700 Remove em-dashes and fix myVariants custom tracks to use encoded usernames in their track names and trash files instead of raw usernames, refs #33808 diff --git src/hg/lib/myVariants.c src/hg/lib/myVariants.c index a40278f202f..ce7d3220a85 100644 --- src/hg/lib/myVariants.c +++ src/hg/lib/myVariants.c @@ -1,27 +1,28 @@ #include "common.h" #include "linefile.h" #include "dystring.h" #include "jksql.h" #include "myVariants.h" #include "myVariantsShare.h" #include "customTrack.h" #include "hdb.h" #include "hgConfig.h" #include "cheapcgi.h" #include "trashDir.h" #include "obscure.h" +#include "wikiLink.h" void myVariantsStaticLoad(char **row, struct myVariants *ret) /* Load a row from myVariants table into ret. The contents of ret will be replaced at the next call to this function. */ { ret->bin = sqlUnsigned(row[0]); ret->chrom = row[1]; ret->chromStart = sqlUnsigned(row[2]); ret->chromEnd = sqlUnsigned(row[3]); ret->name = row[4]; ret->score = sqlUnsigned(row[5]); safecpy(ret->strand, sizeof(ret->strand), row[6]); ret->thickStart = sqlUnsigned(row[7]); ret->thickEnd = sqlUnsigned(row[8]); ret->itemRgb = sqlUnsigned(row[9]); ret->description = row[10]; @@ -368,31 +369,42 @@ char *token = trackName + strlen("myVariants_shared_"); char cartVar[256]; safef(cartVar, sizeof(cartVar), MYVAR_SHARED_CART_PREFIX "%s", token); char *cartVal = (cart != NULL) ? cartOptionalString(cart, cartVar) : NULL; if (isEmpty(cartVal)) return NULL; char *owner = NULL; if (!myVariantsParseShareCartValue(cartVal, &owner, NULL, NULL, NULL, NULL)) return NULL; char *dbTable = myVariantsGetDbTable(owner); freeMem(owner); return dbTable; } if (startsWith("myVariants_", trackName)) { - char *userName = trackName + strlen("myVariants_"); + /* trackName is the SQL-identifier-encoded form "myVariants_<encoded>". + * Resolve via the current logged-in user (an own track is only viewable + * by its owner) and verify the trackName matches the encoded form for + * that user before returning their db.tableName. */ + char *userName = getUserName(); + if (isEmpty(userName)) + return NULL; + char *expected = myVariantsGetTableName(userName); + boolean match = sameOk(expected, trackName); + freeMem(expected); + if (!match) + return NULL; return myVariantsGetDbTable(userName); } return NULL; } char *myVariantsTableExists(char *userName) /* See if we already have a table for this user. If so, return the name * of the table (in db.tableName format), else NULL */ { if (!userName) return NULL; char *dbTable = myVariantsGetDbTable(userName); if (!dbTable) return NULL; struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH); @@ -447,30 +459,37 @@ " INDEX(project)\n" ");", db, tableName); sqlUpdate(conn, dyStringCannibalize(&createTable)); return myVariantsGetDbTable(userName); } } char *myVariantsWriteCtFile(char *userName, char *targetDb, struct cart *cart) /* Write a Custom Track file to trash for user's myVariants in targetDb and any shared * tracks found in cart. Return filename or NULL if nothing to write. */ { if (isEmpty(targetDb)) return NULL; +/* Identifier-safe form of userName for the CT track name and trash filename. + * The raw userName may contain non-ASCII or characters unsafe in trackDb + * syntax, filesystem paths, or SQL (e.g. '@', ';', spaces, quotes). */ +char *encodedTableName = NULL; +if (isNotEmpty(userName)) + encodedTableName = myVariantsGetTableName(userName); + /* Check if user has their own items */ boolean hasOwnItems = FALSE; if (isNotEmpty(userName)) { char *dbTable = myVariantsGetDbTable(userName); struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH); if (isNotEmpty(dbTable) && sqlTableExists(conn, dbTable)) { char countQuery[512]; sqlSafef(countQuery, sizeof countQuery, "select count(*) from %s where db='%s'", dbTable, targetDb); hasOwnItems = (sqlQuickNum(conn, countQuery) > 0); } hFreeConn(&conn); } @@ -484,31 +503,31 @@ struct sqlConnection *centralConn = (shareVars != NULL) ? hConnectCentral() : NULL; for (el = shareVars; el != NULL; el = el->next) { char *owner = NULL, *project = NULL, *db = NULL, *label = NULL; int permission = 0; if (!myVariantsParseShareCartValue(el->val, &owner, &project, &db, &permission, &label)) continue; if (!sameString(db, targetDb)) { freeMem(owner); freeMem(project); freeMem(db); freeMem(label); continue; } - /* Skip if the sharer is the current user — they already see their own track */ + /* Skip if the sharer is the current user - they already see their own track */ if (isNotEmpty(userName) && sameString(owner, userName)) { freeMem(owner); freeMem(project); freeMem(db); freeMem(label); continue; } /* Re-validate the share against hgcentral each render so that revoked * or user-targeted shares get filtered per the viewer's identity. */ char *token = el->name + strlen(MYVAR_SHARED_CART_PREFIX); struct myVariantsShare *share = NULL; if (centralConn != NULL && sqlTableExists(centralConn, "myVariantsShares")) share = myVariantsGetShareByToken(centralConn, token); if (share == NULL) @@ -565,51 +584,53 @@ " longLabel=\"Shared variants: %s (from %s)\"\n", token, shortLabel, projectLabel, owner); freeMem(owner); freeMem(project); freeMem(db); freeMem(label); } if (centralConn != NULL) hDisconnectCentral(¢ralConn); hashElFreeList(&shareVars); } if (!hasOwnItems && dyStringLen(sharedLines) == 0) { dyStringFree(&sharedLines); + freeMem(encodedTableName); return NULL; } -/* Reusable, stable filename per user+db — always rewrite since shares are dynamic */ +/* Reusable, stable filename per user+db - always rewrite since shares are dynamic */ struct tempName tn; char base[PATH_LEN]; char *hostPort = cgiServerNamePort(); safef(base, sizeof base, "myVariants_%s_%s_%s", hostPort ? hostPort : "localhost", targetDb, - isNotEmpty(userName) ? userName : "shared"); + isNotEmpty(encodedTableName) ? encodedTableName : "shared"); for (char *p = base; *p; p++) if (*p == '/') *p = '_'; trashDirReusableFile(&tn, "ct", base, ".bed"); FILE *f = mustOpen(tn.forCgi, "w"); if (hasOwnItems) - fprintf(f, "track name=\"myVariants_%s\" type=\"myVariants\" itemRgb=\"on\"" + fprintf(f, "track name=\"%s\" type=\"myVariants\" itemRgb=\"on\"" " visibility=\"pack\" shortLabel=\"My Variants\"" - " longLabel=\"My Variants (%s)\"\n", userName, userName); + " longLabel=\"My Variants (%s)\"\n", encodedTableName, encodedTableName); if (dyStringLen(sharedLines) > 0) fprintf(f, "%s", dyStringContents(sharedLines)); carefulClose(&f); dyStringFree(&sharedLines); +freeMem(encodedTableName); return cloneString(tn.forCgi); } struct slName *myVariantsGetProjects(char *userName) /* Return list of distinct non-empty project values for this user's myVariants table. * Caller must slFreeList the result. Returns NULL if no projects or table doesn't exist. */ { if (isEmpty(userName)) return NULL; char *dbTable = myVariantsTableExists(userName); if (isEmpty(dbTable)) return NULL; struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH);