9ad04e0a0b06ec3c4f09ef1b6c3ce6be79b61c68
braney
  Sun Aug 16 11:56:56 2026 -0700
cart: validate file names read back out of the cart

Several cart variables hold the name of a file the server created for a user.
Route them through one shared check, isServerUserFilePath(), which accepts the
trash directory, the session-data directories and myVariantsDataDir, and apply
it both where values enter the cart and where the file names are used.

A few of these variables may instead hold a remote URL.  Those get their own
list and isServerUserFileOrUrl(), because the code that reads them chooses
between a fetch and a local open by looking for a protocol.

Consolidates two hand-rolled copies of the same test in blatShare.c and
customFactory.c, and drops the weaker private copy in sessionData.c.

Adds hg/utils/cartFileVarCatalog, a registry that scans the tree for a cart
value reaching a file call and reconciles what it finds against the lists in
cart.c, so a new one of these cannot be added without somebody noticing.  Its
--reconcile is quiet enough for the nightly cron the other catalogs use, and it
is what turned up seven of the names now on those lists.

refs #37623

diff --git src/hg/lib/dupTrack.c src/hg/lib/dupTrack.c
index 9a2caf78f0e..ad69d5c1457 100644
--- src/hg/lib/dupTrack.c
+++ src/hg/lib/dupTrack.c
@@ -1,27 +1,37 @@
 /* Stuff to help handle a track that is a duplicate of a another - sharing data
  * and type, but having it's own cart settings. */
 
 #include "common.h"
 #include "hash.h"
 #include "cart.h"
 #include "dupTrack.h"
 #include "ra.h"
 #include "portable.h"
 #include "trashDir.h"
 #include "hgConfig.h"
 
+static char *dupFileNameFromCart(struct cart *cart)
+/* Return the name of the trash file holding the dupe track stanzas, or NULL.  The value comes
+ * out of the cart, so only use it if it names a file the server itself made. */
+{
+char *dupFileName = cartUsualString(cart, DUP_TRACKS_VAR, NULL);
+if (dupFileName != NULL && !isServerUserFilePath(dupFileName))
+    return NULL;
+return dupFileName;
+}
+
 boolean isDupTrack(char *track)
 /* determine if track name refers to a custom track */
 {
 return (startsWith(DUP_TRACK_PREFIX, track));
 }
 
 char *dupTrackSkipToSourceName(char *dupeTrackName)
 /* If it looks like it's a dupe track then skip over duppy part 
  * in particular skip over dup_N_ form prefix for numerical N. */
 {
 char *name = dupeTrackName;
 
 if ((name != NULL ) && startsWith(DUP_TRACK_PREFIX, name))
     {
     char *s = name + strlen(DUP_TRACK_PREFIX);
@@ -46,31 +56,31 @@
 /* Make up a name of format dup_N_sourceTrack where N is a small unique number */
 {
 int i;
 for (i=0; ;++i)
     {
     makeDupName(sourceName, i, nameBuf, nameBufSize);
     if (!hashLookup(dupHash, nameBuf))
 	return;
     }
 }
 
 char *dupTrackInCartAndTrash(char *sourceTrack, struct cart *cart, struct trackDb *sourceTdb)
 /* Update cart vars to reflect existance of duplicate of sourceTrack.
  * Also write out or append to dupe track trash file */
 {
-char *dupFileName = cartUsualString(cart, DUP_TRACKS_VAR, NULL);
+char *dupFileName = dupFileNameFromCart(cart);
 FILE *f = NULL;	    // This will be our output
 
 /* We keep duplicate's name here. */
 int bufSize = strlen(sourceTrack) + 32;
 char dupeTrackName[bufSize];
 makeDupName(sourceTrack, 0, dupeTrackName, sizeof(dupeTrackName));
 
 if (dupFileName != NULL && fileExists(dupFileName))   // Try and read in from old file
     {
     /* If there are already duplicates and trash cleaner hasn't nuked file read it in
      * and make sre we come up with a unique name before we append to existing file */
     struct hash *dupHash = raReadAll(dupFileName, "track");
     findUniqDupName(sourceTrack, dupHash, dupeTrackName, sizeof(dupeTrackName));
     f = mustOpen(dupFileName, "a");	// Append to old file but use new name
     fputc('\n', f);
@@ -116,31 +126,31 @@
 cartRemovePrefix(cart, namePlusBuf);
 namePlusBuf[nameSize] = '_';
 cartRemovePrefix(cart, namePlusBuf);
 }
 
 static void dupTracksRemoveAllFromCart(struct cart *cart)
 /* Remove all trace of dupe tracks from cart */
 {
 cartRemovePrefix(cart, DUP_TRACK_PREFIX);
 }
 
 void undupTrackInCartAndTrash(char *dupName, struct cart *cart)
 /* Update cart vars to reflect removal of dupTrack.  Also reduce or 
  * remove trash file */
 {
-char *dupFileName = cartUsualString(cart, DUP_TRACKS_VAR, NULL);
+char *dupFileName = dupFileNameFromCart(cart);
 if (dupFileName == NULL)
     return;  // Nothing to do here
 
 /* We'll try and create our new list from info in old file */
 struct dupTrack *newList = NULL;
 if (fileExists(dupFileName))
     {
     struct dupTrack *dupList = dupTrackReadAll(dupFileName);
 
     /* Turn newList into a copy of dupList with our own stanza removed */
     struct dupTrack *dup, *next;
     for (dup = dupList; dup != NULL; dup = next)
         {
 	next = dup->next;
 	if (!sameString(dupName, dup->name))
@@ -209,31 +219,31 @@
     if (!sameString(tag->name, "track"))
 	{
 	if (sameString(tag->name, "longLabel"))
 	    tdb->longLabel = tag->val;
 	else if (sameString(tag->name, "shortLabel"))
 	    tdb->shortLabel = tag->val;
 	hashAdd(tdb->settingsHash, tag->name, tag->val);
 	}
     }
 }
 
 struct dupTrack *dupTrackListFromCart(struct cart *cart)
 /* Consult cart for dup track variable and if it's there return
  * list of dupes */
 {
-char *dupFileName = cartUsualString(cart, DUP_TRACKS_VAR, NULL);
+char *dupFileName = dupFileNameFromCart(cart);
 if (dupFileName == NULL)
     return NULL;
 struct dupTrack *list = dupTrackReadAll(dupFileName);
 if (list == NULL)	// Trash cleaner got it, so clean up cart too
     dupTracksRemoveAllFromCart(cart);
 return list;
 }
 
 struct dupTrack *dupTrackFindInList(struct dupTrack *list, char *name)
 /* Return matching element in list */
 {
 struct dupTrack *dup;
 for (dup = list; dup != NULL; dup = dup->next)
    if (sameString(dup->name, name))
        break;