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(&centralConn);
     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);