e08ae97dc288749c2454fd98a10dc79f06e27cb5
max
  Tue Aug 11 06:16:20 2026 -0700
hgBlat/hgc: fixes from the v502 code review of the BLAT results pages, refs #37893

#4 blatOldTracks now falls back to "keep" (with a warning) on any value other
than keep/hide/delete, instead of dropping into the destructive delete branch,
so a typo in hg.conf can never silently discard a user's earlier BLAT tracks.

#5 htmlEncode() (js/utils.js) now also escapes " and ', which the browser's
text->markup conversion leaves alone. Every hgBlat caller puts the result in a
double-quoted attribute, so an unescaped quote in the cart position string could
break out of the attribute; the shared helper now honors its documented contract.

#6 The non-BLAT alignment title no longer starts with a stray space.

#7 open_memstream() is checked for NULL: on that failure the alignment renders
straight to stdout instead of writing to a NULL FILE and calling fclose(NULL).
The section reorder keys on the literal "<H4><A NAME=genomic>"/"<H4><A NAME=ali>"
strings, so a note was added in fuzzyShow.c and pslShow.c to keep them intact.

Found in the v502 final-build code review, refs #38069.

diff --git src/hg/js/utils.js src/hg/js/utils.js
index 72bbd6f4408..da93bb1447a 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -4308,32 +4308,38 @@
     ele.style.visibility = "hidden";
 }
 
 function titleTagToMouseover(mapEl) {
     /* for a given area tag, extract the title text into a div that can be positioned
     * like a standard tooltip mouseover next to the item */
     if (mapEl.dataset.tooltip)
         addMouseover(mapEl, mapEl.dataset.tooltip);
     else
         addMouseover(mapEl, mapEl.title);
 }
 
 function htmlEncode(s) {
     /* HTML-escape a value (&, <, >, ", ') so it is safe to insert as text or into an attribute
      * value in a string of HTML.  Shared helper: prefer this over rolling a per-file escaper.
-     * Uses the browser's own text->markup conversion via a detached element (jQuery required). */
-    return $('<div>').text(s === null || s === undefined ? '' : String(s)).html();
+     * Uses the browser's own text->markup conversion via a detached element (jQuery required).
+     * That conversion only escapes &, < and > - quotes need no escaping in text, so it leaves them
+     * alone - hence the explicit quote handling below.  Without it this function silently failed
+     * the "safe in an attribute" half of its contract: a value containing a double quote closed the
+     * attribute early, truncating it (and worse, allowing markup injection). */
+    return $('<div>').text(s === null || s === undefined ? '' : String(s)).html()
+        .replace(/"/g, '&quot;')
+        .replace(/'/g, '&#39;');
 }
 
 function convertTitleTagsToMouseovers() {
     /* make all the title tags in the document have mouseovers */
     document.querySelectorAll("[title],[data-tooltip]").forEach(function(a, i) {
         if (a.id !== "" && (a.id === "hotkeyHelp" || a.id.endsWith("Dialog") || a.id.endsWith("Popup"))) {
             // these divs are populated by ui-dialog, they should not have tooltips
             return;
         }
         if (a.title !== undefined &&
                 (a.title.startsWith("click & drag to scroll") || a.title.startsWith("drag select or click to zoom")))
             a.title = "";
         else if ((a.title !== undefined && a.title.length > 0) || a.getAttribute("data-tooltip") !== null) {
             if (a.title && a.title.startsWith("Click to alter the display density")) {
                 // these tooltips have a longer delay: