@blocklet/xss 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/index.d.ts +1 -1
- package/cjs/types.d.ts +1 -0
- package/cjs/utils.d.ts +22 -1
- package/cjs/utils.js +44 -5
- package/es/index.d.ts +1 -1
- package/es/types.d.ts +1 -0
- package/es/utils.d.ts +22 -1
- package/es/utils.js +42 -4
- package/package.json +1 -1
package/cjs/index.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ declare const _default: {
|
|
|
8
8
|
name?: string;
|
|
9
9
|
type?: string;
|
|
10
10
|
}) => boolean;
|
|
11
|
-
sanitizeSvg: (svgContent: string) => string;
|
|
11
|
+
sanitizeSvg: (svgContent: string, options?: SanitizeOptions, svgOptions?: import("xss").IFilterXSSOptions) => string;
|
|
12
12
|
};
|
|
13
13
|
export default _default;
|
package/cjs/types.d.ts
CHANGED
package/cjs/utils.d.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import * as xss from 'xss';
|
|
1
2
|
import { SanitizeOptions } from './types';
|
|
2
3
|
export declare const initSanitize: (_options?: SanitizeOptions) => any;
|
|
3
|
-
export declare const
|
|
4
|
+
export declare const svgWhiteList: {
|
|
5
|
+
svg: string[];
|
|
6
|
+
circle: string[];
|
|
7
|
+
ellipse: string[];
|
|
8
|
+
line: string[];
|
|
9
|
+
path: string[];
|
|
10
|
+
polygon: string[];
|
|
11
|
+
polyline: string[];
|
|
12
|
+
rect: string[];
|
|
13
|
+
g: string[];
|
|
14
|
+
text: string[];
|
|
15
|
+
defs: never[];
|
|
16
|
+
clipPath: string[];
|
|
17
|
+
mask: string[];
|
|
18
|
+
use: string[];
|
|
19
|
+
linearGradient: string[];
|
|
20
|
+
radialGradient: string[];
|
|
21
|
+
stop: string[];
|
|
22
|
+
pattern: string[];
|
|
23
|
+
};
|
|
24
|
+
export declare const sanitizeSvg: (svgContent: string, options?: SanitizeOptions, svgOptions?: xss.IFilterXSSOptions) => string;
|
|
4
25
|
export declare const isSvgFile: (svgContent: string, file?: {
|
|
5
26
|
name?: string;
|
|
6
27
|
type?: string;
|
package/cjs/utils.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.sanitizeSvg = exports.isSvgFile = exports.initSanitize = void 0;
|
|
6
|
+
exports.svgWhiteList = exports.sanitizeSvg = exports.isSvgFile = exports.initSanitize = void 0;
|
|
7
7
|
var xss = _interopRequireWildcard(require("xss"));
|
|
8
8
|
var _omit = _interopRequireDefault(require("lodash/omit"));
|
|
9
9
|
var _path = _interopRequireDefault(require("path"));
|
|
@@ -66,7 +66,38 @@ const initSanitize = (_options = {}) => {
|
|
|
66
66
|
return sanitize;
|
|
67
67
|
};
|
|
68
68
|
exports.initSanitize = initSanitize;
|
|
69
|
-
|
|
69
|
+
function preserveTagCase(content) {
|
|
70
|
+
const tagCaseMapping = {};
|
|
71
|
+
Object.keys(svgWhiteList).forEach(originalTag => {
|
|
72
|
+
const lowerCaseTag = originalTag.toLowerCase();
|
|
73
|
+
if (lowerCaseTag !== originalTag) {
|
|
74
|
+
tagCaseMapping[lowerCaseTag] = originalTag;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
let result = content;
|
|
78
|
+
Object.entries(tagCaseMapping).forEach(([lowercase, original]) => {
|
|
79
|
+
result = result.replace(new RegExp(`<${lowercase}([^>]*)>`, "gi"), (_match, rest) => `<${original}${rest}>`);
|
|
80
|
+
result = result.replace(new RegExp(`</${lowercase}>`, "gi"), `</${original}>`);
|
|
81
|
+
});
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
function preserveAttrCase(tag, name, value, isWhiteAttr) {
|
|
85
|
+
if (isWhiteAttr) {
|
|
86
|
+
const originalTagKey = Object.keys(svgWhiteList).find(key => key.toLowerCase() === tag.toLowerCase());
|
|
87
|
+
if (originalTagKey) {
|
|
88
|
+
const originalAttrName = svgWhiteList[originalTagKey].find(attr => attr.toLowerCase() === name.toLowerCase());
|
|
89
|
+
if (originalAttrName) {
|
|
90
|
+
value = xss.safeAttrValue(tag, name, value);
|
|
91
|
+
if (value) {
|
|
92
|
+
return originalAttrName + '="' + value + '"';
|
|
93
|
+
} else {
|
|
94
|
+
return originalAttrName;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const svgWhiteList = exports.svgWhiteList = {
|
|
70
101
|
svg: ["width", "height", "viewBox", "xmlns", "version", "preserveAspectRatio", "xml:space"],
|
|
71
102
|
circle: ["cx", "cy", "r", "fill", "stroke", "stroke-width", "fill-opacity", "stroke-opacity"],
|
|
72
103
|
ellipse: ["cx", "cy", "rx", "ry", "fill", "stroke", "stroke-width"],
|
|
@@ -114,13 +145,21 @@ const svgSanitizeOptions = {
|
|
|
114
145
|
}
|
|
115
146
|
}
|
|
116
147
|
};
|
|
117
|
-
const sanitizeSvg = svgContent => {
|
|
148
|
+
const sanitizeSvg = (svgContent, options, svgOptions) => {
|
|
118
149
|
const isSvg = isSvgFile(svgContent);
|
|
119
150
|
if (!isSvg) {
|
|
120
151
|
throw new Error("Invalid SVG content");
|
|
121
152
|
}
|
|
122
|
-
const
|
|
123
|
-
|
|
153
|
+
const filterOptions = {
|
|
154
|
+
...svgSanitizeOptions,
|
|
155
|
+
...(svgOptions || {})
|
|
156
|
+
};
|
|
157
|
+
if (options?.preserveCase) {
|
|
158
|
+
filterOptions.onTagAttr = preserveAttrCase;
|
|
159
|
+
}
|
|
160
|
+
const xssInstance = new xss.FilterXSS(filterOptions);
|
|
161
|
+
const processedContent = xssInstance.process(svgContent);
|
|
162
|
+
return options?.preserveCase ? preserveTagCase(processedContent) : processedContent;
|
|
124
163
|
};
|
|
125
164
|
exports.sanitizeSvg = sanitizeSvg;
|
|
126
165
|
const isSvgFile = (svgContent, file) => {
|
package/es/index.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ declare const _default: {
|
|
|
8
8
|
name?: string;
|
|
9
9
|
type?: string;
|
|
10
10
|
}) => boolean;
|
|
11
|
-
sanitizeSvg: (svgContent: string) => string;
|
|
11
|
+
sanitizeSvg: (svgContent: string, options?: SanitizeOptions, svgOptions?: import("xss").IFilterXSSOptions) => string;
|
|
12
12
|
};
|
|
13
13
|
export default _default;
|
package/es/types.d.ts
CHANGED
package/es/utils.d.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import * as xss from 'xss';
|
|
1
2
|
import { SanitizeOptions } from './types';
|
|
2
3
|
export declare const initSanitize: (_options?: SanitizeOptions) => any;
|
|
3
|
-
export declare const
|
|
4
|
+
export declare const svgWhiteList: {
|
|
5
|
+
svg: string[];
|
|
6
|
+
circle: string[];
|
|
7
|
+
ellipse: string[];
|
|
8
|
+
line: string[];
|
|
9
|
+
path: string[];
|
|
10
|
+
polygon: string[];
|
|
11
|
+
polyline: string[];
|
|
12
|
+
rect: string[];
|
|
13
|
+
g: string[];
|
|
14
|
+
text: string[];
|
|
15
|
+
defs: never[];
|
|
16
|
+
clipPath: string[];
|
|
17
|
+
mask: string[];
|
|
18
|
+
use: string[];
|
|
19
|
+
linearGradient: string[];
|
|
20
|
+
radialGradient: string[];
|
|
21
|
+
stop: string[];
|
|
22
|
+
pattern: string[];
|
|
23
|
+
};
|
|
24
|
+
export declare const sanitizeSvg: (svgContent: string, options?: SanitizeOptions, svgOptions?: xss.IFilterXSSOptions) => string;
|
|
4
25
|
export declare const isSvgFile: (svgContent: string, file?: {
|
|
5
26
|
name?: string;
|
|
6
27
|
type?: string;
|
package/es/utils.js
CHANGED
|
@@ -80,7 +80,40 @@ export const initSanitize = (_options = {}) => {
|
|
|
80
80
|
};
|
|
81
81
|
return sanitize;
|
|
82
82
|
};
|
|
83
|
-
|
|
83
|
+
function preserveTagCase(content) {
|
|
84
|
+
const tagCaseMapping = {};
|
|
85
|
+
Object.keys(svgWhiteList).forEach((originalTag) => {
|
|
86
|
+
const lowerCaseTag = originalTag.toLowerCase();
|
|
87
|
+
if (lowerCaseTag !== originalTag) {
|
|
88
|
+
tagCaseMapping[lowerCaseTag] = originalTag;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
let result = content;
|
|
92
|
+
Object.entries(tagCaseMapping).forEach(([lowercase, original]) => {
|
|
93
|
+
result = result.replace(new RegExp(`<${lowercase}([^>]*)>`, "gi"), (_match, rest) => `<${original}${rest}>`);
|
|
94
|
+
result = result.replace(new RegExp(`</${lowercase}>`, "gi"), `</${original}>`);
|
|
95
|
+
});
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
function preserveAttrCase(tag, name, value, isWhiteAttr) {
|
|
99
|
+
if (isWhiteAttr) {
|
|
100
|
+
const originalTagKey = Object.keys(svgWhiteList).find((key) => key.toLowerCase() === tag.toLowerCase());
|
|
101
|
+
if (originalTagKey) {
|
|
102
|
+
const originalAttrName = svgWhiteList[originalTagKey].find(
|
|
103
|
+
(attr) => attr.toLowerCase() === name.toLowerCase()
|
|
104
|
+
);
|
|
105
|
+
if (originalAttrName) {
|
|
106
|
+
value = xss.safeAttrValue(tag, name, value);
|
|
107
|
+
if (value) {
|
|
108
|
+
return originalAttrName + '="' + value + '"';
|
|
109
|
+
} else {
|
|
110
|
+
return originalAttrName;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export const svgWhiteList = {
|
|
84
117
|
svg: ["width", "height", "viewBox", "xmlns", "version", "preserveAspectRatio", "xml:space"],
|
|
85
118
|
circle: ["cx", "cy", "r", "fill", "stroke", "stroke-width", "fill-opacity", "stroke-opacity"],
|
|
86
119
|
ellipse: ["cx", "cy", "rx", "ry", "fill", "stroke", "stroke-width"],
|
|
@@ -128,13 +161,18 @@ const svgSanitizeOptions = {
|
|
|
128
161
|
}
|
|
129
162
|
}
|
|
130
163
|
};
|
|
131
|
-
export const sanitizeSvg = (svgContent) => {
|
|
164
|
+
export const sanitizeSvg = (svgContent, options, svgOptions) => {
|
|
132
165
|
const isSvg = isSvgFile(svgContent);
|
|
133
166
|
if (!isSvg) {
|
|
134
167
|
throw new Error("Invalid SVG content");
|
|
135
168
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
169
|
+
const filterOptions = { ...svgSanitizeOptions, ...svgOptions || {} };
|
|
170
|
+
if (options?.preserveCase) {
|
|
171
|
+
filterOptions.onTagAttr = preserveAttrCase;
|
|
172
|
+
}
|
|
173
|
+
const xssInstance = new xss.FilterXSS(filterOptions);
|
|
174
|
+
const processedContent = xssInstance.process(svgContent);
|
|
175
|
+
return options?.preserveCase ? preserveTagCase(processedContent) : processedContent;
|
|
138
176
|
};
|
|
139
177
|
export const isSvgFile = (svgContent, file) => {
|
|
140
178
|
if (typeof svgContent !== "string") {
|