a53b9958fa734f73aeffb9ddfe2fbad1ca65f90c galt Mon Jan 30 16:18:41 2017 -0800 Check-in of CSP2 Content-Security-Policy work. All C-language CGIs should now support CSP2 in browser to stop major forms of XSS javascript injection. Javascript on pages is gathered together, and then emitted in a single script block at the end with a nonce that tells the browser, this is js that we generated instead of being injected by a hacker. Both inline script from script blocks and inline js event handlers had to be pulled out and separated. You will not see js sprinkled through-out the page now. Older browsers that support CSP1 or that do not understand CSP at all will still work, just without protection. External js libraries loaded at runtime need to be added to the CSP policy header in src/lib/htmshell.c. diff --git src/hg/lib/jsHelper.c src/hg/lib/jsHelper.c index 4ae2fbc..26a7999 100644 --- src/hg/lib/jsHelper.c +++ src/hg/lib/jsHelper.c @@ -32,113 +32,122 @@ void jsInit() /* If this is the first call, set window.onload to the operations * performed upon loading a page and print supporting javascript. * Currently this just sets the page vertical position if specified on * CGI, and includes jsHelper.js. * Subsequent calls do nothing, so this can be called many times. */ { if (! jsInited) { // jsh_pageVertPos trick taken from // http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.html puts(""); int pos = cgiOptionalInt("jsh_pageVertPos", 0); if (pos > 0) - printf("\n", pos); + { + char javascript[1024]; + safef(javascript, sizeof javascript, + "window.onload = function () { window.scrollTo(0, %d); }", pos); + jsInline(javascript); + } jsInited = TRUE; jsIncludeFile("jsHelper.js", NULL); } } struct dyString *jsOnChangeStart() /* Start up an onChange string */ { struct dyString *dy = dyStringNew(1024); -dyStringAppend(dy, "onChange=\""); return dy; } char *jsOnChangeEnd(struct dyString **pDy) /* Finish up javascript onChange command. */ { -dyStringAppend(*pDy, "document.hiddenForm.submit();\""); +dyStringAppend(*pDy, "document.hiddenForm.submit();"); return dyStringCannibalize(pDy); } void jsDropDownCarryOver(struct dyString *dy, char *var) /* Add statement to carry-over drop-down item to dy. */ { dyStringPrintf(dy, "document.hiddenForm.%s.value=", var); dyStringPrintf(dy, "document.mainForm.%s.options", var); dyStringPrintf(dy, "[document.mainForm.%s.selectedIndex].value; ", var); } void jsTextCarryOver(struct dyString *dy, char *var) /* Add statement to carry-over text item to dy. */ { dyStringPrintf(dy, "document.hiddenForm.%s.value=document.mainForm.%s.value; ", var, var); } void jsTrackingVar(char *jsVar, char *val) /* Emit a little Javascript to keep track of a variable. * This helps especially with radio buttons. */ { -hPrintf("\n"); +char javascript[256]; +safef(javascript, sizeof javascript, + "var %s='%s';\n", jsVar, val); +jsInline(javascript); } void jsMakeTrackingRadioButtonExtraHtml(char *cgiVar, char *jsVar, char *val, char *selVal, char *extraHtml) /* Make a radio button with extra HTML attributes that also sets tracking variable * in javascript. */ { -hPrintf(""); } void jsMakeTrackingRadioButton(char *cgiVar, char *jsVar, char *val, char *selVal) /* Make a radio button that also sets tracking variable * in javascript. */ { jsMakeTrackingRadioButtonExtraHtml(cgiVar, jsVar, val, selVal, NULL); } void jsMakeTrackingCheckBox(struct cart *cart, char *cgiVar, char *jsVar, boolean usualVal) /* Make a check box filling in with existing value and * putting a javascript tracking variable on it. */ { char buf[256]; boolean oldVal = cartUsualBoolean(cart, cgiVar, usualVal); -hPrintf("\n", jsVar, oldVal); -hPrintf(""); safef(buf, sizeof(buf), "%s%s", cgiBooleanShadowPrefix(), cgiVar); cgiMakeHiddenVar(buf, "0"); } void jsTrackedVarCarryOver(struct dyString *dy, char *cgiVar, char *jsVar) /* Carry over tracked variable (radio button?) to hidden form. */ { dyStringPrintf(dy, "document.hiddenForm.%s.value=%s; ", cgiVar, jsVar); } char *jsRadioUpdate(char *cgiVar, char *jsVar, char *val) /* Make a little javascript to check and uncheck radio buttons * according to new value. To use this you must have called * jsInit somewhere, and also must use jsMakeTrackingRadioButton @@ -161,44 +170,47 @@ cartSaveSession(cart); for (i=0; i\n", vars[i]); puts(""); } char *jsSetVerticalPosition(char *form) /* Returns a javascript statement for storing the vertical position of the * page; typically this would go just before a document submit. * jsInit must be called first. * Do not free return value! */ { if (! jsInited) errAbort("jsSetVerticalPosition: jsInit must be called first."); static char vertPosSet[2048]; +//TODO XSS filter safef(vertPosSet, sizeof(vertPosSet), "document.%s.jsh_pageVertPos.value = f_scrollTop(); ", form); return vertPosSet; } void jsMakeCheckboxGroupSetClearButton(char *buttonVar, boolean isSet) /* Make a button for setting or clearing a set of checkboxes with the same name. * Uses only javascript to change the checkboxes, no resubmit. */ { +char id[256]; char javascript[256]; safef(javascript, sizeof(javascript), "var list = document.getElementsByName('%s'); " "for (var ix = 0; ix < list.length; ix++) {list[ix].checked = %s}", buttonVar, isSet ? "true" : "false"); -cgiMakeOnClickButton(javascript, isSet ? JS_SET_ALL_BUTTON_LABEL : JS_CLEAR_ALL_BUTTON_LABEL); +safef(id, sizeof id, "%s_grpSetClrBut", buttonVar); +cgiMakeOnClickButton(id, javascript, isSet ? JS_SET_ALL_BUTTON_LABEL : JS_CLEAR_ALL_BUTTON_LABEL); } void jsMakeSetClearContainer() /* Begin a wrapper div with class setClearContainer, plus 'Set all' and 'Clear all' buttons. * This should be followed by a bunch of checkboxes, and then a call to jsEndContainer. */ { puts("
\n" "\n" "\n" "
" ); } void jsEndContainer() /* End a wrapper div. */ @@ -401,36 +413,40 @@ { char collapseGroupVar[512]; safef(collapseGroupVar, sizeof(collapseGroupVar), "%s.section_%s_close", track, section); boolean isOpen = !cartUsualBoolean(cart, collapseGroupVar, !isOpenDefault); // Both plus button and title are now in same // but still colspan=2 because we are lib code and callers own the table. puts(""); printf("\n"); printf("\n", collapseGroupVar, collapseGroupVar, isOpen ? "0" : "1"); char *buttonImage = (isOpen ? "../images/remove_sm.gif" : "../images/add_sm.gif"); +char id[256]; +char javascript[1024]; +safef(id, sizeof id, "%s_button", section); +safef(javascript, sizeof javascript, "return setTableRowVisibility(this, '%s', '%s.section', 'section', true);", + section, track); printf("%s\n", - section, track, - section, buttonImage, (isOpen ? "-" : "+"), (isOpen ? "Collapse": "Expand")); + id, buttonImage, (isOpen ? "-" : "+"), (isOpen ? "Collapse": "Expand")); +jsOnEventById("click", id, javascript); if (oldStyle || fontSize == NULL) printf(" %s\n", sectionTitle); else printf(" %s", fontSize, sectionTitle); puts("\n"); printf("", isOpen ? "" : "style='display: none' ", section, 1); } void jsBeginCollapsibleSectionOldStyle(struct cart *cart, char *track, char *section, char *sectionTitle, boolean isOpenDefault) /* Make the hidden input, collapse/expand button and needed for utils.js's * setTableRowVisibility(). Caller needs to have already created a and . * With support for varying font size */ { jsBeginCollapsibleSectionFull(cart, track, section, sectionTitle, isOpenDefault, NULL, TRUE); @@ -454,172 +470,183 @@ } void jsEndCollapsibleSection() /* End the collapsible . */ { puts(""); } void jsReloadOnBackButton(struct cart *cart) /* Add some javascript to detect that the back button (or reload) has been pressed, * and to resubmit in that case to redraw the page with the latest cart contents. */ // __detectback trick from // http://siphon9.net/loune/2009/07/detecting-the-back-or-refresh-button-click/ // Yes, I know this along with every other inline \n", cartSidUrlString(cart), cgiScriptName(), cartSidUrlString(cart)); + , cartSidUrlString(cart), cgiScriptName(), cartSidUrlString(cart)); +jsInline(javascript); } static char *makeIndentBuf(int indentLevel) { if (indentLevel < 0) return ""; char *indentBuf; indentBuf = needMem(indentLevel + 1); memset(indentBuf, '\t', indentLevel); indentBuf[indentLevel] = 0; return indentBuf; } -static void jsonPrintRecurse(struct jsonElement *ele, int indentLevel) +static void jsonDyStringPrintRecurse(struct dyString *dy, struct jsonElement *ele, int indentLevel) { if (indentLevel >= -1) // Note that < -1 will result in no indenting indentLevel++; char *tab = "\t"; char *nl = "\n"; if (indentLevel < 0) { tab = ""; nl = ""; } char *indentBuf = makeIndentBuf(indentLevel); switch (ele->type) { case jsonObject: { - hPrintf("{%s",nl); + dyStringPrintf(dy,"{%s",nl); if(hashNumEntries(ele->val.jeHash)) { struct hashEl *el, *list = hashElListHash(ele->val.jeHash); slSort(&list, hashElCmp); for (el = list; el != NULL; el = el->next) { struct jsonElement *val = el->val; - hPrintf("%s%s\"%s\": ", indentBuf, tab, el->name); - jsonPrintRecurse(val, indentLevel); - hPrintf("%s%s", el->next == NULL ? "" : ",",nl); + dyStringPrintf(dy,"%s%s\"%s\": ", indentBuf, tab, el->name); + jsonDyStringPrintRecurse(dy, val, indentLevel); + dyStringPrintf(dy,"%s%s", el->next == NULL ? "" : ",",nl); } hashElFreeList(&list); } - hPrintf("%s}", indentBuf); + dyStringPrintf(dy,"%s}", indentBuf); break; } case jsonList: { struct slRef *el; - hPrintf("[%s",nl); + dyStringPrintf(dy,"[%s",nl); if(ele->val.jeList) { for (el = ele->val.jeList; el != NULL; el = el->next) { struct jsonElement *val = el->val; - hPrintf("%s%s", indentBuf,tab); - jsonPrintRecurse(val, indentLevel); - hPrintf("%s%s", el->next == NULL ? "" : ",",nl); + dyStringPrintf(dy,"%s%s", indentBuf,tab); + jsonDyStringPrintRecurse(dy, val, indentLevel); + dyStringPrintf(dy,"%s%s", el->next == NULL ? "" : ",",nl); } } - hPrintf("%s]", indentBuf); + dyStringPrintf(dy,"%s]", indentBuf); break; } case jsonString: { - hPrintf("\"%s\"", jsonStringEscape(ele->val.jeString)); + dyStringPrintf(dy,"\"%s\"", jsonStringEscape(ele->val.jeString)); break; } case jsonBoolean: { - hPrintf("%s", ele->val.jeBoolean ? "true" : "false"); + dyStringPrintf(dy,"%s", ele->val.jeBoolean ? "true" : "false"); break; } case jsonNumber: { char buf[256]; safef(buf, sizeof(buf), "%ld", ele->val.jeNumber); - hPrintf("%s", buf); + dyStringPrintf(dy,"%s", buf); break; } case jsonDouble: { char buf[256]; safef(buf, sizeof(buf), "%g", ele->val.jeDouble); - hPrintf("%s", buf); + dyStringPrintf(dy,"%s", buf); break; } default: { errAbort("jsonPrintRecurse; invalid type: %d", ele->type); break; } } if (indentLevel >= 0) freez(&indentBuf); } -void jsonPrint(struct jsonElement *json, char *name, int indentLevel) +void jsonDyStringPrint(struct dyString *dy, struct jsonElement *json, char *name, int indentLevel) +// dyStringPrint out a jsonElement, indentLevel -1 means no indenting { -// print out a jsonElement, indentLevel -1 means no indenting char *indentBuf = makeIndentBuf(indentLevel); if(name != NULL) { if (indentLevel >= 0 ) - hPrintf("// START %s\n%s", name, indentBuf); - hPrintf("var %s = ", name); + dyStringPrintf(dy, "// START %s\n%s", name, indentBuf); + dyStringPrintf(dy, "var %s = ", name); } -jsonPrintRecurse(json, (indentLevel - 1)); // will increment back to indentLevel +jsonDyStringPrintRecurse(dy, json, (indentLevel - 1)); // will increment back to indentLevel if(name != NULL) { - hPrintf("%s;\n", indentBuf); + dyStringPrintf(dy, "%s;\n", indentBuf); if (indentLevel >= 0 ) - hPrintf("// END %s\n", name); + dyStringPrintf(dy, "// END %s\n", name); } if (indentLevel >= 0) freez(&indentBuf); } +void jsonPrint(struct jsonElement *json, char *name, int indentLevel) +// print out a jsonElement, indentLevel -1 means no indenting +{ +struct dyString *dy = dyStringNew(1024); +jsonDyStringPrint(dy, json, name, indentLevel); +hPrintf("%s", dy->string); +dyStringFree(&dy); +} + void jsonErrPrintf(struct dyString *ds, char *format, ...) // Printf a json error to a dyString for communicating with ajax code; format is: // {"error": error message here} { va_list args; va_start(args, format); dyStringPrintf(ds, "{\"error\": \""); struct dyString *buf = newDyString(1000); dyStringVaPrintf(buf, format, args); dyStringAppend(ds, jsonStringEscape(dyStringCannibalize(&buf))); dyStringPrintf(ds, "\"}"); va_end(args); }