554396e44745165ee4baf5214aa771f2b17b3be6
braney
  Mon Aug 17 16:04:26 2026 -0700
hgPcr, cart: screen the PCR result file names read back out of the cart, refs #37623

The hgPcrResult_<db> cart variable holds two file names and an optional target
name in one value.  The cart.c arrays compare a whole value against
isServerUserFilePath(), so none of them fit that shape.  Add a fourth array for
it and check the first two words.  hgPcrResult_targetStyle shares the prefix and
is a display setting, so it is excluded by name.

Check both names where they are used as well, in pcrResultParseCart() and in
hgPcr's append path, the way dupTrack.c already does.

Two other things in writePcrResultTrack().  pcrFiles[2] was read without ever
being set whenever the value held only two words, which is the usual case.  And
the saved-session test was a plain prefix compare that missed sessionDataDirOld;
it now asks whether the file is in the trash instead.

hg/utils/cartFileVarCatalog knows about the new array and has a row for
hgPcrResult_<db> saying why its scan cannot see this one.

diff --git src/hg/hgPcr/hgPcr.c src/hg/hgPcr/hgPcr.c
index 9a3caa06a8b..67c3ad3bdea 100644
--- src/hg/hgPcr/hgPcr.c
+++ src/hg/hgPcr/hgPcr.c
@@ -532,49 +532,63 @@
 "for academic, personal, and non-profit purposes.  Non-exclusive commercial\n"
 "licenses are also available.  Contact Jim for details.</P>\n");
 }
 
 void writePrimers(struct gfPcrOutput *gpo, char *fileName)
 /* Write primer sequences to file.  Look at only the first gpo because there
  * is only one set of primers in the input form. */
 {
 if (gpo == NULL)
     return;
 FILE *f = mustOpen(fileName, "a");
 fprintf(f, "%s\t%s\n", gpo->fPrimer, gpo->rPrimer);
 carefulClose(&f);
 }
 
+static boolean pcrResultCartFiles(char *cartResult, char **retPslFile, char **retTxtFile,
+                                  char **retTarget)
+/* Chop an existing hgPcrResult_<db> value into its two file names and its optional target
+ * name.  Return FALSE unless both names are files the server made for this user: the value
+ * comes back out of the cart, and the caller writes to both files.  Refs #37623. */
+{
+char *words[4];
+int wordCount = chopByWhite(cloneString(cartResult), words, ArraySize(words));
+if (wordCount < 2 || wordCount > 3)
+    return FALSE;
+if (!isServerUserFilePath(words[0]) || !isServerUserFilePath(words[1]))
+    return FALSE;
+*retPslFile = words[0];
+*retTxtFile = words[1];
+*retTarget = (wordCount > 2) ? words[2] : NULL;
+return TRUE;
+}
+
 void writePcrResultTrack(struct gfPcrOutput *gpoList, char *db, char *target, boolean appendToResults)
 /* Write trash files and store their name in a cart variable. */
 {
 char *cartVar = pcrResultCartVar(db);
 struct tempName bedTn, primerTn;
 char buf[2048];
 char *pslFile, *txtFile, *cartTarget, *cartResult;
-if ( (cartResult = cartOptionalString(cart, cartVar)) != NULL && appendToResults)
+if ( (cartResult = cartOptionalString(cart, cartVar)) != NULL && appendToResults &&
+     pcrResultCartFiles(cartResult, &pslFile, &txtFile, &cartTarget))
     {
-    char *pcrFiles[3];
-    chopByWhite(cloneString(cartResult), pcrFiles, 3);
-    pslFile = pcrFiles[0];
-    txtFile = pcrFiles[1];
-    cartTarget = pcrFiles[2];
     // if the old result is from a saved session, we can't append to it
     // because we want the session to not change. Copy the old results
-    // into a new file and append these results to it
-    char *sessionDataDir = cfgOption("sessionDataDir");
-    if (sessionDataDir && startsWith(sessionDataDir, pslFile))
+    // into a new file and append these results to it.  A trash file is ours to append to;
+    // anything else pcrResultCartFiles() accepted is durable session data.
+    if (!isTrashPath(pslFile))
         {
         trashDirFile(&bedTn, "hgPcr", "hgPcr", ".psl");
         trashDirFile(&primerTn, "hgPcr", "hgPcr", ".txt");
         // copy the old to the new
         copyFile(pslFile, bedTn.forCgi);
         copyFile(txtFile, primerTn.forCgi);
         gfPcrOutputWriteAll(gpoList, "psl", NULL, bedTn.forCgi);
         writePrimers(gpoList, primerTn.forCgi);
         if (isNotEmpty(target))
             safef(buf, sizeof(buf), "%s %s %s", bedTn.forCgi, primerTn.forCgi, target);
         else
             safef(buf, sizeof(buf), "%s %s", bedTn.forCgi, primerTn.forCgi);
         cartSetString(cart, cartVar, buf);
         }
     else