Skip to content

Commit

Permalink
Merge pull request #96 from Esri/supported-tag-update
Browse files Browse the repository at this point in the history
feat: add support for html 5 tags
  • Loading branch information
BlakeStearman authored Apr 29, 2024
2 parents dc79102 + dbb1b3d commit de0aeda
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 15 deletions.
27 changes: 19 additions & 8 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ describe("Sanitizer", () => {

// Extending the defaults
const sanitizer2 = new Sanitizer(
{ allowCommentTag: false, whiteList: { blockquote: [] } },
{ allowCommentTag: false, whiteList: { blink: [] } },
true
);
const defaultSanitizer2 = new Sanitizer();
const filterOptions2 = Object.create(defaultSanitizer2.arcgisFilterOptions);
filterOptions2.whiteList = defaultSanitizer2.arcgisWhiteList;
filterOptions2.whiteList.blockquote = [];
filterOptions2.whiteList.blink = [];
filterOptions2.allowCommentTag = false;
expect(sanitizer2.xssFilterOptions).toEqual(filterOptions2);

Expand Down Expand Up @@ -389,21 +389,21 @@ describe("Sanitizer", () => {
// Escaped double quotes are encoded
expect(sanitizer.sanitizeHTMLAttribute('button', 'aria-label', '\"Text content\"')).toBe('"Text content"');
// src with javascript URL should be removed
expect(sanitizer.sanitizeHTMLAttribute('img', 'src', 'javascript:alert("xss")')).toBe('');
expect(sanitizer.sanitizeHTMLAttribute('img', 'src', 'javascript:alert("xss")')).toBe('');
// href with javascript URL should be removed
expect(sanitizer.sanitizeHTMLAttribute('a', 'href', 'javascript:alert("xss")')).toBe('');
expect(sanitizer.sanitizeHTMLAttribute('a', 'href', 'javascript:alert("xss")')).toBe('');
// background with javascript URL should be removed
expect(sanitizer.sanitizeHTMLAttribute('div', 'background', 'javascript:alert("xss")')).toBe('');
// style with javascript URL should be removed
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'background-image:url("javascript:alert(\"xss\")")')).toBe('');
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'background-image:url("javascript:alert(\"xss\")")')).toBe('');
// safe styles should be allowed
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'color:red;font-size:12px;')).toBe('color:red; font-size:12px;');
// custom filter removes style value
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'color:red;', { process: (value: string) => value.indexOf('color') !== -1 ? '' : value })).toBe('');
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'color:red;', { process: (value: string) => value.indexOf('color') !== -1 ? '' : value })).toBe('');
// custom filter still disallows javascript URLs
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'background-image:url("javascript:alert(\"xss\")"', { process: (value: string) => value })).toBe('');
expect(sanitizer.sanitizeHTMLAttribute('div', 'style', 'background-image:url("javascript:alert(\"xss\")"', { process: (value: string) => value })).toBe('');
// attempt to prematurely close the HTML element and inject script tag should be thwarted by encoding
expect(sanitizer.sanitizeHTMLAttribute('img', 'alt', '"><script>alert("Text content")</script>')).toBe('&quot;&gt;&lt;script&gt;alert(&quot;Text content&quot;)&lt;/script&gt;')
expect(sanitizer.sanitizeHTMLAttribute('img', 'alt', '"><script>alert("Text content")</script>')).toBe('&quot;&gt;&lt;script&gt;alert(&quot;Text content&quot;)&lt;/script&gt;');

const customSanitizer = new Sanitizer({
safeAttrValue: (tag, name, value, cssFilter) => {
Expand Down Expand Up @@ -574,4 +574,15 @@ describe("Sanitizer", () => {
expect(sanitizer.sanitize(b)).toBe(bExpected);
});

test("pre sanitizes contents", () => {
const sanitizer = new Sanitizer();
const script = "<pre><script>alert(1)</script></pre>";
const sanitizedScript = "<pre>&lt;script&gt;alert(1)&lt;/script&gt;</pre>";
const includesAllowed = "<pre><p><script>alert(1)</script><p></pre>";
const sanitizedAllowed = "<pre><p>&lt;script&gt;alert(1)&lt;/script&gt;<p></pre>";

expect(sanitizer.sanitize(script)).toBe(sanitizedScript);
expect(sanitizer.sanitize(includesAllowed)).toBe(sanitizedAllowed);
});

});
32 changes: 25 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,33 +42,46 @@ export class Sanitizer {
public readonly arcgisWhiteList: IWhiteList = {
a: ["href", "style", "target"],
abbr: ["title"],
article: ["style"],
aside: ["style"],
audio: ["autoplay", "controls", "loop", "muted", "preload"],
b: [],
blockquote: ["style"],
br: [],
code: ["style"],
dd: ["style"],
div: ["align", "style"],
details: ["style"],
div: ["align", "style", "aria-label", "aria-hidden"],
dl: ["style"],
dt: ["style"],
em: [],
figcaption: ["style"],
figure: ["style"],
font: ["color", "face", "size", "style"],
footer: ["style"],
h1: ["style"],
h2: ["style"],
h3: ["style"],
h4: ["style"],
h5: ["style"],
h6: ["style"],
header: ["style"],
hr: [],
i: [],
img: ["alt", "border", "height", "src", "style", "width"],
li: [],
main: ["style"],
mark: ["style"],
nav: ["style"],
ol: [],
p: ["style"],
pre: ["style"],
section: ["style"],
source: ["media", "src", "type"],
span: ["style"],
strong: [],
sub: ["style"],
summary: ["style"],
sup: ["style"],
table: ["border", "cellpadding", "cellspacing", "height", "style", "width"],
tbody: [],
Expand All @@ -93,6 +106,7 @@ export class Sanitizer {
"valign",
"width",
],
time: ["style"],
u: [],
ul: [],
video: [
Expand Down Expand Up @@ -234,15 +248,19 @@ export class Sanitizer {
* @param {{ isProtocolRequired: boolean }} options Configuration options for URL checking.
* @returns {string} The sanitized URL if it's valid, or an empty string if the URL is invalid.
*/
public sanitizeUrl(value: string, options?: {
/** Whether a protocol must exist on the URL for it to be considered valid. Defaults to `true`. If `false` and the provided URL has no protocol, it will be automatically prefixed with `https://`. */
isProtocolRequired?: boolean;
}): string {
public sanitizeUrl(
value: string,
options?: {
/** Whether a protocol must exist on the URL for it to be considered valid. Defaults to `true`. If `false` and the provided URL has no protocol, it will be automatically prefixed with `https://`. */
isProtocolRequired?: boolean;
}
): string {
const { isProtocolRequired = true } = options ?? {};
const protocol = this._trim(value.substring(0, value.indexOf(":")));
const isRootUrl = value === '/';
const isRootUrl = value === "/";
const isUrlFragment = /^#/.test(value);
const isValidProtocol = protocol && this.allowedProtocols.indexOf(protocol.toLowerCase()) > -1;
const isValidProtocol =
protocol && this.allowedProtocols.indexOf(protocol.toLowerCase()) > -1;

if (isRootUrl || isUrlFragment || isValidProtocol) {
return xss.escapeAttrValue(value);
Expand Down

0 comments on commit de0aeda

Please sign in to comment.