b5af24c744cd0f5571634a863c34f208bbd0a199
max
  Wed Sep 23 11:15:40 2026 -0700
hgTrackUi: better error when a faceted composite's metadata file is refused, refs #29344

The 400 error now says which URL was refused and for which track, and
that a hub's metaDataUrl or colorSettingsUrl has to point inside the
hub's directory. facetedComposite.js now shows that text instead of
just "HTTP Status: 400".

Also in facetedComposite.js: facet values with a count of zero are
hidden, unless they are checked, and there is a new "Clear all filters"
button that unchecks all facets and empties the search boxes.

diff --git src/hg/hgTrackUi/hgTrackUi.c src/hg/hgTrackUi/hgTrackUi.c
index bbb9878110f..d2d55bd1c84 100644
--- src/hg/hgTrackUi/hgTrackUi.c
+++ src/hg/hgTrackUi/hgTrackUi.c
@@ -4473,67 +4473,80 @@
 /* Checks if a requested file is a legal request based on an attached cart or
  * native track.  If so, retrieves the file content via UDC and retransmits
  * it as the page content. */
 {
 char *genome = NULL;
 getDbAndGenome(cart, &database, &genome, NULL);
 initGenbankTableNames(database);
 
 char *fileUrl = cartOptionalString(cart, "fileUrl");
 char *urlClone = cloneString(fileUrl);
 cgiDecode(urlClone, urlClone, strlen(urlClone));
 fileUrl = resolveDotDots(urlClone);
 freeMem(urlClone);
 
 boolean matchFound = FALSE;
+char *track = cartOptionalString(cart, "track");
 
 // Check if fileUrl falls under a connected hub's base directory
 struct hubConnectStatus *hubStatusList = hubConnectStatusListFromCartAll(cart);
 struct hubConnectStatus *hubStatus = hubStatusList;
 while (hubStatus != NULL)
     {
     if (isEmpty(hubStatus->errorMessage) && fileUrlMatchesHub(fileUrl, hubStatus))
         {
         matchFound = TRUE;
         break;
         }
     hubStatus = hubStatus->next;
     }
 
 // For native database tracks and curated hub tracks, check if fileUrl matches a
 // whitelisted trackDb setting.  Only these are checked here because their settings are
 // admin-configured and trusted.  User hub and custom track settings are user-controlled
 // and could be used for SSRF attacks.
 if (!matchFound)
     {
-    char *track = cartOptionalString(cart, "track");
     char *sourceDb = cartOptionalString(cart, "sourceDb"); // for future quickLift use
     if (sourceDb == NULL)
         sourceDb = database;
     if (track != NULL && !isCustomTrack(track) &&
         (!isHubTrack(track) || trackIsFromCuratedHub(sourceDb, track, hubStatusList)))
         {
         struct trackDb *tdb = tdbForTrack(sourceDb, track, NULL);
         if (tdb != NULL)
             matchFound = fileUrlMatchesTrackSetting(fileUrl, tdb);
         }
     }
 
 if (!matchFound)
     {
+    struct dyString *dy = dyStringNew(512);
+    dyStringPrintf(dy, "Requested URL '%s' does not fall under any connected hub's "
+                       "directory, and does not match a whitelisted trackDb setting.",
+                       fileUrl);
+    if (isNotEmpty(track))
+        dyStringPrintf(dy, "  This URL is the value of a fetchable trackDb setting "
+                           "('metaDataUrl' or 'colorSettingsUrl') in the stanza for "
+                           "track '%s'.  Those settings name, respectively, the TSV "
+                           "table of per-subtrack metadata and the JSON file of facet "
+                           "colors used to build the filter/metadata table on this "
+                           "page.  For a track hub, such a URL must point to a file "
+                           "inside the hub's own directory (alongside hub.txt), not an "
+                           "external URL.", track);
     puts("Status: 400 Bad Request");
-    errAbort("Supplied fileUrl does not match any connected hubs or track settings.");
+    errAbort("%s", dyStringContents(dy));
     }
 
 // By now we know that fileUrl points to something valid to fetch and return to the user.
 // Now we just have to fetch the file contents and retransmit it.
 
 int timeout = cartUsualInt(cart, "udcTimeout", 300);
 if (udcCacheTimeout() < timeout)
     udcSetCacheTimeout(timeout);
 
 struct udcFile *udc = udcFileMayOpen(fileUrl, NULL);
 if (udc == NULL)
     {
     puts("Status: 404 Not Found");
     cgiPrintContentType("text/plain");
     printf("Error: could not open %s\n", fileUrl);