From 3ad77506eb599c3bc0086af0e00e499c96029b0a Mon Sep 17 00:00:00 2001 From: Tobias Smolka Date: Mon, 6 Jun 2022 13:30:12 +0200 Subject: [PATCH] Make TAG and ATTR cfg options case-sensitive when parsing XHTML --- src/purify.js | 60 ++++++++++++++++++++++++++++------------------ src/utils.js | 5 ++-- test/test-suite.js | 20 ++++++++++++++++ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/purify.js b/src/purify.js index 9be3e45e..f8ecacc7 100644 --- a/src/purify.js +++ b/src/purify.js @@ -380,29 +380,55 @@ function createDOMPurify(window = getGlobal()) { /* Shield configuration object from prototype pollution */ cfg = clone(cfg); + PARSER_MEDIA_TYPE = + // eslint-disable-next-line unicorn/prefer-includes + SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 + ? (PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE) + : (PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE); + + // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. + transformCaseFunc = + PARSER_MEDIA_TYPE === 'application/xhtml+xml' + ? (x) => x + : stringToLowerCase; + /* Set configuration parameters */ ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg - ? addToSet({}, cfg.ALLOWED_TAGS) + ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg - ? addToSet({}, cfg.ALLOWED_ATTR) + ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg - ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) + ? addToSet( + clone(DEFAULT_URI_SAFE_ATTRIBUTES), + cfg.ADD_URI_SAFE_ATTR, + transformCaseFunc + ) : DEFAULT_URI_SAFE_ATTRIBUTES; DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg - ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) + ? addToSet( + clone(DEFAULT_DATA_URI_TAGS), + cfg.ADD_DATA_URI_TAGS, + transformCaseFunc + ) : DEFAULT_DATA_URI_TAGS; FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg - ? addToSet({}, cfg.FORBID_CONTENTS) + ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS; - FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}; - FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}; + FORBID_TAGS = + 'FORBID_TAGS' in cfg + ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) + : {}; + FORBID_ATTR = + 'FORBID_ATTR' in cfg + ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) + : {}; USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true @@ -443,18 +469,6 @@ function createDOMPurify(window = getGlobal()) { cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; } - PARSER_MEDIA_TYPE = - // eslint-disable-next-line unicorn/prefer-includes - SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 - ? (PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE) - : (PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE); - - // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. - transformCaseFunc = - PARSER_MEDIA_TYPE === 'application/xhtml+xml' - ? (x) => x - : stringToLowerCase; - if (SAFE_FOR_TEMPLATES) { ALLOW_DATA_ATTR = false; } @@ -497,7 +511,7 @@ function createDOMPurify(window = getGlobal()) { ALLOWED_TAGS = clone(ALLOWED_TAGS); } - addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); + addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); } if (cfg.ADD_ATTR) { @@ -505,11 +519,11 @@ function createDOMPurify(window = getGlobal()) { ALLOWED_ATTR = clone(ALLOWED_ATTR); } - addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); + addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); } if (cfg.ADD_URI_SAFE_ATTR) { - addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); + addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); } if (cfg.FORBID_CONTENTS) { @@ -517,7 +531,7 @@ function createDOMPurify(window = getGlobal()) { FORBID_CONTENTS = clone(FORBID_CONTENTS); } - addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS); + addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); } /* Add #text in case KEEP_CONTENT is set to true */ diff --git a/src/utils.js b/src/utils.js index 285ea4f1..1ce7e9a7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -58,7 +58,8 @@ export function unconstruct(func) { } /* Add properties to a lookup table */ -export function addToSet(set, array) { +export function addToSet(set, array, transformCaseFunc) { + transformCaseFunc = transformCaseFunc ? transformCaseFunc : stringToLowerCase; if (setPrototypeOf) { // Make 'in' and truthy checks like Boolean(set.constructor) // independent of any properties defined on Object.prototype. @@ -70,7 +71,7 @@ export function addToSet(set, array) { while (l--) { let element = array[l]; if (typeof element === 'string') { - const lcElement = stringToLowerCase(element); + const lcElement = transformCaseFunc(element); if (lcElement !== element) { // Config presets (e.g. tags.js, attrs.js) are immutable. if (!isFrozen(array)) { diff --git a/test/test-suite.js b/test/test-suite.js index 7e5e286b..540764c5 100644 --- a/test/test-suite.js +++ b/test/test-suite.js @@ -1842,6 +1842,26 @@ }); }); + QUnit.test( + 'Config-Flag tests: PARSER_MEDIA_TYPE + ALLOWED_TAGS/ALLOWED_ATTR', + function (assert) { + assert.contains( + DOMPurify.sanitize( + 'abc', + { + PARSER_MEDIA_TYPE: 'application/xhtml+xml', + ALLOWED_TAGS: ['a', 'CustomTag'], + ALLOWED_ATTR: ['href', 'CustomAttr'], + } + ), + [ + 'abc' + + '', + ] + ); + } + ); + QUnit.test('Test invalid xml', function (assert) { var tests = [ {