f2d3014a3cc95116346eab4675817cf0c65f6d62
braney
  Tue Aug 18 11:12:35 2026 -0700
hgc: encode item detail text consistently, refs #38123

diff --git src/hg/hgc/hgc.c src/hg/hgc/hgc.c
index bb86647bed5..4222db1b9c7 100644
--- src/hg/hgc/hgc.c
+++ src/hg/hgc/hgc.c
@@ -296,30 +296,43 @@
 char mousedb[] = "mm3";
 
 #define NUMTRACKS 9
 int prevColor[NUMTRACKS]; /* used to optimize color change html commands */
 int currentColor[NUMTRACKS]; /* used to optimize color change html commands */
 int maxShade = 9;	/* Highest shade in a color gradient. */
 Color shadesOfGray[10+1];	/* 10 shades of gray from white to black */
 
 Color shadesOfRed[16];
 boolean exprBedColorsMade = FALSE; /* Have the shades of red been made? */
 int maxRGBShade = 16;
 
 struct bed *sageExpList = NULL;
 char ncbiOmimUrl[255] = {"https://www.ncbi.nlm.nih.gov/omim/"};
 
+char *hubEncode(struct trackDb *tdb, char *text)
+/* Return text escaped for HTML if it belongs to a track hub, otherwise return it unchanged.
+ * A hub's trackDb, autoSql schema and data file are all written by a stranger, so anything
+ * from them has to be escaped before it goes in the page.  Our own tracks are a
+ * different case: they put real HTML in fields on purpose - ClinVar's review-status stars,
+ * the CRISPR track's links in an extra column, the <BR> in the Denisova schema comments -
+ * and escaping those would print the markup instead of rendering it. */
+{
+if (text != NULL && tdb != NULL && isHubTrack(tdb->track))
+    return htmlEncode(text);
+return text;
+}
+
 struct palInfo
 {
     char *chrom;
     int left;
     int right;
     char *rnaName;
 };
 
 
 /* See this NCBI web doc for more info about entrezFormat:
  * https://www.ncbi.nlm.nih.gov/entrez/query/static/linking.html */
 char *entrezFormat = "https://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Search&db=%s&term=%s&doptcmdl=%s&tool=genome.ucsc.edu";
 char *entrezPureSearchFormat = "https://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=PureSearch&db=%s&details_term=%s[%s] ";
 char *ncbiGeneFormat = "https://www.ncbi.nlm.nih.gov/gene/%s";
 char *entrezUidFormat = "https://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=Retrieve&db=%s&list_uids=%d&dopt=%s&tool=genome.ucsc.edu";
