Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/shared/src/proxy-console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ export const LEVELS = [

type Level = (typeof LEVELS)[number];

const PRESERVE_HTML_ENTITIES = ["amp", "lt", "gt", "quot", "apos"];

// Double-encode PRESERVE_HTML_ENTITIES so they survive decoding by sanitizeHtml
// in freeCodeCamp/client/src/templates/Challenges/components/output.tsx
function preserveHtmlEntities(str: string): string {
const entityPattern = new RegExp(
`&(${PRESERVE_HTML_ENTITIES.join("|")})(;?)`,
"g",
);
return str.replace(entityPattern, "&$1$2");
}

export class ProxyConsole {
#originalConsole: Console;
#proxyConsole: Console;
Expand All @@ -32,7 +44,9 @@ export class ProxyConsole {
for (const level of LEVELS) {
this.#proxyConsole[level] = (...args: unknown[]) => {
this.#originalConsole[level](...args);
const msg = args.map((arg) => this.#format(arg)).join(" ");
const msg = preserveHtmlEntities(
args.map((arg) => this.#format(arg)).join(" "),
);
this.#calls.push({ level, msg });
};
}
Expand Down
24 changes: 24 additions & 0 deletions packages/tests/proxy-console.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,28 @@ describe("proxy-console", () => {
expect(results).toEqual({});
});
});

describe("preserveHtmlEntities", () => {
it("should double-encode HTML entities so they survive sanitizeHtml decoding", () => {
vi.spyOn(window.console, "log").mockImplementation(vi.fn());
const proxy = new ProxyConsole(window.console, simpleFormat);
proxy.on();

window.console.log("A & B");
const results = proxy.flush();

expect(results.logs).toEqual([{ level: "log", msg: "A & B" }]);
});

it("should not encode & that is not part of an HTML entity", () => {
vi.spyOn(window.console, "log").mockImplementation(vi.fn());
const proxy = new ProxyConsole(window.console, simpleFormat);
proxy.on();

window.console.log("A & B");
const results = proxy.flush();

expect(results.logs).toEqual([{ level: "log", msg: "A & B" }]);
});
});
});