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/customTrack.c src/hg/lib/customTrack.c
index e24a910f6cc..74df0b83f57 100644
--- src/hg/lib/customTrack.c
+++ src/hg/lib/customTrack.c
@@ -631,51 +631,70 @@
 
 boolean customTrackIsCompressed(char *fileName)
 /* test for file suffix indicating compression */
 {
 char *fileNameDecoded = cloneString(fileName);
 cgiDecode(fileName, fileNameDecoded, strlen(fileName));
 boolean result = 
     (endsWith(fileNameDecoded,".gz") || 
      endsWith(fileNameDecoded,".Z")  ||
      endsWith(fileNameDecoded,".zip")  ||
      endsWith(fileNameDecoded,".bz2"));
 freeMem(fileNameDecoded);
 return result;
 }
 
+#define CT_UPLOAD_GONE_MSG "The contents of the uploaded file are no longer available. " \
+			   "Please choose the file again."
+
+static char *memSpecFromCart(struct cart *cart, char *binVar, char *fileVar)
+/* Return the "<address> <size>" text naming the uploaded contents of a file
+ * field, or NULL if there are none.  The binVar cart variable holds such text
+ * already, but any request can set that variable, so look up the block cheapcgi
+ * handed out rather than trusting the text.  Otherwise the contents came
+ * through as an ordinary string in fileVar, and get a name of their own here. */
+{
+char *cFBin = cartOptionalString(cart, binVar);
+if (cFBin != NULL)
+    {
+    unsigned long size;
+    char *mem = cgiMemBlobFind(cFBin, &size);
+    if (mem == NULL)
+	return NULL;
+    return cgiMemBlobRegister(mem, size);
+    }
+char *cF = cartOptionalString(cart, fileVar);
+if (cF == NULL)
+    return NULL;
+return cgiMemBlobRegister(cF, strlen(cF));
+}
+
 static char *prepCompressedFile(struct cart *cart, char *fileName,
                                         char *binVar, char *fileVar)
 /* determine compression type and format properly for parser */
 {
     if (!customTrackIsCompressed(fileName))
     return NULL;
 char buf[256];
-char *cFBin = cartOptionalString(cart, binVar);
-if (cFBin)
-    {
-    safef(buf,sizeof(buf),"compressed://%s %s", fileName,  cFBin);
 /* cgi functions preserve binary data, cart vars have been
  *  cloneString-ed  which is bad for a binary stream that might
  * contain 0s  */
-    }
-else
-    {
-    char *cF = cartOptionalString(cart, fileVar);
-    safef(buf,sizeof(buf),"compressed://%s %lu %lu",
-        fileName, (unsigned long) cF, (unsigned long) strlen(cF));
-    }
+char *spec = memSpecFromCart(cart, binVar, fileVar);
+if (spec == NULL)
+    return NULL;
+safef(buf,sizeof(buf),"compressed://%s %s", fileName, spec);
+freeMem(spec);
 return cloneString(buf);
 }
 
 char* customTrackTypeFromBigFile(char *url)
 /* return most likely type for a big file name or NULL,
  * has to be freed. */
 {
 // pull out file part from the URL, strip off the query part after "?"
 char fileName[2000];
 safecpy(fileName, sizeof(fileName), url);
 chopSuffixAt(fileName, '?');
 
 // based on udc cache dir analysis by hiram in rm #12813
 if (endsWith(fileName, ".bb") || endsWith(fileName, ".bigBed") || endsWith(fileName, ".bigbed")
  || endsWith(fileName, "%2Ebb") || endsWith(fileName, "%2EbigBed") || endsWith(fileName, "%2Ebigbed")
@@ -722,45 +741,36 @@
 char *type = customTrackTypeFromBigFile(fileNameDecoded);
 // exclude plain VCF (as opposed to vcfTabix) from bigData treatment
 result = (type != NULL && differentString(type, "vcf"));
 
 freeMem(type);
 freeMem(fileNameDecoded);
 return result;
 }
 
 static char *prepBigData(struct cart *cart, char *fileName, char *binVar, char *fileVar)
 /* Pass data's memory offset and size through to customFactory */
 {
 if (!customTrackIsBigData(fileName))
     return NULL;
 char buf[1024];
-char *cFBin = cartOptionalString(cart, binVar);
-char *cF = cartOptionalString(cart, fileVar);
-if (cFBin)
-    {
-    // cFBin already contains memory offset and size (search for __binary in cheapcgi.c)
-    safef(buf,sizeof(buf),"memory://%s %s", fileName, cFBin);
-    char *split[3];
-    int splitCount = chopByWhite(cloneString(cFBin), split, sizeof(split));
-    if (splitCount > 2) {errAbort("hgCustom: extra garbage in %s", binVar);}
-    }
-else
-    {
-    safef(buf, sizeof(buf),"memory://%s %lu %lu",
-	  fileName, (unsigned long) cF, (unsigned long) strlen(cF));
-    }
+// the spec holds the memory offset and size (search for __binary in cheapcgi.c)
+char *spec = memSpecFromCart(cart, binVar, fileVar);
+if (spec == NULL)
+    return NULL;
+safef(buf,sizeof(buf),"memory://%s %s", fileName, spec);
+freeMem(spec);
 return cloneString(buf);
 }
 
 boolean ctConfigUpdate(char *ctFile)
 /* CT update is needed if database has been enabled since
  * the custom tracks in this file were created.  The only way to check is
  * by file mod time, unless we add the enable time to
  * browser metadata somewhere */
 {
 if (!ctFile || !fileExists(ctFile))
     return FALSE;
 return cfgModTime() > fileModTime(ctFile);
 }
 
 struct customTrack *customTracksParseCartDetailed(char *genomeDb, struct cart *cart,
@@ -799,78 +809,91 @@
 
 fileName = cartOptionalString(cart, CT_CUSTOM_FILE_NAME_VAR);
 char *fileContents = cartOptionalString(cart, CT_CUSTOM_FILE_VAR);
 if (isNotEmpty(fileName))
     {
     /* handle file input, optionally with compression */
     if (isNotEmpty(fileContents))
         customText = fileContents;
     else
         {
         /* file contents not available -- check for compressed */
         if (customTrackIsCompressed(fileName))
             {
             customText = prepCompressedFile(cart, fileName,
                                 CT_CUSTOM_FILE_BIN_VAR, CT_CUSTOM_FILE_VAR);
+            if (customText == NULL)
+                err = cloneString(CT_UPLOAD_GONE_MSG);
             }
 	else if (customTrackIsBigData(fileName))
 	    {
 	    // User is trying to directly upload a bigData file; pass data to
 	    // customFactory, which will alert the user that they need bigDataUrl etc.
 	    customText = prepBigData(cart, fileName, CT_CUSTOM_FILE_BIN_VAR, CT_CUSTOM_FILE_VAR);
+	    if (customText == NULL)
+	        err = cloneString(CT_UPLOAD_GONE_MSG);
 	    }
         else
             {
             /* unreadable file */
             struct dyString *ds = dyStringNew(0);
             dyStringPrintf(ds, "Unrecognized binary data format in file %s. You can only upload text files on this page. If you have a binary file, like bigBed, bigWig, BAM, etc, copy them to a webserver and paste the URL of the file into the text box here or create a track hub for them. For more details, our <a href='https://genome.ucsc.edu/goldenpath/help/hgTrackHubHelp.html#Hosting'>documentation</a> discusses where you can host binary files.", fileName);
             err = dyStringCannibalize(&ds);
             }
 	}
     }
 customText = skipLeadingSpaces(customText);
 
 /* get track description from cart */
 char *html = NULL;
 char *docFileName = cartOptionalString(cart, CT_CUSTOM_DOC_FILE_NAME_VAR);
 char *docFileContents = cartOptionalString(cart, CT_CUSTOM_DOC_FILE_VAR);
 if (isNotEmpty(docFileContents))
     html = docFileContents;
 else if (isNotEmpty(docFileName))
     {
     if (customTrackIsCompressed(docFileName))
+        {
         html = prepCompressedFile(cart, docFileName,
                         CT_CUSTOM_DOC_FILE_BIN_VAR, CT_CUSTOM_DOC_FILE_VAR);
+        if (html == NULL)
+            {
+            err = cloneString(CT_UPLOAD_GONE_MSG);
+            customText = NULL;
+            }
+        }
     else
         {
         /* unreadable file */
         struct dyString *ds = dyStringNew(0);
         dyStringPrintf(ds, "Can't read doc file: %s", docFileName);
         err = dyStringCannibalize(&ds);
         customText = NULL;
         }
     }
 else
     html = cartUsualString(cart, CT_CUSTOM_DOC_TEXT_VAR, "");
 html = cloneString(html);     /* do not let original cart var get eaten up */
 html = customDocParse(html);  /* this will chew up the input string */
 if(html != NULL)
     {
     char *tmp = html;
     html = jsStripJavascript(html);
     freeMem(tmp);
     }
+else
+    html = cloneString("");   /* the doc file could not be read, see above */
 
 if ((strlen(html) > 50*1024) || startsWith("track ", html) || startsWith("browser ", html))
     {
     err = cloneString(
 	"Optional track documentation appears to be either too large (greater than 50k) or it starts with a track or browser line. "
 	"This is usually an indication that the data has been accidentally put into the documentation field. "
 	"Only html documentation is intended for this field. "
         "Please correct and re-submit.");
     html = NULL;  /* we do not want to save this bad value */
     customText = NULL;  /* trigger a return to the edit page */
     }
 
 struct customTrack *newCts = NULL, *ct = NULL;
 if (isNotEmpty(customText))
     {