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/hgTracks/hgTracks.c src/hg/hgTracks/hgTracks.c
index d6b50788425..85ef023b500 100644
--- src/hg/hgTracks/hgTracks.c
+++ src/hg/hgTracks/hgTracks.c
@@ -4384,30 +4384,36 @@
     }
 
 struct lineFile *lf = NULL;
 if (strstr(multiRegionsBedUrl,"://"))
     {
     lf = lineFileUdcMayOpen(multiRegionsBedUrl, FALSE);
     if (!lf)
 	{
 	warn("Unable to open [%s] with udc", multiRegionsBedUrl);
 	return FALSE;
 	}
     *bedDateTime = udcTimeFromCache(multiRegionsBedUrl, NULL);
     }
 else
     {
+    /* Not a URL, so this is the trash file we wrote the pasted BED to. */
+    if (!isServerUserFilePath(multiRegionsBedUrl))
+	{
+	warn("BED custom regions file [%s] not found.", multiRegionsBedUrl);
+	return FALSE;
+	}
     lf = lineFileMayOpen(multiRegionsBedUrl, TRUE);
     if (!lf)
 	{
 	warn("BED custom regions file [%s] not found.", multiRegionsBedUrl);
 	return FALSE;
 	}
     *bedDateTime = 0;
     // touch corresponding .sha1 file to save it from trash cleaner.
     char multiRegionsBedUrlSha1Name[1024];
     safef(multiRegionsBedUrlSha1Name, sizeof multiRegionsBedUrlSha1Name, "%s.sha1", multiRegionsBedUrl);
     if (fileExists(multiRegionsBedUrlSha1Name))
 	readAndIgnore(multiRegionsBedUrlSha1Name);	
     }
 char *line;
 int lineSize;
@@ -8612,31 +8618,31 @@
 	tg->drawItems = bigDrawWarning;
 	tg->totalHeight = bigWarnTotalHeight;
 	}
     }
 }
 
 
 void outCollectionsToJson()
 /* Output the current collections to the hgTracks JSON block. */
 {
 struct grp *groupList = NULL;
 char buffer[4096];
 safef(buffer, sizeof buffer, "%s-%s", customCompositeCartName, database);
 char *hubFile = cartOptionalString(cart, buffer);
 
-if (hubFile != NULL)
+if (hubFile != NULL && isServerUserFilePath(hubFile))
     {
     char *hubName = hubNameFromUrl(hubFile);
     struct trackDb *hubTdbs = hubCollectTracks( database,  &groupList);
     struct trackDb *tdb;
     struct jsonElement *jsonList = NULL;
     for(tdb = hubTdbs; tdb;  tdb = tdb->next)
         {
         if (sameString(tdb->grp, hubName))
             {
             if (jsonList == NULL)
                 jsonList = newJsonList(NULL);
 
             struct jsonElement *collection = newJsonObject(newHash(4));
             jsonObjectAdd(collection, "track", newJsonString(tdb->track));
             jsonObjectAdd(collection, "shortLabel", newJsonString(tdb->shortLabel));