8f1e82cfa7e46ca358c97ea135bf20cf0bbf6026
max
  Sun Oct 4 10:49:13 2026 -0700
hVarSubst/trackHub: encode a hub genome's organism/date text instead of rejecting it

The earlier character-exclusion check could reject a hub whose organism or
freeze/date label uses ordinary punctuation that has every right to be there.
Replace it with a narrow %-encode of the two characters that mattered for
where this text gets substituted, applied at render time; everything else
passes through unchanged, refs #38399

diff --git src/hg/lib/trackHub.c src/hg/lib/trackHub.c
index 115692885fb..b33d3cdc549 100644
--- src/hg/lib/trackHub.c
+++ src/hg/lib/trackHub.c
@@ -525,42 +525,30 @@
 return str;
 }
 
 static void checkHubIdName(char *type, char *name)
 /* Abort if name holds a character that is not valid in an identifier.  A hub
  * identifier - a genome name, a group name - is printed into dozens of URLs,
  * form values and attributes all over the CGIs, so the check belongs here rather than at every
  * one of those places.  Real names use letters, digits, underscore, dot and dash. */
 {
 if (strchr(name, '<') || strchr(name, '>') || strchr(name, '"')
     || strchr(name, '\'') || strchr(name, '&'))
     errAbort("Bad %s name: \"%s\". The characters < > \" ' and & are not allowed in a %s name.",
              type, name, type);
 }
 
-static void checkHubDisplayText(char *type, char *name)
-/* Abort if name holds a character that is not valid in a short display label (a hub's
- * organism name or freeze/date label).  These get substituted into all sorts of contexts
- * in the CGIs, including attribute values, so keep the same restriction as checkHubIdName
- * plus the punctuation an attribute value has no legitimate use for. Real labels use
- * letters, digits, spaces and ordinary punctuation like . , - / ( ). */
-{
-if (strpbrk(name, "<>\"'&:;") || strchr(name, '\n') || strchr(name, '\r'))
-    errAbort("Bad %s: \"%s\". The characters < > \" ' & : ; and newlines are not allowed in a %s.",
-             type, name, type);
-}
-
 struct grp *readGroupRa(char *groupFileName)
 /* Read in the ra file that describes the groups in an assembly hub. */
 {
 if (groupFileName == NULL)
     return NULL;
 struct hash *ra;
 struct grp *list = NULL;
 struct lineFile *lf = udcWrapShortLineFile(groupFileName, NULL, MAX_HUB_GROUP_FILE_SIZE);
 while ((ra = raNextRecord(lf)) != NULL)
     {
     char *str;
     struct grp *grp;
     AllocVar(grp);
     slAddHead(&list, grp);
 
@@ -739,37 +727,34 @@
     AllocVar(el);
     el->name = cloneString(genome);
     el->trackDbFile = trackHubRelativeUrl(url, trackDb);
     el->trackHub = hub;
     hashAdd(hash, el->name, el);
     hashAdd(hash, hubConnectSkipHubPrefix(el->name), el);
     slAddHead(&list, el);
     char *orderKey = hashFindVal(ra, "orderKey");
     if (orderKey != NULL)
 	el->orderKey = sqlUnsigned(orderKey);
 
     char *groups = hashFindVal(ra, "groups");
     if (twoBitPath != NULL)
 	{
 	el->description  = hashFindVal(ra, "description");
-	if (el->description != NULL)
-	    checkHubDisplayText("genome description", el->description);
 	char *organism = hashFindVal(ra, "organism");
 	if (organism == NULL)
 	    errAbort("must have 'organism' set in assembly hub in stanza ending line %d of %s",
 		     lf->lineIx, lf->fileName);
-	checkHubDisplayText("organism", organism);
 	el->organism  = addHubName(organism, hub->name);
 	hashReplace(ra, "organism", el->organism);
 	el->defaultPos  = hashFindVal(ra, "defaultPos");
 	if (el->defaultPos == NULL)
 	    errAbort("must have 'defaultPos' set in assembly hub in stanza ending line %d of %s",
 		     lf->lineIx, lf->fileName);
 	el->twoBitPath = trackHubRelativeUrl(url, twoBitPath);
         if (twoBitBptUrl != NULL)
             el->twoBitBptUrl = trackHubRelativeUrl(url, twoBitBptUrl);
 
 	char *htmlPath = hashFindVal(ra, "htmlPath");
 	if (htmlPath != NULL)
 	    hashReplace(ra, "htmlPath",trackHubRelativeUrl(url, htmlPath));
 	if (groups != NULL)
 	    el->groups = trackHubRelativeUrl(url, groups);