Skip to content

Commit e333def

Browse files
author
liuliquan
authored
Merge pull request #56 from topcoder-platform/PLAT-2461
PLAT-2461 fix style attribute and blockquote
2 parents 19d3d0d + 363293c commit e333def

File tree

2 files changed

+47
-8
lines changed

2 files changed

+47
-8
lines changed

src/domain/Challenge.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ChallengeDomain as LegacyChallengeDomain } from "@topcoder-framework/domain-acl";
22
import { DomainHelper, PhaseFactRequest, PhaseFactResponse } from "@topcoder-framework/lib-common";
3-
import xss from "xss";
3+
import { sanitize } from "../helpers/Sanitizer";
44
import CoreOperations from "../common/CoreOperations";
55
import { Value } from "../dal/models/nosql/parti_ql";
66
import IdGenerator from "../helpers/IdGenerator";
@@ -129,15 +129,15 @@ class ChallengeDomain extends CoreOperations<Challenge, CreateChallengeInput> {
129129
}
130130

131131
public async create(input: CreateChallengeInput, metadata: Metadata): Promise<Challenge> {
132-
input.name = xss(input.name);
132+
input.name = sanitize(input.name);
133133

134134
// prettier-ignore
135135
const handle = metadata?.get("handle").length > 0 ? metadata?.get("handle")?.[0].toString() : "tcwebservice";
136136

137137
if (Array.isArray(input.discussions)) {
138138
for (const discussion of input.discussions) {
139139
discussion.id = IdGenerator.generateUUID();
140-
discussion.name = xss(discussion.name.substring(0, 100));
140+
discussion.name = sanitize(discussion.name.substring(0, 100));
141141
}
142142
}
143143

@@ -151,6 +151,7 @@ class ChallengeDomain extends CoreOperations<Challenge, CreateChallengeInput> {
151151

152152
// End Anti-Corruption Layer
153153

154+
// prettier-ignore
154155
const challenge: Challenge = {
155156
id: IdGenerator.generateUUID(),
156157
created: now,
@@ -177,8 +178,8 @@ class ChallengeDomain extends CoreOperations<Challenge, CreateChallengeInput> {
177178
legacy,
178179
phases,
179180
legacyId: legacyChallengeId != null ? legacyChallengeId : undefined,
180-
description: xss(input.description ?? ""),
181-
privateDescription: xss(input.privateDescription ?? ""),
181+
description: sanitize(input.description ?? "", input.descriptionFormat),
182+
privateDescription: sanitize(input.privateDescription ?? "", input.descriptionFormat),
182183
metadata:
183184
input.metadata.map((m) => {
184185
let parsedValue = m.value;
@@ -286,14 +287,14 @@ class ChallengeDomain extends CoreOperations<Challenge, CreateChallengeInput> {
286287
scanCriteria,
287288
// prettier-ignore
288289
{
289-
name: input.name != null ? xss(input.name) : undefined,
290+
name: input.name != null ? sanitize(input.name) : undefined,
290291
typeId: input.typeId != null ? input.typeId : undefined,
291292
trackId: input.trackId != null ? input.trackId : undefined,
292293
timelineTemplateId: input.timelineTemplateId != null ? input.timelineTemplateId : undefined,
293294
legacy: input.legacy != null ? input.legacy : undefined,
294295
billing: input.billing != null ? input.billing : undefined,
295-
description: input.description != null ? xss(input.description) : undefined,
296-
privateDescription: input.privateDescription != null ? xss(input.privateDescription) : undefined,
296+
description: input.description != null ? sanitize(input.description, input.descriptionFormat ?? challenge.descriptionFormat) : undefined,
297+
privateDescription: input.privateDescription != null ? sanitize(input.privateDescription, input.descriptionFormat ?? challenge.descriptionFormat) : undefined,
297298
descriptionFormat: input.descriptionFormat != null ? input.descriptionFormat : undefined,
298299
task: input.task != null ? input.task : undefined,
299300
winners: input.winnerUpdate != null ? input.winnerUpdate.winners : undefined,

src/helpers/Sanitizer.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as xss from "xss";
2+
3+
const xssWhiteTags = xss.getDefaultWhiteList();
4+
for (const key of Object.keys(xssWhiteTags)) {
5+
// Allow style attribute
6+
if (!xssWhiteTags[key]?.includes("style")) {
7+
xssWhiteTags[key]?.push("style");
8+
}
9+
}
10+
11+
// XSS filter for html
12+
const htmlXSS = new xss.FilterXSS();
13+
14+
// XSS filter for markdown
15+
const markdownXSS = new xss.FilterXSS({
16+
whiteList: xssWhiteTags,
17+
escapeHtml: (html) => {
18+
const splitted = html.split("\n");
19+
return splitted
20+
.map((str) => {
21+
// Handle blockquote which starts with '>'
22+
const blockquoteMatched = str.match(/^\s*>/);
23+
if (blockquoteMatched) {
24+
// prettier-ignore
25+
return blockquoteMatched[0] + str.substring(blockquoteMatched[0].length).replace(/</g, "&lt;").replace(/>/g, "&gt;");
26+
}
27+
return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
28+
})
29+
.join("\n");
30+
},
31+
});
32+
33+
export function sanitize(str: string, format: string = "html"): string {
34+
if (!str) {
35+
return str;
36+
}
37+
return format === "markdown" ? markdownXSS.process(str) : htmlXSS.process(str);
38+
}

0 commit comments

Comments
 (0)