diff --git a/src/xss-filters.js b/src/xss-filters.js index 42994eb..d006b19 100644 --- a/src/xss-filters.js +++ b/src/xss-filters.js @@ -251,6 +251,57 @@ exports._getPrivFilters = function () { } return null; } + + /* + * Create URL whitelist filter + * Ref: https://url.spec.whatwg.org/#url-parsing + * @param {boolean} allowAllSafeURIs - to enable inherited protocol (//), blob:, http://, https://, ftp://, "data:image/[gif,jpeg,png];base64," are allowed + * @param {boolean} allowInheritProto - to enable inherited protocol (//), or otherwise, only http:// and https:// are allowed + * @param {boolean} allowRelPath - to allow relative path + * @param {boolean} allowHosts - an optional array of hostnames that each matches /^[\w\.-]+$/. If any one is found unmatched, return null + * @param {boolean} reqHtmlDecode - to run html decoding first before applying the tests + * @returns {function|null} the yuwl filter that tests value according to the config + */ + function yuwlFactory (allowAllSafeURIs, allowInheritProto, allowRelPath, allowHosts, reqHtmlDecode) { + var i, n, reHost, reHosts, + reProto = allowAllSafeURIs ? + /^[\x00-\x20]*(?:(?:(blob:)?https?|ftp):\/\/|data:image\/(?:gif|jpe?g|png);base64,|\/\/)/i : + allowInheritProto ? + /^[\x00-\x20]*(?:https?:\/\/|\/\/)/i : + /^[\x00-\x20]*https?:\/\//i, + reRelPath = allowRelPath && /^[\x00-\x20]*(?![a-z][a-z0-9+-.]*:|[\/\\]{2})/i; + + // create reHosts from the hostList array, that is case insensitive + // reHosts is defined as must start with either of the allowed hosts, and + // followed by either nothing (i.e., end of string), or / ? # \ + // return null if there exists an host not fullfilling the regexp /^[\w\.-]+$/ + if (allowHosts) { + reHost = /^[\w\.-]+$/; + for (i = 0, n = allowHosts.length; i < n; i++) { + if (!reHost.test(allowHosts[i])) { + return null; + } + } + reHosts = new RegExp('^(?:' + + allowHosts.join('|').replace(/\./g, '\\.') + + ')(?:$|[\\/?#\\\\])', 'i'); + } + + return function(url) { + if (reqHtmlDecode) { + url = htmlDecode(url); + } + // either its a relativeURL, or + // its protocol is allowed, and no host restrictions, or + // its protocol and host is both allowed + if ((allowRelPath && reRelPath.test(url)) || + ((result = reProto.exec(url)) !== null && + (!reHosts || (reHosts && reHosts.test(url.slice(result[0].length)))))) { + return url; + } + return 'x-' + url; + }; + } function cssEncode(chr) { @@ -270,6 +321,8 @@ exports._getPrivFilters = function () { } return (x = { + yuwl: null, + yuwlFactory: yuwlFactory, yHtmlDecode: htmlDecode, /* * @param {string} s - An untrusted uri input