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/hgGene/rnaStructure.c src/hg/hgGene/rnaStructure.c
index 2c6d1a59068..de4ef002a20 100644
--- src/hg/hgGene/rnaStructure.c
+++ src/hg/hgGene/rnaStructure.c
@@ -3,30 +3,31 @@
 /* Copyright (C) 2014 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include <limits.h>
 #include "hash.h"
 #include "linefile.h"
 #include "jksql.h"
 #include "rnaFold.h"
 #include "hui.h"
 #include "web.h"
 #include "portable.h"
 #include "hgGene.h"
 #include "hgConfig.h"
 #include "pipeline.h"
+#include "trashDir.h"
 
 
 static void rnaTrashDirsInit(char **tables, int count)
 /*	create trash directories if necessary */
 {
 for ( count--; count > -1; count--)
     mkdirTrashDirectory(tables[count]);
 }
 
 static boolean rnaStructureExists(struct section *section, 
 	struct sqlConnection *conn, char *geneId)
 /* Return TRUE if tables exists and have our gene. */
 {
 if (sqlTableExists(conn, "foldUtr3") && 
 	sqlRowExists(conn, "foldUtr3", "name", geneId))
@@ -238,30 +239,37 @@
     }
 else if (sameString(how, "picture"))
     {
     char *psFile = cartString(cart, hggMrnaFoldPs);
 
     // Sanity check the psFile variable
     // It should point to a .ps file that exists in the trash/foldUtr5 or foldUtr3 directory.
     // That means it should match trashDir()/foldUtr[35]/foldUtr[35]_.*\.ps as a regex,
     // and that the file should exist.
 
     if (!endsWith(psFile, ".ps"))
         {
         warn("Invalid file provided for creating of RNA structure pdf/image");
         return;
         }
+    // The prefix tests below match on the front of the name only, so a ".." further along
+    // would still walk out of the trash directory.  isTrashPath refuses that.
+    if (!isTrashPath(psFile))
+        {
+        warn("Invalid file provided for creating of RNA structure pdf/image");
+        return;
+        }
     char sanCheck[4096];
     safef(sanCheck, sizeof(sanCheck), "%s/foldUtr5/foldUtr5_", trashDir());
     if (!startsWith(sanCheck, psFile))
         {
         safef(sanCheck, sizeof(sanCheck), "%s/foldUtr3/foldUtr3_", trashDir());
         if (!startsWith(sanCheck, psFile))
             {
             warn("Invalid file provided for creating of RNA structure pdf/image");
             return;
             }
         }
     if (!fileExists(psFile))
         {
         warn("Indicated postscript file for RNA structure pdf/image generation does not exist.");
         return;