7181c0af889a0eee451b91ff0b23d67f35e0e772
braney
  Tue Aug 18 08:39:08 2026 -0700
cheapcgi, customTrack, hgSession, hgPhyloPlace: track in-memory uploads in a registry

Uploaded file contents are handed to the reading code as a text address and
size.  Collect that bookkeeping in cheapcgi, which now records each block it
makes and hands back a name for it, and have the custom track, session and
phyloPlace upload paths look the block up by that name.

Also removes the duplicated address/size parsing those callers each had, and
makes lineFileDecompressMem ignore a too-small buffer.

refs #38108

diff --git src/hg/lib/customFactory.c src/hg/lib/customFactory.c
index 718f748183a..5f01d3c78ed 100644
--- src/hg/lib/customFactory.c
+++ src/hg/lib/customFactory.c
@@ -1,30 +1,31 @@
 /* customFactory - a polymorphic object for handling
  * creating various types of custom tracks. */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include <pthread.h>
 #include "common.h"
 #include "errCatch.h"
 #include "hash.h"
 #include "linefile.h"
 #include "portable.h"
 #include "obscure.h"
 #include "binRange.h"
 #include "pipeline.h"
+#include "cheapcgi.h"
 #include "jksql.h"
 #include "net.h"
 #include "bed.h"
 #include "psl.h"
 #include "gff.h"
 #include "wiggle.h"
 #include "genePred.h"
 #include "trackDb.h"
 #include "hgConfig.h"
 #include "hdb.h"
 #include "hui.h"
 #include "customTrack.h"
 #include "customPp.h"
 #include "customFactory.h"
 #include "trashDir.h"
@@ -4109,68 +4110,85 @@
 }
 
 static struct customTrack *trackLineToTrack(char *genomeDb, char *line, int lineIx)
 /* Convert a track specification line to a custom track structure. */
 {
 /* Make up basic track with associated tdb.  Fill in settings
  * from var=val pairs in line. */
 struct customTrack *track;
 AllocVar(track);
 struct trackDb *tdb = customTrackTdbDefault();
 track->tdb = tdb;
 customTrackUpdateFromSettings(track, genomeDb, line, lineIx);
 return track;
 }
 
+static char *customMemFromSpec(char *text, char **retName, unsigned long *retSize)
+/* Parse a "<scheme>://<name> <address> <size>" string made by prepCompressedFile
+ * or prepBigData, and return the uploaded data it names.  Return NULL unless
+ * this program handed out that address:  such a string can also arrive in a cgi
+ * variable, and then the address comes from whoever sent the request.  If
+ * retName is not NULL the "<scheme>://<name>" part is cloned into it. */
+{
+char *copy = cloneString(text);
+char *words[4];
+int wordCount = chopByWhite(copy, words, ArraySize(words));
+char *mem = NULL;
+/* A registered address and size are each an unsigned long, at most 20 digits.
+ * A longer word cannot name a block we handed out, so leave mem NULL and let
+ * the caller fall back to treating the text as literal data. */
+if (wordCount == 3 && strlen(words[1]) <= 20 && strlen(words[2]) <= 20)
+    {
+    char spec[64];
+    safef(spec, sizeof(spec), "%s %s", words[1], words[2]);
+    mem = cgiMemBlobFind(spec, retSize);
+    if (mem != NULL && retName != NULL)
+	*retName = cloneString(words[0]);
+    }
+freeMem(copy);
+return mem;
+}
+
 static struct lineFile *customLineFile(char *text, boolean isFile)
 /* Figure out input source, handling URL's and compression */
 {
 if (!text)
     return NULL;
 
 struct lineFile *lf = NULL;
 if (isFile)
     {
     if (stringIn("://", text))
         lf = netLineFileOpen(text);
     else
 	lf = lineFileOpen(text, TRUE);
     }
 else
     {
-    if (startsWith("compressed://",text))
+    if (startsWith("compressed://",text) || startsWith("memory://", text))
 	{
-	char *words[3];
-	char *mem;
-        unsigned long size;
-	chopByWhite(text,words,3);
-    	mem = (char *)sqlUnsignedLong(words[1]);
-        size = sqlUnsignedLong(words[2]);
+	char *name = NULL;
+	unsigned long size = 0;
+	char *mem = customMemFromSpec(text, &name, &size);
+	if (mem == NULL)
+	    /* Not data this program uploaded, so it is just text that happens
+	     * to look like a reference to some. */
+	    lf = lineFileOnString(CT_NO_FILE_NAME, TRUE, text);
+	else if (startsWith("compressed://", text))
 	    lf = lineFileDecompressMem(TRUE, mem, size);
-	}
-    else if (startsWith("memory://", text))
-	{
-	int len = strlen(text) + 1;
-	char copy[len];
-	safecpy(copy, len, text);
-	char *words[3];
-	int wordCount = chopByWhite(copy, words, 3);
-	if (wordCount != 3)
-	    errAbort("customLineFile: badly formatted input '%s': expected 3 words, got %d",
-		     text, wordCount);
-	char *mem = (char *)sqlUnsignedLong(words[1]);
-	lf = lineFileOnString(words[0], TRUE, mem);
+	else
+	    lf = lineFileOnString(name, TRUE, mem);
 	}
     else
         {
 	lf = lineFileOnString(CT_NO_FILE_NAME, TRUE, text);
 	}
     }
 return lf;
 }
 
 char *customDocParse(char *text)
 /* Return description text, expanding URLs as for custom track data */
 {
 char *line;
 struct lineFile *lf = customLineFile(text, FALSE);
 if (!lf)