a1d7c07c50f1d895337de121680ea672b261c058 max Mon Aug 17 02:26:36 2026 -0700 escape reflected/echoed user input across several CGIs (XSS), refs #38057 Route user-, DB- and hub-derived values through htmlEncode (HTML/attribute text), cgiEncode (values composed into URLs), jsonStringEscape (values placed in a JS string literal inside an inline script) or, for hgMirror, the existing mustBeClean sanitizer. Covers hgHubConnect, hgUserSuggestion, hgLiftOver, hgBlat, hgc pubs, hgVisiGene, hgSession, hgTrackUi, hgGenome, phyloPng, hgFileSearch, hgLinkIn, hgPal, hui, hgPhyloPlace, hgMirror, hgCustom and hgSearch. diff --git src/hg/hgGenome/mainPage.c src/hg/hgGenome/mainPage.c index c7145456375..c10dc02cca7 100644 --- src/hg/hgGenome/mainPage.c +++ src/hg/hgGenome/mainPage.c @@ -1,694 +1,694 @@ /* mainPage - draws the main hgGenome page, including some controls * on the top and the graphic. */ /* Copyright (C) 2013 The Regents of the University of California * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */ #include "common.h" #include "psGfx.h" #include "pscmGfx.h" #include "linefile.h" #include "hash.h" #include "cheapcgi.h" #include "htmshell.h" #include "cart.h" #include "hui.h" #include "dbDb.h" #include "ra.h" #include "hdb.h" #include "web.h" #include "portable.h" #include "hgColors.h" #include "trackLayout.h" #include "chromInfo.h" #include "hvGfx.h" #include "genoLay.h" #include "cytoBand.h" #include "hCytoBand.h" #include "errCatch.h" #include "chromGraph.h" #include "customTrack.h" #include "hPrint.h" #include "jsHelper.h" #include "hgGenome.h" #include "trashDir.h" static char *allColors[] = { "black", "blue", "purple", "red", "orange", "yellow", "green", "gray", }; static char *defaultColors[maxLinesOfGraphs][maxGraphsPerLine] = { {"blue", "red", "black", "yellow", }, {"gray", "purple", "green", "orange",}, {"blue", "red", "black", "yellow", }, {"gray", "purple", "green", "orange",}, {"blue", "red", "black", "yellow", }, {"gray", "purple", "green", "orange",}, }; char *graphColorAt(int row, int col) /* Return graph color at given row/column, NULL if nonw. */ { char *varName = graphColorVarName(row, col); char *color = cartUsualString(cart, varName, defaultColors[row][col]); if (color == NULL) color = defaultColors[0][0]; return color; } Color colorFromAscii(struct hvGfx *hvg, char *asciiColor) /* Get color index for a named color. */ { if (sameWord("red", asciiColor)) return MG_RED; else if (sameWord("blue", asciiColor)) return MG_BLUE; else if (sameWord("yellow", asciiColor)) return hvGfxFindColorIx(hvg, 220, 220, 0); else if (sameWord("purple", asciiColor)) return hvGfxFindColorIx(hvg, 150, 0, 200); else if (sameWord("orange", asciiColor)) return hvGfxFindColorIx(hvg, 230, 120, 0); else if (sameWord("green", asciiColor)) return hvGfxFindColorIx(hvg, 0, 180, 0); else if (sameWord("gray", asciiColor)) return MG_GRAY; else return MG_BLACK; } /* Page drawing stuff. */ void drawChromGraph(struct hvGfx *hvg, struct sqlConnection *conn, struct genoLay *gl, char *chromGraph, int yOff, int height, Color color, boolean leftLabel, boolean rightLabel, boolean firstInRow) /* Draw chromosome graph on all chromosomes in layout at given * y offset and height. */ { boolean yellowMissing = getYellowMissing(); struct genoGraph *gg = hashFindVal(ggHash, chromGraph); if (gg != NULL) { /* Get binary data source and scaling info etc. */ struct chromGraphBin *cgb = gg->cgb; struct chromGraphSettings *cgs = gg->settings; int maxGapToFill = cgs->maxGapToFill; static struct rgbColor missingDataColor = { 180, 180, 120, 255}; Color missingColor = hvGfxFindRgb(hvg, &missingDataColor); double pixelsPerBase = 1.0/gl->basesPerPixel; double gMin = cgs->minVal, gMax = cgs->maxVal, gScale; gScale = height/(gMax-gMin); /* Draw significance threshold as a light blue line */ if (leftLabel) { static struct rgbColor guidelineColor = { 220, 220, 255, 255}; Color lightBlue = hvGfxFindRgb(hvg, &guidelineColor); struct slRef *ref; struct genoLayChrom *chrom; int rightX = gl->picWidth - gl->rightLabelWidth - gl->margin; int leftX = gl->leftLabelWidth + gl->margin; int width = rightX - leftX; double threshold = getThreshold(); if (threshold >= gMin && threshold <= gMax) { int y = height - ((threshold - gMin)*gScale) + yOff; for (ref = gl->leftList; ref != NULL; ref = ref->next) { chrom = ref->val; hvGfxBox(hvg, leftX, y + chrom->y, width, 1, lightBlue); } ref = gl->bottomList; if (ref != NULL) { chrom = ref->val; hvGfxBox(hvg, leftX, y + chrom->y, width, 1, lightBlue); } } } /* Draw graphs on each chromosome */ chromGraphBinRewind(cgb); while (chromGraphBinNextChrom(cgb)) { struct genoLayChrom *chrom = hashFindVal(gl->chromHash, cgb->chrom); if (chrom) { int chromX = chrom->x, chromY = chrom->y; int minY = chromY + yOff; int maxY = chromY + yOff + height - 1; if (chromGraphBinNextVal(cgb)) { /* Set clipping so can't scribble outside of box. */ hvGfxSetClip(hvg, chromX, chromY, chrom->width+1, chrom->height); /* Handle first point as special case here, so don't * have to test for first point in inner loop. */ double x,lastX; int y,start,lastStart,lastY; start = lastStart = cgb->chromStart; x = lastX = pixelsPerBase*start + chromX; y = lastY = (height - ((cgb->val - gMin)*gScale)) + chromY+yOff; if (y < minY) y = minY; else if (y > maxY) y = maxY; struct pscmGfx *pscm = NULL; if (hvg->pixelBased) { hvGfxDot(hvg, x, y, color); } else { pscm = (struct pscmGfx *)hvg->vg->data; pscmSetColor(pscm, color); psFillEllipse(pscm->ps, x, y, 0.01, 0.01); psSetLineWidth(pscm->ps, 0.01); } /* Draw rest of points, connecting with line to previous point * if not too far off. */ while (chromGraphBinNextVal(cgb)) { start = cgb->chromStart; x = pixelsPerBase*start + chromX; if (hvg->pixelBased) x = (int) x; y = (height - ((cgb->val - gMin)*gScale)) + chromY+yOff; if (y < minY) y = minY; else if (y > maxY) y = maxY; if (x != lastX || y != lastY) { if (start - lastStart <= maxGapToFill) { if (hvg->pixelBased) hvGfxLine(hvg, lastX, lastY, x, y, color); else { pscmSetColor(pscm, color); psDrawLine(pscm->ps, lastX, lastY, x, y); } } else { if (yellowMissing && leftLabel) { int width = x - lastX - 1; if (width > 0) hvGfxBox(hvg, lastX+1, minY, width, height, missingColor); } if (hvg->pixelBased) { hvGfxDot(hvg, x, y, color); } else { pscmSetColor(pscm, color); psFillEllipse(pscm->ps, x, y, 0.01, 0.01); } } } lastX = x; lastY = y; lastStart = start; } if (!hvg->pixelBased) { psSetLineWidth(pscm->ps, 1); } hvGfxUnclip(hvg); } } else { /* Just read and discard data. */ while (chromGraphBinNextVal(cgb)) ; } } /* Draw labels. */ if (withLabels && (leftLabel || rightLabel)) { int lineY = yOff; int i,j; int spaceWidth = tl.nWidth; int tickWidth = spaceWidth*2/3; int fontPixelHeight = mgFontPixelHeight(gl->font); for (i=0; i<gl->lineCount; ++i) { hvGfxSetClip(hvg, 0, lineY, gl->picWidth, gl->lineHeight); for (j=0; j< cgs->linesAtCount; ++j) { double lineAt = cgs->linesAt[j]; int y = (height - ((lineAt - gMin)*gScale)) + lineY; int textTop = y - fontPixelHeight/2+1; int textBottom = textTop + fontPixelHeight; char label[24]; safef(label, sizeof(label), "%g", lineAt); if (leftLabel) { hvGfxBox(hvg, gl->margin + gl->leftLabelWidth - tickWidth-1, y, tickWidth, 1, color); if (textTop >= lineY && textBottom < lineY + height) { hvGfxTextRight(hvg, gl->margin, textTop, gl->leftLabelWidth-spaceWidth, fontPixelHeight, color, gl->font, label); } } if (rightLabel) { hvGfxBox(hvg, gl->picWidth - gl->margin - gl->rightLabelWidth+1, y, tickWidth, 1, color); if (textTop >= lineY && textBottom < lineY + height) { hvGfxText(hvg, gl->picWidth - gl->margin - gl->rightLabelWidth + spaceWidth, textTop, color, gl->font, label); } } } lineY += gl->lineHeight; hvGfxUnclip(hvg); } } } } void genomeGif(struct sqlConnection *conn, struct genoLay *gl, int graphRows, int graphCols, int oneRowHeight, char *psOutput) /* Create genome GIF file and HTML that includes it. */ { struct hvGfx *hvg; struct tempName gifTn; Color shadesOfGray[10]; int maxShade = ArraySize(shadesOfGray)-1; int spacing = 1; int yOffset = 2*spacing; int innerHeight = oneRowHeight - 3*spacing; int i,j; if (psOutput) { hvg = hvGfxOpenPostScript(gl->picWidth, gl->picHeight, psOutput); } else { /* Create gif file and make reference to it in html. */ trashDirFile(&gifTn, "hgg", "ideo", ".png"); hvg = hvGfxOpenPng(gl->picWidth, gl->picHeight, gifTn.forCgi, FALSE); hPrintf("<INPUT TYPE=IMAGE SRC=\"%s\" BORDER=1 WIDTH=%d HEIGHT=%d NAME=\"%s\">", gifTn.forHtml, gl->picWidth, gl->picHeight, hggClick); } /* Get our grayscale. */ hMakeGrayShades(hvg, shadesOfGray, maxShade); /* Draw the labels and then the chromosomes. */ genoLayDrawChromLabels(gl, hvg, MG_BLACK); genoLayDrawBandedChroms(gl, hvg, database, conn, shadesOfGray, maxShade, MG_BLACK); /* Draw chromosome graphs. */ for (i=0; i<graphRows; ++i) { boolean firstInRow = TRUE; for (j=graphCols-1; j>=0; --j) { char *graph = graphSourceAt(i,j); if (graph != NULL && graph[0] != 0) { Color color = colorFromAscii(hvg, graphColorAt(i,j)); drawChromGraph(hvg, conn, gl, graph, gl->betweenChromOffsetY + yOffset, innerHeight, color, j==0, j==1, firstInRow); firstInRow = FALSE; } } yOffset += oneRowHeight; } hvGfxBox(hvg, 0, 0, gl->picWidth, 1, MG_GRAY); hvGfxBox(hvg, 0, gl->picHeight-1, gl->picWidth, 1, MG_GRAY); hvGfxBox(hvg, 0, 0, 1, gl->picHeight, MG_GRAY); hvGfxBox(hvg, gl->picWidth-1, 0, 1, gl->picHeight, MG_GRAY); hvGfxClose(&hvg); } void graphDropdown(struct sqlConnection *conn, char *varName, char *curVal, char *event, char *js) /* Make a drop-down with available chrom graphs */ { int totalCount = 1; char **menu, **values; int i = 0; struct slRef *ref; for (ref = ggList; ref != NULL; ref = ref->next) { struct genoGraph *gg = ref->val; if (gg->isComposite == FALSE) totalCount++; } AllocArray(menu, totalCount); AllocArray(values, totalCount); menu[0] = "-- nothing --"; values[0] = ""; for (ref = ggList; ref != NULL; ref = ref->next) { struct genoGraph *gg = ref->val; if (gg->isComposite == FALSE) { ++i; - menu[i] = gg->shortLabel; + menu[i] = htmlEncode(gg->shortLabel); // user graph label in dropdown text, escape (XSS) values[i] = gg->name; } } cgiMakeDropListFull(varName, menu, values, totalCount, curVal, event, js); freez(&menu); freez(&values); } void colorDropdown(int row, int col, char *event, char *js) /* Put up color drop down menu. */ { char *varName = graphColorVarName(row, col); char *curVal = graphColorAt(row, col); cgiMakeDropListFull(varName, allColors, allColors, ArraySize(allColors), curVal, event, js); } static void addThresholdGraphCarries(struct dyString *dy, int graphRows, int graphCols, boolean cgaOnly) /* Add javascript that carries over threshold and graph vars * to new form. */ { if (cgaOnly) return; jsTextCarryOver(dy, getThresholdName()); int i,j; for (i=0; i<graphRows; ++i) for (j=0; j<graphCols; ++j) { jsDropDownCarryOver(dy, graphVarName(i,j)); jsDropDownCarryOver(dy, graphColorVarName(i,j)); } } static struct dyString *onChangeStart(int graphRows, int graphCols, boolean cgaOnly) /* Return common prefix to onChange javascript string */ { struct dyString *dy = jsOnChangeStart(); addThresholdGraphCarries(dy, graphRows, graphCols, cgaOnly); return dy; } static char *onChangeClade(int graphRows, int graphCols, boolean cgaOnly) /* Return javascript executed when they change clade. */ { struct dyString *dy = onChangeStart(graphRows, graphCols, cgaOnly); jsDropDownCarryOver(dy, "clade"); dyStringAppend(dy, " document.hiddenForm.org.value=0;"); dyStringAppend(dy, " document.hiddenForm.db.value=0;"); return jsOnChangeEnd(&dy); } static char *onChangeOrg(int graphRows, int graphCols, boolean cgaOnly) /* Return javascript executed when they change organism. */ { struct dyString *dy = onChangeStart(graphRows, graphCols, cgaOnly); jsDropDownCarryOver(dy, "clade"); jsDropDownCarryOver(dy, "org"); dyStringAppend(dy, " document.hiddenForm.db.value=0;"); return jsOnChangeEnd(&dy); } static void saveOnChangeOtherFunction(int graphRows, int graphCols, boolean cgaOnly) /* Write out Javascript function to save vars in hidden * form and submit. */ { struct dyString *dy = dyStringNew(4096); dyStringAppend(dy, "var submitted=false;\n" "function changeOther()\n" "{\n" "if (!submitted)\n" " {\n" " submitted=true;\n" " "); addThresholdGraphCarries(dy, graphRows, graphCols, cgaOnly); jsDropDownCarryOver(dy, "clade"); jsDropDownCarryOver(dy, "org"); jsDropDownCarryOver(dy, "db"); dyStringAppend(dy, "document.hiddenForm.submit();\n"); dyStringAppend(dy, " }\n" "}\n" ); jsInline(dy->string); } static char *onChangeOther() /* Return javascript executed when they change database. */ { return "changeOther();"; } boolean renderGraphic(struct sqlConnection *conn, char *psOutput) /* draw just the graphic */ { struct genoLay *gl; int graphRows = linesOfGraphs(); int graphCols = graphsPerLine(); boolean result = FALSE; if (ggList != NULL) { /* Get genome layout. This can fail so it is wrapped in an error * catcher. */ struct errCatch *errCatch = errCatchNew(); if (errCatchStart(errCatch)) { gl = ggLayout(conn, graphRows, graphCols); /* Draw picture. Enclose in table to add a couple of pixels between * it and controls on IE. */ genomeGif(conn, gl, graphRows, graphCols, graphHeight()+betweenRowPad, psOutput); result = TRUE; } errCatchEnd(errCatch); if (errCatch->gotError) warn("%s", errCatch->message->string); errCatchFree(&errCatch); } else { hPrintf("<BR><B>No graph data is available for this assembly. " "Upload your own data or import from a table or custom track.</B>"); } return result; } void handlePostscript(struct sqlConnection *conn) /* Deal with Postscript output. */ { struct tempName psTn; char *pdfFile = NULL; trashDirFile(&psTn, "hgg", "hgg", ".eps"); cartWebStart(cart, database, "%s Genome Graphs", genome); printf("<H1>PDF Output</H1>\n"); boolean result = renderGraphic(conn, psTn.forCgi); if (result) { printf("EPS (PostScript) output has been discontinued in pursuit of additional features\n"); printf("that are not PostScript-compatible. If you require PostScript output for your\n"); printf("workflow, please <a href='https://genome.ucsc.edu/contacts.html'>reach out to us</a>\n"); printf("and let us know what your needs are - we may be able to help.\n"); pdfFile = convertEpsToPdf(psTn.forCgi); if(pdfFile != NULL) { printf("<BR><BR>PDF can be viewed with Adobe Acrobat Reader.<BR>\n"); printf("<A TARGET=_blank HREF=\"%s\">Click here</A> " "to download the current browser graphic in PDF.", pdfFile); } else printf("<BR><BR>PDF format not available"); freez(&pdfFile); } printf("<BR><BR><A HREF=\"../cgi-bin/hgGenome\">Return</A>"); cartWebEnd(); } void mainPage(struct sqlConnection *conn) /* Do main page of application: hotlinks bar, controls, graphic. */ { int graphRows = linesOfGraphs(); int graphCols = graphsPerLine(); int i, j; int realCount = 0; char *scriptName = "../cgi-bin/hgGenome"; cartWebStart(cart, database, "%s Genome Graphs", genome); /* Start form and save session var. */ hPrintf("<FORM ACTION=\"%s\" NAME=\"mainForm\" METHOD=GET>\n", scriptName); cartSaveSession(cart); /* it might be a non-chrom based assembly */ // defined externally in import.c at the moment extern struct slName *getChroms(); /* Get a chrom list. */ struct slName *chromList = getChroms(); int count = slCount(chromList); boolean cgaOnly = FALSE; if ((count > 500) || (count == 0)) { cgaOnly = TRUE; } jsInit(); saveOnChangeOtherFunction(graphRows, graphCols, cgaOnly); char *jsOther = onChangeOther(); /* Print clade, genome and assembly line. */ hPrintf("<TABLE>"); boolean gotClade = hGotClade(); if (gotClade) { hPrintf("<TR><TD><B>clade:</B>\n"); printCladeListHtml(hGenome(database), "change", onChangeClade(graphRows, graphCols, cgaOnly)); htmlNbSpaces(3); hPrintf("<B>genome:</B>\n"); printGenomeListForCladeHtml(database, "change", onChangeOrg(graphRows, graphCols, cgaOnly)); } else { hPrintf("<TR><TD><B>genome:</B>\n"); printGenomeListHtml(database, "change", onChangeOrg(graphRows, graphCols, cgaOnly)); } htmlNbSpaces(3); hPrintf("<B>assembly:</B>\n"); printAssemblyListHtml(database, "change", jsOther); hPrintf("</TD></TR>\n"); hPrintf("</TABLE>"); if (cgaOnly) { if (count > 500) /* Too-many-chrom assembly */ { warn("Sorry, too many chromosomes. " "This one has %d. Please select another organism or assembly.", count); } else if (count == 0) /* non-chrom assembly */ { warn("Sorry, can only do genome layout on assemblies mapped to chromosomes. " "This one has no usable chromosomes. Please select another organism or assembly."); } } else { /* Draw graph controls. */ hPrintf("<TABLE>"); for (i=0; i<graphRows; ++i) { hPrintf("<TR>"); hPrintf("<TD><B>graph</B></TD>"); for (j=0; j<graphCols; ++j) { char *varName = graphVarName(i,j); char *curVal = cartUsualString(cart, varName, ""); if (curVal[0] != 0) ++realCount; hPrintf("<TD>"); graphDropdown(conn, varName, curVal, "change", jsOther); hPrintf(" <B>in</B> "); colorDropdown(i, j, "change", jsOther); if (j != graphCols-1) hPrintf(","); hPrintf("</TD>"); } hPrintf("</TR>"); } hPrintf("</TABLE>"); cgiMakeButton(hggUpload, "upload"); hPrintf(" "); cgiMakeButton(hggImport, "import"); hPrintf(" "); cgiMakeButton(hggConfigure, "configure"); hPrintf(" "); cgiMakeOptionalButton(hggCorrelate, "correlate", realCount < 2); hPrintf(" <B>significance threshold:</B>"); hPrintf("<INPUT TYPE=\"TEXT\" NAME=\"%s\" id='%s' SIZE=\"%d\" VALUE=\"%g\">" , getThresholdName(), getThresholdName(), 3, getThreshold()); jsOnEventById("change" , getThresholdName(), "changeOther();"); jsOnEventById("keypress", getThresholdName(), "return submitOnEnter(event,document.mainForm);"); hPrintf(" "); cgiMakeOptionalButton(hggBrowse, "browse regions", realCount == 0); hPrintf(" "); cgiMakeOptionalButton(hggSort, "sort genes", realCount == 0 || !hgNearOk(database)); hPrintf("<BR>"); hPrintf("<TABLE CELLPADDING=2><TR><TD>\n"); boolean result = renderGraphic(conn, NULL); hPrintf("</TD></TR></TABLE>\n"); if (result) /* Write a little click-on-help */ hPrintf("<i>Click on a chromosome to open Genome Browser at that position.</i>"); } /* end else non-chrom assembly */ hPrintf("</FORM>\n"); /* Hidden form - fo the benefit of javascript. */ { /* Copy over both the regular, non-changing variables, and * also all the source/color pairs that depend on the * configuration. */ static char *regularVars[] = { "clade", "org", "db", }; int regularCount = ArraySize(regularVars); int varCount = regularCount + 1 + 2 * graphRows * graphCols; int varIx = regularCount; char **allVars; AllocArray(allVars, varCount); CopyArray(regularVars, allVars, regularCount); allVars[varIx++] = cloneString(getThresholdName()); for (i=0; i<graphRows; ++i) { for (j=0; j<graphCols; ++j) { allVars[varIx++] = cloneString(graphVarName(i,j)); allVars[varIx++] = cloneString(graphColorVarName(i,j)); } } jsCreateHiddenForm(cart, scriptName, allVars, varCount); } webNewSection("Using Genome Graphs"); printMainHelp(); cartWebEnd(); }