Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make TAG and ATTR cfg options case-sensitive when parsing XHTML #692

Merged
merged 1 commit into from Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 37 additions & 23 deletions src/purify.js
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -497,27 +511,27 @@ 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) {
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
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) {
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
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 */
Expand Down
5 changes: 3 additions & 2 deletions src/utils.js
Expand Up @@ -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.
Expand All @@ -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)) {
Expand Down
20 changes: 20 additions & 0 deletions test/test-suite.js
Expand Up @@ -1842,6 +1842,26 @@
});
});

QUnit.test(
'Config-Flag tests: PARSER_MEDIA_TYPE + ALLOWED_TAGS/ALLOWED_ATTR',
function (assert) {
assert.contains(
DOMPurify.sanitize(
'<a href="#">abc</a><CustomTag customattr="bar" CustomAttr="foo"/>',
{
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
ALLOWED_TAGS: ['a', 'CustomTag'],
ALLOWED_ATTR: ['href', 'CustomAttr'],
}
),
[
'<a xmlns="http://www.w3.org/1999/xhtml" href="#">abc</a>' +
'<CustomTag xmlns="http://www.w3.org/1999/xhtml" CustomAttr="foo"></CustomTag>',
]
);
}
);

QUnit.test('Test invalid xml', function (assert) {
var tests = [
{
Expand Down