@@ -819,38 +832,39 @@
 // its bed-compatible leading fields (only the first bedSize fields are read).
 // At -O3 GCC's -Warray-bounds flags those casts because the real object is
 // smaller than struct bed; the accesses are safe by the bed-layout convention.
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Warray-bounds"
 void bedPrintPos(struct bed *bed, int bedSize, struct trackDb *tdb)
 /* Print first bedSize fields of a bed type structure in
  * standard format. */
 {
 char *strand = NULL;
 if (bedSize >= 4 && bed->name[0] != 0)
     {
     char *label = "Item", *tdbLabel = NULL;
     if (tdb && ((tdbLabel = trackDbSetting(tdb, "bedNameLabel")) != NULL))
 	label = tdbLabel;
-    printf("<B>%s:</B> %s<BR>\n", label, bed->name);
+    // bedNameLabel is a trackDb setting and the name comes from the data file
+    printf("<B>%s:</B> %s<BR>\n", hubEncode(tdb, label), hubEncode(tdb, bed->name));
     }
 if (bedSize >= 5)
     {
     if (!tdb || !trackDbSetting(tdb, "noScoreFilter"))
         {
         char *scoreLabel = trackDbSettingOrDefault(tdb, "scoreLabel", "Score");
-	printf("<B>%s:</B> %d<BR>\n", scoreLabel, bed->score);
+	printf("<B>%s:</B> %d<BR>\n", hubEncode(tdb, scoreLabel), bed->score);
         }
     }
 if (bedSize >= 6)
    {
    strand = bed->strand;
    }
 printPos(bed->chrom, bed->chromStart, bed->chromEnd, strand, TRUE, bed->name);
 
 }
 #pragma GCC diagnostic pop
 
 void genericHeader(struct trackDb *tdb, char *item)
 /* Put up generic track info. */
 {
 if (item != NULL && item[0] != 0)
@@ -987,50 +1001,53 @@
 
 char *eUrl = replaceInUrl(url, itemName, cart, database, seqName, winStart, winEnd, tdb->track, 
                             encode, fields);
 if (eUrl==NULL)
     return;
 
 /* create the url label setting for trackDb from the url
    setting prefix */
 safef(urlLabelSetting, sizeof(urlLabelSetting), "%sLabel", urlSetting);
 char *linkLabel = trackDbSettingOrDefault(tdb, urlLabelSetting, "Outside Link:");
 char *eLinkLabel = replaceInUrl(linkLabel, itemName, cart, database, seqName, winStart, winEnd, tdb->track,
                             encode, fields);
 
 // if we got no item name from hgTracks or the item name does not appear in the URL
 // there is no need to show the item name at all
+// the url and its label come from trackDb and the item name from the data file, both of
+// which a track hub supplies, so escape them
 if (isEmpty(itemName) || !stringIn("$$", url))
     {
-    printf("<A TARGET=_blank HREF='%s'>%s</A><BR>",eUrl, eLinkLabel);
+    printf("<A TARGET=_blank HREF='%s'>%s</A><BR>",hubEncode(tdb, eUrl),
+           hubEncode(tdb, eLinkLabel));
     return;
     }
 
-printf("<B>%s </B>",eLinkLabel);
+printf("<B>%s </B>",hubEncode(tdb, eLinkLabel));
 
-printf("<A HREF=\"%s\" target=_blank>", eUrl);
+printf("<A HREF=\"%s\" target=_blank>", hubEncode(tdb, eUrl));
 
 if (sameWord(tdb->table, "npredGene"))
     {
     printf("%s (%s)</A><BR>\n", itemName, "NCBI MapView");
     }
 else
     {
     char *label = itemName;
     if (isNotEmpty(itemLabel) && differentString(itemName, itemLabel))
         label = itemLabel;
-    printf("%s</A><BR>\n", label);
+    printf("%s</A><BR>\n", hubEncode(tdb, label));
     }
 //freeMem(&eUrl); small memory leak
 }
 
 void printCustomUrlWithFields(struct trackDb *tdb, char *itemName, char *itemLabel, boolean encode,                                                      struct slPair *fields) 
 /* Wrapper to call printCustomUrlWithLabel with additional fields to substitute */
 {
 char urlSetting[10];
 safef(urlSetting, sizeof(urlSetting), "url");
 
 printCustomUrlWithLabel(tdb, itemName, itemLabel, urlSetting, encode, fields);
 }
 
 void printCustomUrl(struct trackDb *tdb, char *itemName, boolean encode)
 /* Wrapper to call printCustomUrlWithLabel using the url setting in trackDb */
