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 "

"/"

" 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/hgc/hgc.c src/hg/hgc/hgc.c index fef34392649..98409105b14 100644 --- src/hg/hgc/hgc.c +++ src/hg/hgc/hgc.c @@ -9193,46 +9193,58 @@ qName, chrom, tStartC, tEndC, idColor, ident, matchC, qSizeC, psl->strand); if (isNotEmpty(aliasStr)) printf("

Genome sequence %s is also known as: %s.

\n", chrom, aliasStr); /* The shared library returns the number of alignment blocks it actually shows. The DNA path merges * blocks separated by gaps <= 8 bases, so this can be fewer than psl->blockCount; use it (not * psl->blockCount) so the sidebar's "Block N" links match the #1..#N anchors that were emitted. */ int blockCount; /* Capture the shared library's alignment HTML so we can reorder its sections for this page. The * library emits them as Query (#cDNA), Genome (#genomic), then Side-by-side (#ali), with the * per-block anchors living inside the Genome section. We want Query, Side-by-side, Genome so the * long per-block list sits at the bottom of both the page and the sidebar. Reorder here, on the * server, rather than in JS, so the page does not reflow after it loads. */ char *alnHtml = NULL; size_t alnLen = 0; FILE *alnF = open_memstream(&alnHtml, &alnLen); +if (alnF == NULL) + { + /* open_memstream failed (out of memory): render straight to stdout, skipping the section + * reorder, rather than passing a NULL FILE to the renderer and then calling fclose(NULL). */ + if (qType == gftRna || qType == gftDna) + blockCount = showPartialDnaAlignment(psl, oSeq, stdout, cdsS, cdsE, FALSE); + else + blockCount = showGfAlignment(psl, oSeq, stdout, qType, qStart, qEnd, qName); + } +else + { if (qType == gftRna || qType == gftDna) blockCount = showPartialDnaAlignment(psl, oSeq, alnF, cdsS, cdsE, FALSE); else blockCount = showGfAlignment(psl, oSeq, alnF, qType, qStart, qEnd, qName); fclose(alnF); char *pGenome = (alnHtml != NULL) ? stringIn("

", alnHtml) : NULL; char *pAli = (alnHtml != NULL) ? stringIn("

", alnHtml) : NULL; if (pGenome != NULL && pAli != NULL && pGenome < pAli) { /* Query, then Side-by-side, then Genome */ fwrite(alnHtml, 1, pGenome - alnHtml, stdout); /* legend + Query (#cDNA) section */ fputs(pAli, stdout); /* Side-by-side (#ali) section, through footnote */ fwrite(pGenome, 1, pAli - pGenome, stdout); /* Genome (#genomic) section, with block anchors */ } else fputs((alnHtml != NULL) ? alnHtml : "", stdout); free(alnHtml); /* libc free: open_memstream's buffer is malloc'd, not a kent needMem block */ + } printf("\n"); /* #blatAlnContent */ /* Sidebar, emitted after the alignment so blockCount is known; CSS grid puts it back in column 1. * The inner div is position:sticky so the links stay in view as the long alignment scrolls. */ printf("
\n"); printf("Only query sequence\n" "Side by side alignment\n" "Only genome sequence\n"); if (blockCount > 1) /* per-block jump links, indented under the genome-sequence item where their anchors live */ { int bi; printf("
\n"); for (bi = 1; bi <= blockCount; ++bi) printf("Block %d\n", bi, bi); printf("
\n"); @@ -27494,30 +27506,40 @@ if (isProt) extraForMismatch = ""; char buffer[4096]; safef(buffer, sizeof buffer, customTextTemplate, bigBedTn.forCgi, host, extraForMismatch, bigBedTn.forCgi, trackName, trackDescription); struct customTrack *ctList = getCtList(); struct customTrack *newCts = customFactoryParse(database, buffer, FALSE, NULL, NULL); /* Optionally clear PREVIOUS BLAT result tracks (those tagged blatResult=on) so the user is not * confused about which results are current. hg.conf "blatOldTracks": * keep (default) - do nothing, every search's track stays as-is * hide - leave earlier BLAT tracks in the session but set them to hide * delete - remove earlier BLAT tracks from the session (their trash files age out) * Only BLAT-tagged tracks are touched; the track just made is left alone. */ char *oldTracks = cfgOptionDefault("blatOldTracks", "keep"); +/* Fail safe on an unrecognized value (e.g. a typo in hg.conf): fall back to "keep" rather than to + * the destructive "delete" branch below, so a misconfiguration never silently discards a user's + * previous BLAT tracks. */ +if (differentString(oldTracks, "keep") && differentString(oldTracks, "hide") + && differentString(oldTracks, "delete")) + { + warn("hg.conf blatOldTracks has unrecognized value '%s'; expected keep|hide|delete. " + "Treating as 'keep'.", oldTracks); + oldTracks = "keep"; + } if (differentString(oldTracks, "keep")) { struct customTrack *ct, *next, *keptList = NULL; for (ct = ctList; ct != NULL; ct = next) { next = ct->next; if (ct->tdb != NULL && sameOk(trackDbSetting(ct->tdb, "blatResult"), "on")) { if (sameString(oldTracks, "hide")) { cartSetString(cart, ct->tdb->track, "hide"); slAddHead(&keptList, ct); /* keep it in the session, just hidden */ } /* "delete": drop it from the list so customTracksSaveCart writes it out */ }