From 60f2582bb80f1f09a2c4da3822bed4b3f09d68f7 Mon Sep 17 00:00:00 2001 From: mdk000 <17576893+mdk000@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:54:57 +0100 Subject: [PATCH] feat: single-quoted attribute value syntax support --- README.md | 14 ++++++++++++++ lib/default.js | 1 + lib/xss.js | 5 ++++- test/test_xss.js | 16 ++++++++++++++++ typings/xss.d.ts | 2 ++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b40bdaf4..9471fbfc 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,20 @@ function safeAttrValue(tag, name, value) { } ``` +### Customize output attribute value syntax for HTML + +By specifying a `singleQuotedAttributeValue`. Use `true` for `'`. Otherwise default `"` will be used + +```javascript +var options = { + singleQuotedAttributeValue: true, +}; +// With the configuration specified above, the following HTML: +// Hello +// would become: +// Hello +``` + ### Customize CSS filter If you allow the attribute `style`, the value will be processed by [cssfilter](https://github.com/leizongmin/js-css-filter) module. The cssfilter module includes a default css whitelist. You can specify the options for cssfilter module like this: diff --git a/lib/default.js b/lib/default.js index 142f58ea..9fa05364 100644 --- a/lib/default.js +++ b/lib/default.js @@ -455,5 +455,6 @@ exports.onIgnoreTagStripAll = onIgnoreTagStripAll; exports.StripTagBody = StripTagBody; exports.stripCommentTag = stripCommentTag; exports.stripBlankChar = stripBlankChar; +exports.attributeWrapSign = '"'; exports.cssFilter = defaultCSSFilter; exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList; diff --git a/lib/xss.js b/lib/xss.js index ed810492..b78cec4a 100644 --- a/lib/xss.js +++ b/lib/xss.js @@ -100,6 +100,8 @@ function FilterXSS(options) { options.whiteList = DEFAULT.whiteList; } + this.attributeWrapSign = options.singleQuotedAttributeValue === true ? "'" : DEFAULT.attributeWrapSign; + options.onTag = options.onTag || DEFAULT.onTag; options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr; options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag; @@ -137,6 +139,7 @@ FilterXSS.prototype.process = function (html) { var onIgnoreTagAttr = options.onIgnoreTagAttr; var safeAttrValue = options.safeAttrValue; var escapeHtml = options.escapeHtml; + var attributeWrapSign = me.attributeWrapSign; var cssFilter = me.cssFilter; // remove invisible characters @@ -190,7 +193,7 @@ FilterXSS.prototype.process = function (html) { // call `safeAttrValue()` value = safeAttrValue(tag, name, value, cssFilter); if (value) { - return name + '="' + value + '"'; + return name + '=' + attributeWrapSign + value + attributeWrapSign; } else { return name; } diff --git a/test/test_xss.js b/test/test_xss.js index 5a668b23..ca0e6f20 100644 --- a/test/test_xss.js +++ b/test/test_xss.js @@ -428,6 +428,22 @@ describe("test XSS", function() { ); }); + it("#singleQuotedAttributeValue", function() { + assert.equal(xss('not-defined'), 'not-defined'); + assert.equal( + xss('single-quoted', { singleQuotedAttributeValue: true }), + 'single-quoted' + ); + assert.equal( + xss('double-quoted', { singleQuotedAttributeValue: false }), + 'double-quoted' + ); + assert.equal( + xss('invalid-value', { singleQuotedAttributeValue: 'invalid' }), + 'invalid-value' + ); + }) + it("no options mutated", function() { var options = {}; diff --git a/typings/xss.d.ts b/typings/xss.d.ts index f55d44a8..bdb62208 100644 --- a/typings/xss.d.ts +++ b/typings/xss.d.ts @@ -22,6 +22,7 @@ declare module "xss" { stripIgnoreTagBody?: boolean | string[]; allowCommentTag?: boolean; stripBlankChar?: boolean; + singleQuotedAttributeValue?: boolean; css?: {} | boolean; } @@ -195,6 +196,7 @@ declare module "xss" { export function onIgnoreTagStripAll(): string; export const stripCommentTag: EscapeHandler; export const stripBlankChar: EscapeHandler; + export const attributeWrapSign: string; export const cssFilter: ICSSFilter; export function getDefaultCSSWhiteList(): ICSSFilter;