@@ -1460,31 +1477,31 @@
 return idNames;
 }
 
 void printIdOrLinks(struct asColumn *col, struct hash *fieldToUrl, struct trackDb *tdb, char *idList)
 /* if trackDb does not contain a "urls" entry for current column name, just print idList as it is.
  * Otherwise treat idList as a comma-sep list of IDs and print one row per id, with a link to url,
  * ($$ in url is OK, wildcards like $P, $p, are also OK)
  * */
 {
 // try to find a fieldName=url setting in the "urls" tdb statement, print id if not found
 char *url = NULL;
 if (fieldToUrl != NULL)
     url = (char*)hashFindVal(fieldToUrl, col->name);
 if (url == NULL)
     {
-    printf("<td class='bedExtraTblVal'>%s</td></tr>\n", idList);
+    printf("<td class='bedExtraTblVal'>%s</td></tr>\n", hubEncode(tdb, idList));
     return;
     }
 
 // split the id into parts and print each part as a link
 struct slName *slIds = slNameListFromComma(idList);
 struct slName *itemId = NULL;
 
 // handle id->name mapping for multi-source items
 int nameCount;
 char **idNames = getIdNameMap(tdb, col, &nameCount);
 
 printf("<td>");
 for (itemId = slIds; itemId!=NULL; itemId = itemId->next) 
     {
     if (itemId != slIds)
@@ -1502,31 +1519,32 @@
     char *idForUrl = itemName;
     boolean encode = TRUE;
     if (strstr(itemName, "|"))
         {
         char *parts[2];
 	chopString(itemName, "|", parts, ArraySize(parts));
         idForUrl = parts[0];
         itemName = parts[1];
         encode = FALSE; // assume the link is already encoded
         }
     if (startsWith("http", itemName)) // the ID may be a full URL already, encoding would destroy it
         encode = FALSE;
 
     char *idUrl = replaceInUrl(url, idForUrl, cart, database, seqName, winStart, 
                     winEnd, tdb->track, encode, NULL);
-    printf("<a href=\"%s\" target=\"_blank\">%s</a>", idUrl, itemName);
+    printf("<a href=\"%s\" target=\"_blank\">%s</a>", hubEncode(tdb, idUrl),
+           hubEncode(tdb, itemName));
     } 
 printf("</td></tr>\n");
 freeMem(slIds);
 //freeMem(idNames);
 }
 
 char *readOneLineMaybeBgzip(char *fileOrUrl, bits64 offset, bits64 len)
 /* If fileOrUrl is bgzip-compressed and indexed, then use htslib's bgzf functions to
  * retrieve uncompressed data from offset; otherwise (plain text) use udc. If len is 0,
  * read up to next '\n' delimiter. */
 {
 char *line = needMem(len+1);
 if (endsWith(fileOrUrl, ".gz"))
     {
     BGZF *fp = bgzf_open(fileOrUrl, "r");
@@ -1970,47 +1988,49 @@
         printf("<br><table class='bedExtraTbl'>");
 
     // split this table to separate current row from the previous one, if the trackDb option is set
     if (sepFields && slNameInList(sepFields, fieldName))
         printf("</tr></table>\n<p>\n<table class='bedExtraTbl'>");
 
     // field description
     char *entry;
     if (sameString(fieldName, "cdsStartStat") && sameString("enum('none','unk','incmpl','cmpl')", col->comment))
         entry = "Status of CDS start annotation (none, unknown, incomplete, or complete)";
     else if (sameString(fieldName, "cdsEndStat") && sameString("enum('none','unk','incmpl','cmpl')", col->comment))
         entry = "Status of CDS end annotation (none, unknown, incomplete, or complete)";
     else
         entry = col->comment;
 
-    printFieldLabelWithId(entry, fieldName);
+    // the field name and its comment come from the autoSql schema, which for a hub bigBed
+    // is written by the hub author
+    printFieldLabelWithId(hubEncode(tdb, entry), hubEncode(tdb, fieldName));
 
     // detailsScript fields: print empty cell, JavaScript will fill it
     if (dsScriptFields && slNameInList(dsScriptFields, fieldName))
         printf("<td></td></tr>\n");
     else if (col->isList || col->isArray || col->lowType->stringy || asTypesIsInt(col->lowType->type))
         printIdOrLinks(col, fieldToUrl, tdb, fields[ix]);
     else if (asTypesIsFloating(col->lowType->type))
         {
         double valDouble = strtod(fields[ix],NULL);
         if (errno == 0 && valDouble != 0)
             printf("<td>%g</td></tr>\n", valDouble);
         else
-            printf("<td>%s</td></tr>\n", fields[ix]); // decided not to print error
+            printf("<td>%s</td></tr>\n", hubEncode(tdb, fields[ix])); // decided not to print error
         }
     else
-        printf("<td class='bedExtraTblVal'>%s</td></tr>\n", fields[ix]);
+        printf("<td class='bedExtraTblVal'>%s</td></tr>\n", hubEncode(tdb, 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)
@@ -3663,33 +3683,35 @@
 	   "&hgta_table=%s&position=%s:%d-%d&"
            "hgta_doSchema=describe+table+schema\" target=ucscSchema title='Open schema in new window'>"
 	   "View table schema</A></P>\n",
 	   database, tdb->grp, trackTable, tdb->table,
 	   seqName, winStart+1, winEnd);
     }
 }
 
 void printTrackUiLink(struct trackDb *tdb)
 /* Make link to hgTrackUi. */
 {
 char *trackName = getParentTrackName(tdb);
 struct trackDb *parentTdb = tdb;
 if (!sameString(trackName, tdb->track))
     parentTdb = hTrackDbForTrack(database, trackName);
+// shortLabel comes from trackDb, which a track hub controls, escape it
 printf("<P><A HREF=\"%s?db=%s&g=%s&%s\">"
        "Go to %s track controls</A></P>\n",
-       hTrackUiForTrack(tdb->track), database, trackName, cartSidUrlString(cart), parentTdb->shortLabel);
+       hTrackUiForTrack(tdb->track), database, trackName, cartSidUrlString(cart),
+       htmlEncode(parentTdb->shortLabel));
 }
 
 void printDataRestrictionDate(struct trackDb *tdb)
 /* If this annotation has a dateUnrestricted trackDb setting, print it */
 {
 char *restrictionDate = encodeRestrictionDateDisplay(database,tdb);
 if (restrictionDate != NULL)
     {
     printf("<A HREF=\"/ENCODE/terms.html\" TARGET=_BLANK><B>Restricted until</A>:</B> %s <BR>\n",
                 restrictionDate);
     freeMem(restrictionDate);
     }
 }
 
 static void printOrigAssembly(struct trackDb *tdb)