fcf433a736a8670c9da0e2af3ac54db24b0aaa3b
braney
  Mon Aug 24 14:47:47 2026 -0700
htmlSanitize: rename the ids that come in with the HTML, refs #38126

The description HTML is printed inside a page of ours, and it is our own
JavaScript that looks ids up.  A page that carries an id we already use puts
two elements of that name in one document, and getElementById returns whichever
comes first.  Two ids in the public hub pages collide with ours today, one of
them "content".  An id also becomes a property of that name on window, which
reaches the guards we write as "typeof X !== 'undefined' && X".

Every id, and every name on an anchor, now gets the prefix descPage- .  The
hyphen means the window property it makes can never be spelled as a JavaScript
name, so it cannot stand in for one of our globals either.  A link to a name on
the same page, href="#x", is rewritten with the same prefix and keeps working.
A link that names another page is left alone: it leaves our page for the hub's
own file, where the names are unchanged.

Measured over the 5390 description pages of the public hubs: 1643 ids and
anchor names renamed, no id left without the prefix, and the number of same
page links with nothing to point at is 22 before and 22 after, the same ones.
No page loses reader-visible text and the hubCheck warnings are unchanged at
118 pages, since a reader sees nothing of this.

diff --git src/lib/tests/htmlSanitizeTest.c src/lib/tests/htmlSanitizeTest.c
index df0f96b8c48..5f18da8167a 100644
--- src/lib/tests/htmlSanitizeTest.c
+++ src/lib/tests/htmlSanitizeTest.c
@@ -1,70 +1,74 @@
 /* htmlSanitizeTest - check that htmlSanitize keeps what it should and drops the rest. */
 
 /* Copyright (C) 2026 The Regents of the University of California
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 
 #include "common.h"
 #include "htmlSanitize.h"
 
 static char *cases[] = {
 /* a whole pasted document comes out as the article it was meant to be */
 "<html><head><title>T</title><meta charset=\"utf-8\"></head><body class=\"x\">"
     "<h2>Head</h2><p>Text</p></body></html>",
 /* script and style go, with their contents */
 "<p>before</p><script>alert(1)</script><style>body{visibility:hidden}</style><p>after</p>",
 /* a script that is never closed takes the rest with it */
 "<p>before</p><script>if (a < b) alert(1)",
 /* event handlers and class go, id and title stay */
 "<div id=\"top\" class=\"warn\" title=\"t\" onclick=\"alert(1)\">text</div>",
 /* an entity encoded scheme is still that scheme, however it is spelled */
 "<a href=\"&#106;avascript:alert(1)\">one</a> <a href=\"java&Tab;script:alert(1)\">two</a>",
 "<a href=\"&#0000000106;avascript:alert(1)\">three</a> <a href=\"&#106avascript:alert(1)\">four</a>",
 "<a href=\"jav&#9;ascript:alert(1)\">five</a> <a href=\"java&colon;script:alert(1)\">six</a>",
 /* an address that only looks encoded is left working */
 "<a href=\"&#109;ailto:help&#64;riken.jp?subject=x\">write to us</a>",
 /* ordinary links are left alone, and a new window does not get a handle on ours */
 "<a href=\"https://genome.ucsc.edu\">u</a> <a href=\"#anchor\" target=\"_blank\">a</a>",
 /* an image keeps its source, not its onerror */
 "<img src=\"pic.png\" alt=\"a\" onerror=\"alert(1)\" width=\"20\">",
 /* a frame survives only when it plays a video from a host we know */
 "<iframe width=\"560\" src=\"https://www.youtube.com/embed/abc\"></iframe>"
     "<iframe src=\"https://example.com/x\">fallback</iframe>",
 /* the style attribute is filtered a property at a time */
 "<p style=\"color:red;behavior:url(#default#VML);text-align:center;position:fixed\">p</p>",
 /* unknown elements lose their tag and keep their text */
 "<o:p>word</o:p><vertebrates>more</vertebrates>",
 /* tags left open are closed for us, and a slash does not close one of these */
 "<div><b>bold<p>para",
 "<div style=\"color:red\"/>the rest of the page is not inside that div",
 /* a page built of tags that are never closed is not a way to make us work all day */
 "<svg><svg><svg><svg>text",
 /* a stray quote inside an unquoted value does not swallow the page */
 "<a href=https://example.com/x\\\">link text</a> and more text",
 /* a form and everything in it goes */
 "<form action=\"/x\"><input name=\"password\"><button>Log in</button></form><p>after</p>",
 /* comments and doctypes go */
 "<!DOCTYPE html><!-- <p>hidden</p> --><p>shown</p>",
+/* an id, and a name on an anchor, are renamed, and a link to one of them is renamed too */
+"<h2 id=\"methods\">M</h2><a name=\"top\">t</a>"
+    "<a href=\"#methods\">same page</a> <a href=\"other.html#methods\">other page</a>"
+    "<a href=\"#\">to the top</a><div id=\"\">no name at all</div>",
 /* a table keeps its shape */
 "<table border=\"1\"><tr><td colspan=\"2\" bgcolor=\"#eee\">cell</td></tr></table>",
 };
 
 int main(int argc, char *argv[])
 {
 int i;
 for (i = 0;  i < ArraySize(cases);  ++i)
     {
     char *clean = htmlSanitize(cases[i]);
     printf("in : %s\nout: %s\n", cases[i], clean);
     struct slName *removed = NULL, *el;
     freeMem(clean);
     clean = htmlSanitizeReport(cases[i], &removed);
     for (el = removed;  el != NULL;  el = el->next)
         printf("     (%s)\n", el->name);
     printf("\n");
     freeMem(clean);
     slFreeList(&removed);
     }
 if (htmlSanitize(NULL) != NULL)
     errAbort("htmlSanitize(NULL) should be NULL");
 return 0;
 }