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;