3842ba31ab0b697b0d4026b5751da7dac7c84e35
braney
  Tue Sep 29 13:49:49 2026 -0700
htmlSanitize: keep the stdTbl and copyLinkSpan class names, refs #38126

GenArk description pages use these two classes from our own stylesheet and
scripts: stdTbl for bordered tables, and copyLinkSpan with data-target for
the Copy button next to the share link.  They now come through, and
data-target gets the same prefix as the id it names.

Style values are now read the same way on every pass, so a page that is
saved again comes out unchanged.

Over the 5390-page hub corpus, 278 pages change, all in class or
data-target only, and no page's text changes.

diff --git src/lib/tests/htmlSanitizeTest.c src/lib/tests/htmlSanitizeTest.c
index 6d4595e3670..e95f95f1134 100644
--- src/lib/tests/htmlSanitizeTest.c
+++ src/lib/tests/htmlSanitizeTest.c
@@ -72,30 +72,45 @@
     "<circle r=\"1\"></circle></svg>",
 /* nothing in a drawing may run, link, load or animate */
 "<svg onload=\"alert(1)\"><script>alert(1)</script>"
     "<a href=\"javascript:alert(1)\"><rect width=\"10\" height=\"10\"/></a>"
     "<use href=\"#x\"/><animate attributeName=\"href\" to=\"javascript:alert(1)\"/>"
     "<image href=\"https://example.com/x.png\"/>"
     "<foreignObject><p>text</p></foreignObject></svg>",
 /* a fill or a stroke cannot fetch anything, however the url is spelled */
 "<svg><circle r=\"1\" fill=\"url(https://example.com/x.svg#p)\"/>"
     "<rect stroke=\"u&#114;l(https://example.com/y)\" fill=\"#c00\"/></svg>",
 /* a shape that only clips another is not drawn on its own */
 "<svg><defs><clipPath id=\"c\"><path d=\"M0 0L9 9\"/></clipPath></defs>"
     "<rect width=\"9\" height=\"9\"/></svg>",
 /* a shape outside a drawing loses its tag */
 "<circle r=\"5\"/>text after",
+/* a class survives only when it is one of ours */
+"<table class=\"stdTbl wide\"><tr><td class=\"copyLinkSpanX\">cell</td></tr></table>",
+/* the share link on a GenArk page keeps its copy button, and the button still finds the link */
+"<span id='urlText0'><em>https://http_host/h/GCA_1</em></span>"
+    "<span class='copyLinkSpan' data-target='urlText0'></span>",
+/* a quote written as a reference does not cut the declaration in two, so our own output
+ * reads back unchanged */
+"<p style='font-family:\"Verdana\",sans-serif;color:black'>a</p>",
+/* nor does the semicolon of a number, and the declaration after a refused one survives */
+"<p style=\"list-style:u&#114;l(http://example.com/x); color:green\">a</p>",
+"<p style=\"color:re&#100;;text-align:center\">a</p>",
+/* a name a browser would decode to a parenthesis is refused */
+"<p style=\"list-style:url&lpar;//example.com/x.png);color:red\">a</p>",
+/* a name a browser does not know leaves its semicolon to end the declaration */
+"<p style=\"color:red&foo;position:fixed\">a</p>",
 };
 
 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");