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/js/utils.js src/hg/js/utils.js index 1664d71..a3f8ffe 100644 --- src/hg/js/utils.js +++ src/hg/js/utils.js @@ -1,2865 +1,3024 @@ // Utility JavaScript // "use strict"; // Don't complain about line break before '||' etc: /* jshint -W014 */ var debug = false; function clickIt(obj,state,force) { // calls click() for an object, and click();click() if force if (obj.checked !== state) { obj.click(); } else if (force) { obj.click(); obj.click(); //force onclick event } } function setCheckBoxesWithPrefix(obj, prefix, state) { // Set all checkboxes with given prefix to state boolean var list = inputArrayThatMatches("checkbox","id",prefix,""); for (var i=0;i 0 && inpType !== 'select' && ele.type !== inpType) continue; var identifier = ele.name; if (nameOrId.search(/id/i) !== -1) identifier = ele.id; var failed = false; if (prefix.length > 0) failed = (identifier.indexOf(prefix) !== 0); if (!failed && suffix.length > 0) failed = (identifier.lastIndexOf(suffix) !== (identifier.length - suffix.length)); if (!failed) { for (var aIx=4;aIx2) alert("arrayOfInputsThatMatch is unimplemented for this browser"); } return found; } function normed(thing) { // RETURNS undefined, the lone member of the set or the full set if more than one member. // Used for normalizing returns from jquery DOM selects (e.g. $('tr.track').children('td.data')) // jquery returns an "array like 'object'" with 0 or more entries. // May be used on non-jquery objects and will reduce single element arrays to the element. // Use this to treat 0 entries the same as undefined and 1 entry as the item itself if (typeof(thing) === 'undefined' || thing === null || (thing.length !== undefined && thing.length === 0) // Empty array (or 'array like object') || ($.isPlainObject(thing) && $.isEmptyObject(thing))) // Empty simple object return undefined; if (thing.length && thing.length === 1 && jQuery.type(thing) !== 'string') // string is overkill return thing[0]; // Container of one item should return the item itself. return thing; } var theClient = (function() { // Object that detects client browser if requested // - - - - - Private variables and/or methods - - - - - var ieVersion = null; var browserNamed = null; // - - - - - Public methods - - - - - return { // returns an object with public methods getIeVersion: function () { // Adapted from the web: stackOverflow.com answer by Joachim Isaksson if (ieVersion === null) { ieVersion = -1.0; var re = null; if (navigator.appName === 'Microsoft Internet Explorer') { browserNamed = 'MSIE'; re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})"); if (re.exec(navigator.userAgent) !== null) ieVersion = parseFloat( RegExp.$1 ); } else if (navigator.appName === 'Netscape') { re = new RegExp("Trident/.*rv:([0-9]{1,}[.0-9]{0,})"); if (re.exec(navigator.userAgent) !== null) { browserNamed = 'MSIE'; ieVersion = parseFloat( RegExp.$1 ); } } } return ieVersion; }, isIePre11: function () { var ieVersion = theClient.getIeVersion(); if ( ieVersion !== -1.0 && ieVersion < 11.0 ) return true; return false; }, isIePost11: function () { if (theClient.getIeVersion() >= 11.0 ) return true; return false; }, isIe: function () { if (theClient.getIeVersion() === -1.0 ) return false; return true; }, isChrome: function () { if (browserNamed !== null) return (browserNamed === 'Chrome'); if (navigator.userAgent.indexOf("Chrome") !== -1) { browserNamed = 'Chrome'; return true; } return false; }, isNetscape: function () { // IE sometimes mimics netscape if (browserNamed !== null) return (browserNamed === 'Netscape'); if (navigator.appName === 'Netscape' && navigator.userAgent.indexOf("Trident") === -1) { browserNamed = 'Netscape'; return true; } return false; }, isFirefox: function () { if (browserNamed !== null) return (browserNamed === 'FF'); if (navigator.userAgent.indexOf("Firefox") !== -1) { browserNamed = 'FF'; return true; } return false; }, isSafari: function () { // Chrome sometimes mimics Safari if (browserNamed !== null) return (browserNamed === 'Safari'); if (navigator.userAgent.indexOf("Safari") !== -1 && navigator.userAgent.indexOf('Chrome') === -1) { browserNamed = 'Safari'; return true; } return false; }, isOpera: function () { if (browserNamed !== null) return (browserNamed === 'Opera'); if (navigator.userAgent.indexOf("Presto") !== -1) { browserNamed = 'Opera'; return true; } return false; }, nameIs: function () { // simple enough, this needs no comment! if (browserNamed === null && theClient.isChrome() === false // Looking in the order of popularity && theClient.isFirefox() === false && theClient.isIe() === false && theClient.isSafari() === false && theClient.isOpera() === false && theClient.isNetscape() === false) browserNamed = navigator.appName; // Don't know what else to look for. return browserNamed; } }; }()); function waitCursor(obj) // DEAD CODE? { //document.body.style.cursor="wait" obj.style.cursor="wait"; } function endWaitCursor(obj) // DEAD CODE? { obj.style.cursor=""; } function getURLParam() { // Retrieve variable value from an url. // Can be called either: // getURLParam(url, name) // or: // getURLParam(name) // Second interface will default to using window.location.href var strHref, strParamName; var strReturn = ""; if (arguments.length === 1) { strHref = window.location.href; strParamName = arguments[0]; } else { strHref = arguments[0]; strParamName = arguments[1]; } if ( strHref.indexOf("?") > -1) { var strQueryString = strHref.substr(strHref.indexOf("?")).toLowerCase(); var aQueryString = strQueryString.split("&"); for (var iParam = 0; iParam < aQueryString.length; iParam++) { if (aQueryString[iParam].indexOf(strParamName.toLowerCase() + "=") > -1) { var aParam = aQueryString[iParam].split("="); strReturn = aParam[1]; break; } } } return unescape(strReturn); } function makeHiddenInput(theForm,aName,aValue) { // Create a hidden input to hold a value $(theForm).find("input:last").after(""); } function updateOrMakeNamedVariable(theForm,aName,aValue) { // Store a value to a named input. Will make the input if necessary var inp = normed($(theForm).find("input[name='"+aName+"']:last")); if (inp) { $(inp).val(aValue); inp.disabled = false; } else makeHiddenInput(theForm,aName,aValue); } function disableNamedVariable(theForm,aName) { // Store a value to a named input. Will make the input if necessary var inp = normed($(theForm).find("input[name='"+aName+"']:last")); if (inp) inp.disabled = true; } function parseUrlAndUpdateVars(theForm,href) // DEAD CODE? { // Parses the URL and converts GET vals to POST vals var url = href; var extraIx = url.indexOf("?"); if (extraIx > 0) { var extra = url.substring(extraIx+1); url = url.substring(0,extraIx); // now extra must be repeatedly broken into name=var extraIx = extra.indexOf("="); for (; extraIx > 0;extraIx = extra.indexOf("=")) { var aValue; var aName = extra.substring(0,extraIx); var endIx = extra.indexOf("&"); if (endIx>0) { aValue = extra.substring(extraIx+1,endIx); extra = extra.substring(endIx+1); } else { aValue = extra.substring(extraIx+1); extra = ""; } if (aName.length > 0 && aValue.length > 0) updateOrMakeNamedVariable(theForm,aName,aValue); } } return url; } function postTheForm(formName,href) { // posts the form with a passed in href var goodForm = normed($("form[name='"+formName+"']")); if (goodForm) { if (href && href.length > 0) { $(goodForm).attr('action',href); // just attach the straight href } $(goodForm).attr('method','POST'); $(goodForm).submit(); } return false; // Meaning do not continue with anything else } function setVarAndPostForm(aName,aValue,formName) { // Sets a specific variable then posts var goodForm = normed($("form[name='"+formName+"']")); if (goodForm) { updateOrMakeNamedVariable(goodForm,aName,aValue); } return postTheForm(formName,window.location.href); } // json help routines function tdbGetJsonRecord(trackName) { return hgTracks.trackDb[trackName]; } // NOTE: These must jive with tdbKindOfParent() and tdbKindOfChild() in trackDb.h function tdbIsFolder(tdb) { return (tdb.kindOfParent === 1); } function tdbIsComposite(tdb) { return (tdb.kindOfParent === 2); } function tdbIsMultiTrack(tdb) { return (tdb.kindOfParent === 3); } function tdbIsView(tdb) { return (tdb.kindOfParent === 4); } // Don't expect to use function tdbIsContainer(tdb) { return (tdb.kindOfParent === 2 || tdb.kindOfParent === 3); } function tdbIsLeaf(tdb) { return (tdb.kindOfParent === 0); } function tdbIsFolderContent(tdb) { return (tdb.kindOfChild === 1); } function tdbIsCompositeSubtrack(tdb) { return (tdb.kindOfChild === 2); } function tdbIsMultiTrackSubtrack(tdb) { return (tdb.kindOfChild === 3); } function tdbIsSubtrack(tdb) { return (tdb.kindOfChild === 2 || tdb.kindOfChild === 3); } function tdbHasParent(tdb) { return (tdb.kindOfChild !== 0 && tdb.parentTrack); } function aryFind(ary,val) {// returns the index of a value on the array or -1; for (var ix=0; ix < ary.length; ix++) { if (ary[ix] === val) { return ix; } } return -1; } function aryRemove(ary,vals) { // removes one or more variables that are found in the array for (var vIx=0; vIx < vals.length; vIx++) { var ix = aryFind(ary,vals[vIx]); if (ix !== -1) ary.splice(ix,1); } return ary; } function arysToObj(names,values) { // Make hash type obj with two parallel arrays. var obj = {}; for (var ix=0; ix < names.length; ix++) { obj[names[ix]] = values[ix]; } return obj; } function objNotEmpty(obj) { // returns true on non empty object. Obj should pass $.isPlainObject() if ($.isPlainObject(obj) === false) warn("Only use plain js objects in objNotEmpty()"); return ($.isEmptyObject(obj) === false); } function objKeyCount(obj) { // returns number of keys in object. if (!Object.keys) { var count = 0; for (var key in obj) { count++; } return count; } else return Object.keys(obj).length; } function isInteger(s) { return (!isNaN(parseInt(s)) && isFinite(s) && s.toString().indexOf('.') < 0); } function isFloat(s) { return (!isNaN(parseFloat(s)) && isFinite(s)); } function validateInt(obj,min,max) { // validates an integer which may be restricted to a range (if min and/or max are numbers) var title = obj.title; var rangeMin=parseInt(min); var rangeMax=parseInt(max); if (title.length === 0) title = "Value"; var popup=( theClient.isIePre11() === false ); for (;;) { if ((obj.value === undefined || obj.value === null || obj.value === "") && isInteger(obj.defaultValue)) obj.value = obj.defaultValue; if (!isInteger(obj.value)) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be an integer.",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be an integer."); obj.value = obj.defaultValue; return false; } } var val = parseInt(obj.value); if (isInteger(min) && isInteger(max)) { if (val < rangeMin || val > rangeMax) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be between "+rangeMin+ " and "+rangeMax+".",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be between "+ rangeMin+" and "+rangeMax+"."); obj.value = obj.defaultValue; return false; } } } else if (isInteger(min)) { if (val < rangeMin) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be no less than "+ rangeMin+".",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be no less than "+ rangeMin+"."); obj.value = obj.defaultValue; return false; } } } else if (isInteger(max)) { if (val > rangeMax) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be no greater than "+ rangeMax+".",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be no greater than "+ rangeMax+"."); obj.value = obj.defaultValue; return false; } } } return true; } } function validateFloat(obj,min,max) { // validates an float which may be restricted to a range (if min and/or max are numbers) var title = obj.title; var rangeMin=parseFloat(min); var rangeMax=parseFloat(max); if (title.length === 0) title = "Value"; var popup=( theClient.isIePre11() === false ); for (;;) { if ((obj.value === undefined || obj.value === null || obj.value === "") && isFloat(obj.defaultValue)) obj.value = obj.defaultValue; if (!isFloat(obj.value)) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be a number.",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be a number."); // try a prompt box! obj.value = obj.defaultValue; return false; } } var val = parseFloat(obj.value); if (isFloat(min) && isFloat(max)) { if (val < rangeMin || val > rangeMax) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be between "+rangeMin+" and "+ rangeMax+".",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be between "+rangeMin+ " and "+rangeMax+"."); obj.value = obj.defaultValue; return false; } } } else if (isFloat(min)) { if (val < rangeMin) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be no less than "+rangeMin+ ".",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be no less than "+ rangeMin+"."); obj.value = obj.defaultValue; return false; } } } else if (isFloat(max)) { if (val > rangeMax) { if (popup) { obj.value = prompt(title +" is invalid.\nMust be no greater than "+ rangeMax+".",obj.value); continue; } else { alert(title +" of '"+obj.value +"' is invalid.\nMust be no greater than "+ rangeMax+"."); obj.value = obj.defaultValue; return false; } } } return true; } } function validateUrl(url) { // returns true if url is a valid url, otherwise returns false and shows an alert // I got this regexp from http://stackoverflow.com/questions/1303872/url-validation-using-javascript var regexp = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; if (regexp.test(url)) { return true; } else { alert(url + " is an invalid url"); return false; } } function metadataIsVisible(trackName) { var divit = normed($("#div_"+trackName+"_meta")); if (!divit) return false; return ($(divit).css('display') !== 'none'); } function metadataShowHide(trackName,showLonglabel,showShortLabel) { // Will show subtrack specific configuration controls // Config controls not matching name will be hidden var divit = normed($("#div_"+trackName+"_meta")); if (!divit) return false; var img = normed($(divit).prev('a').find("img")); if (img) { if ($(divit).css('display') === 'none') $(img).attr('src','../images/upBlue.png'); else $(img).attr('src','../images/downBlue.png'); } if ($(divit).css('display') === 'none') { if (typeof(subCfg) === "object") {// subCfg.js file included? var cfg = normed($("#div_cfg_"+trackName)); if (cfg) // Hide any configuration when opening metadata $(cfg).hide(); } } var tr = $(divit).parents('tr'); if (tr.length > 0) { tr = tr[0]; var bgClass = null; var classes = $( tr ).attr("class").split(" "); for (var ix=0;ix