53621f3c5ef75891a7b04cf18d85180677a38f23
chmalee
  Wed Apr 8 11:38:53 2026 -0700
Fix bug in hgc user defined table printing where we overwrote the variable that held the data for each custom table. Also fix a XSS vector with the table titles and some related code cleanup, refs #37340

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index 84614360b20..ed9efe08ccb 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -1636,36 +1636,39 @@
     {
     if (startsWith("_json", thisTbl->field) || startsWith("json", thisTbl->field))
         {
         struct jsonElement *jsElem = NULL;
         struct errCatch *errCatch = errCatchNew();
         if (errCatchStart(errCatch))
             jsElem = jsonParse(thisTbl->encodedTbl);
         errCatchEnd(errCatch);
         if (errCatch->gotError)
             warn("ERROR: JSON field '%s' for track '%s' is malformed: %s", thisTbl->field, tdb->track, errCatch->message->string);
         else if (errCatch->gotWarning)
             warn("Warning: %s", errCatch->message->string);
         errCatchFree(&errCatch);
         if (jsElem != NULL)
             {
-            dyStringPrintf(dy, "{label: \"%s\", data: %s},", thisTbl->title != NULL ? thisTbl->title : thisTbl->field, thisTbl->encodedTbl);
+            char *labelStr = thisTbl->title != NULL ?
+                    jsonStringEscape(thisTbl->title) :
+                    jsonStringEscape(thisTbl->field);
+            dyStringPrintf(dy, "{label: \"%s\", data: %s},", labelStr, thisTbl->encodedTbl);
             }
         }
     else
         {
-        printf("<tr><td>%s</td><td>", thisTbl->title);
+        printf("<tr><td>%s</td><td>", htmlEncode(thisTbl->title));
         printf("<table class='jsonTable'>\n");
         printf("<tr><td>");
         char table[4096];
         safef(table, sizeof(table), "%s", thisTbl->encodedTbl);
         int swapped = strSwapStrs(table, 4096, ";", "</td></tr><tr><td>");
         if (swapped == -1)
             errAbort("Error substituting ';' for '</tr><tr>' in hgc.c:printEmbeddedTable()");
         swapped = strSwapStrs(table, 4096, "|", "</td><td>");
         if (swapped == -1)
             errAbort("Error substituting '|' for '</td><td>' in hgc.c:printEmbeddedTable()");
         printf("%s</tr>\n", table);
         printf("</table>\n");
         printf("</td></tr>\n");
         }
     }
@@ -1713,46 +1716,48 @@
 return foundFields;
 }
 
 #define TDB_DYNAMICTABLE_SETTING "detailsDynamicTable"
 #define TDB_DYNAMICTABLE_SETTING_2 "extraTableFields"
 void getExtraTableFields(struct trackDb *tdb, struct slName **retFieldNames, struct embeddedTbl **retList, struct hash *embeddedTblHash)
 /* Parse the trackDb field TDB_DYNAMICTABLE_FIELD into the field names and titles specified,
  * and fill out a hash keyed on the bigBed field name (which may be in an external file
  * and not in the bigBed itself) to a helper struct for storing user defined tables. */
 {
 struct slName *tmp, *embeddedTblSetting = slNameListFromComma(trackDbSetting(tdb, TDB_DYNAMICTABLE_SETTING));
 struct slName *embeddedTblSetting2 = slNameListFromComma(trackDbSetting(tdb, TDB_DYNAMICTABLE_SETTING_2));
 char *title = NULL, *fieldName = NULL;
 for (tmp = embeddedTblSetting; tmp != NULL; tmp = tmp->next)
     {
+    title = NULL;
     fieldName = cloneString(tmp->name);
     if (strchr(tmp->name, '|'))
         {
         title = strchr(fieldName, '|');
         *title++ = 0;
         }
     struct embeddedTbl *new;
     AllocVar(new);
     new->field = fieldName;
     new->title = title != NULL ? cloneString(title) : fieldName;
     slAddHead(retList, new);
     slNameAddHead(retFieldNames, fieldName);
     hashAdd(embeddedTblHash, fieldName, new);
     }
 for (tmp = embeddedTblSetting2; tmp != NULL; tmp = tmp->next)
     {
+    title = NULL;
     fieldName = cloneString(tmp->name);
     if (strchr(tmp->name, '|'))
         {
         title = strchr(fieldName, '|');
         *title++ = 0;
         }
     struct embeddedTbl *new;
     AllocVar(new);
     new->field = fieldName;
     new->title = title != NULL ? cloneString(title) : fieldName;
     slAddHead(retList, new);
     slNameAddHead(retFieldNames, fieldName);
     hashAdd(embeddedTblHash, fieldName, new);
     }
 }
@@ -1986,39 +1991,45 @@
     else
         printf("<td class='bedExtraTblVal'>%s</td></tr>\n", fields[ix]);
     printCount++;
     }
 if (skipIds)
     slFreeList(skipIds);
 if (sepFields)
     slFreeList(sepFields);
 
 if (embeddedTblFields)
     {
     printf("<br><table class='bedExtraTbl'>\n");
 
     struct embeddedTbl *thisTbl;
     struct dyString *tableLabelsDy = dyStringNew(0);
+    boolean initted = FALSE;
     for (thisTbl = embeddedTblList; thisTbl != NULL; thisTbl = thisTbl->next)
         {
         if (thisTbl->encodedTbl)
             {
+            if (!initted)
+                {
+                initted = TRUE;
                 dyStringPrintf(tableLabelsDy, "var _jsonHgcLabels = [");
+                }
             printEmbeddedTable(tdb, thisTbl, tableLabelsDy);
-            dyStringPrintf(tableLabelsDy, "];\n");
             }
         }
+    if (initted)
+        dyStringPrintf(tableLabelsDy, "];\n");
 
     jsInline(dyStringCannibalize(&tableLabelsDy));
     }
 
 if (printCount > 0)
     printf("</table>\n");
 
 
 if (detailsTableFields)
     {
     printExtraDetailsTable(tdb->track, extraDetailsTableName, extraDetails, extraTblStr);
     }
 if (detailsTable2Fields)
     {
     printExtraDetailsTable(tdb->track, extraDetails2TableName, extraDetails2, extraTbl2Str);