304e190d0af4be54569ac20edc26999673c44f8b
braney
Tue Aug 18 11:05:19 2026 -0700
hgTrackUi, hui: encode trackDb-derived label text consistently, refs #38123
diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 982ef64ad30..743ed56095a 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -206,41 +206,44 @@
dyStringAppend(dyLink,suffix); // Don't encode since this may contain HTML
freeMem(encTerm);
freeMem(encValue);
return dyStringCannibalize(&dyLink);
}
char *pairsAsHtmlTable( struct slPair *pairs, struct trackDb *tdb, boolean showLongLabel,boolean showShortLabel)
/* Return a string which is an HTML table of the tags for this track. */
{
if (pairs == NULL)
return "";
struct dyString *dyTable = dyStringCreate("
");
+// the labels and the metadata pairs all come from trackDb, which a hub controls, escape
if (showLongLabel)
- dyStringPrintf(dyTable,"
%s
",tdb->longLabel);
+ dyStringPrintf(dyTable,"
%s
",
+ htmlEncode(tdb->longLabel));
if (showShortLabel)
dyStringPrintf(dyTable,"
");
return dyStringCannibalize(&dyTable);
}
char *metadataAsHtmlTable(char *db,struct trackDb *tdb,boolean showLongLabel,boolean showShortLabel)
// If metadata from metaDb exists, return string of html with table definition
{
struct slPair *pairs = NULL;
if ((pairs = trackDbMetaPairs(tdb)) != NULL)
return pairsAsHtmlTable(pairs, tdb, showLongLabel, showShortLabel);
const struct mdbObj *safeObj = metadataForTable(db,tdb,NULL);
if (safeObj == NULL || safeObj->vars == NULL)
@@ -2959,41 +2962,50 @@
}
members = needMem(sizeof(members_t));
members->setting = cloneString(setting);
#define MAX_SUBGROUP_MEMBERS 2000
char *words[MAX_SUBGROUP_MEMBERS+3]; // members preceded by tag and title, one extra to detect
count = chopLine(members->setting, words);
if (count == ArraySize(words))
warn("Subgroup %s exceeds maximum %d members", words[1], MAX_SUBGROUP_MEMBERS);
if (count <= 1)
{
freeMem(members->setting);
freeMem(members);
tdbExtrasMembersSet(parentTdb, groupNameOrTag, &nullMember);
return NULL;
}
+// A subGroup label from a track hub is text from a stranger and is printed into the page in
+// a dozen places, so escape it here, once. Only for hub tracks: our own trackDb puts real
+// HTML entities in these labels on purpose (the ENCODE composites use and &alpha),
+// and escaping those would show the entity text instead of the character.
+boolean escapeLabels = isHubTrack(parentTdb->track);
members->groupTag = words[0];
members->groupTitle = strSwapChar(words[1],'_',' '); // Titles replace '_' with space
+if (escapeLabels)
+ members->groupTitle = htmlEncode(members->groupTitle);
members->tags = needMem(count*sizeof(char*));
members->titles = needMem(count*sizeof(char*));
for (ix = 2,members->count=0; ix < count; ix++)
{
char *name,*value;
if (parseAssignment(words[ix], &name, &value))
{
members->tags[members->count] = tagEncode(name);
members->titles[members->count] = strSwapChar(value,'_',' ');
+ if (escapeLabels)
+ members->titles[members->count] = htmlEncode(members->titles[members->count]);
members->count++;
}
else
{
warn("Subgroup \"%s\" is missing a tag=val pair", words[1]);
}
}
tdbExtrasMembersSet(parentTdb, groupNameOrTag, members);
return members;
}
static int membersSubGroupIx(members_t* members, char *tag)
// Returns the index of the subgroup within the members struct (or -1)
{
@@ -4404,34 +4416,35 @@
jsIncludeFile("ui.dropdownchecklist.js",NULL);
jsIncludeFile("ddcl.js",NULL);
}
// TODO: columnCount (Number of filterBoxes per row) should be configurable through tdb setting
for (filterBy = filterBySet; filterBy != NULL; filterBy = filterBy->next)
{
puts("
");
char selectStatement[4096];
char *setting = getFilterType(cart, tdb, filterBy->column, FILTERBY_DEFAULT);
if (filterByColumnIsMultiple(cart, tdb, setting))
safef(selectStatement, sizeof selectStatement, " (select multiple items - %s)", FILTERBY_HELP_LINK);
else
selectStatement[0] = 0;
+ // the title is filterLabel. or an autoSql column comment, both hub supplied
if(count == 1)
- printf("%s by %s%s",filterTypeTitle,filterBy->title,selectStatement);
+ printf("%s by %s%s",filterTypeTitle,htmlEncode(filterBy->title),selectStatement);
else
- printf("%s",filterBy->title);
+ printf("%s",htmlEncode(filterBy->title));
puts("
Match if ");
@@ -4478,43 +4491,43 @@
{
safef(varName, sizeof(varName), "%d",valIx);
name = varName;
label = slValue->name;
}
else
{
label = (filterBy->valueAndLabel ? slValue->name + strlen(slValue->name)+1
: slValue->name);
name = slValue->name;
}
printf("\n",label);
+ printf(">%s\n",htmlEncode(label)); // filterValues are hub supplied
}
printf("\n");
puts("
");
}
puts("
");
}
void filterBySetCfgUi(struct cart *cart, struct trackDb *tdb,
filterBy_t *filterBySet, boolean onOneLine, char *prefix)
/* Does the filter UI for a list of filterBy structure */
{
char selectIdPrefix[4096];
safef(selectIdPrefix, sizeof(selectIdPrefix), "fbc_%s", prefix);
// Our checklists use ddcl.js, which doesn't seem to play nicely when elements have id strings that include '.'
@@ -5556,31 +5569,32 @@
printf("
Missing subgroup
");
}
else
{
if (ix >= 0)
{
char *term = membership->membership[ix];
char *title = membership->titles[ix];
char *titleRoot=NULL;
if (cvTermIsEmpty(col, title))
titleRoot = cloneString(" ");
else
titleRoot = labelRoot(title, NULL);
// Each sortable column requires hidden goop (in the "abbr" field currently)
// which is the actual sort on value
- printf("
", subtrack->track, col, term);
+ printf("
", subtrack->track, col,
+ htmlEncode(term));
printf(" ");
char *link = NULL;
if (vocabHash)
{
struct hash *colHash = hashFindVal(vocabHash, col);
if (colHash)
link = vocabLink(colHash, term, titleRoot);
}
printf("%s", link ? link : titleRoot);
puts("
");
freeMem(titleRoot);
}
else if (sameString(col, SUBTRACK_COLOR_SUBGROUP))
{
char *hue = subtrackColorToCompare(subtrack);
@@ -5592,36 +5606,36 @@
}
}
}
else // Non-sortable tables do not have sort by columns but will display a short label
{ // (which may be a configurable link)
if (settings->colorPatch)
{
printf("