Skip to content

Commit c130ef1

Browse files
committed
Prevent escapes in no-markdown fields
1 parent 695a83f commit c130ef1

File tree

4 files changed

+24
-22
lines changed

4 files changed

+24
-22
lines changed

backend/src/common/schema/message.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { MessageFlags } from "oceanic.js";
66
import { TomlDate } from "smol-toml";
77
import { z } from "zod/v4";
88

9-
export const MessageLiteral = message(z.string());
9+
export const MessageLiteral = message(() => z.string());
1010
export type MessageLiteral = z.infer<typeof MessageLiteral>;
1111

1212
export function messageTemplate<S extends TemplateSchema>(schema: S) {
13-
const result = message(template(schema))
13+
const result = message(markdown => template(schema, markdown))
1414
.transform(({ content, embeds, ...message }) => (params: ParameterRecord<S>) => ({
1515
content: content?.(params),
1616
embeds: embeds?.map(
@@ -50,9 +50,9 @@ export function messageTemplate<S extends TemplateSchema>(schema: S) {
5050
return result;
5151
}
5252

53-
function message<Z extends z.ZodTypeAny>(stringType: Z) {
53+
function message<Z extends z.ZodTypeAny>(stringType: (markdown: boolean) => Z) {
5454
return z.strictObject({
55-
content: stringType,
55+
content: stringType(true),
5656
allowed_mentions: z.strictObject({
5757
everyone: z.boolean(),
5858
replied_user: z.boolean(),
@@ -70,24 +70,24 @@ function message<Z extends z.ZodTypeAny>(stringType: Z) {
7070
}));
7171
}
7272

73-
function embed<Z extends z.ZodType>(stringType: Z) {
73+
function embed<Z extends z.ZodType>(stringType: (markdown: boolean) => Z) {
7474
return z.strictObject({
75-
title: stringType,
76-
description: stringType,
77-
url: stringType,
75+
title: stringType(true),
76+
description: stringType(true),
77+
url: stringType(false),
7878
timestamp: z.instanceof(TomlDate).transform(date => date.toISOString()), // TODO is this filter consistent with Discord's
7979
color: Color,
80-
footer: z.strictObject({ text: stringType, icon: stringType }),
81-
image: stringType.transform(url => ({ url })),
82-
thumbnail: stringType.transform(url => ({ url })),
80+
footer: z.strictObject({ text: stringType(false), icon: stringType(false) }),
81+
image: stringType(false).transform(url => ({ url })),
82+
thumbnail: stringType(false).transform(url => ({ url })),
8383
author: z.strictObject({
84-
name: stringType,
85-
url: stringType.optional(),
86-
icon_url: stringType.optional(),
84+
name: stringType(false),
85+
url: stringType(false).optional(),
86+
icon_url: stringType(false).optional(),
8787
}).transform(({ icon_url, ...input }) => ({ iconURL: icon_url, ...input })),
8888
fields: z.strictObject({
89-
name: stringType,
90-
value: stringType,
89+
name: stringType(true),
90+
value: stringType(true),
9191
inline: z.boolean().optional(),
9292
}).array(),
9393
}).partial();

backend/src/common/schema/template.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import { parseTemplate, type TemplateSchema } from "#common/template/index.ts";
33
import { z } from "zod/v4";
44

5-
export function template<S extends TemplateSchema>(schema: S) {
5+
export function template<S extends TemplateSchema>(schema: S, allowEscape = true) {
66
return z.string().transform((input, context) => {
7-
const result = parseTemplate(input, schema);
7+
const result = parseTemplate(input, schema, allowEscape);
88

99
if (typeof result === "string") {
1010
context.addIssue({ message: result });

backend/src/common/template/formatting.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { TokenType, type Token } from "#common/template/parsing.ts";
55
import { dateToUnixSeconds, humanizeDuration } from "#common/time.ts";
66
import { INTERNAL_TYPE_INTEGRITY } from "#environment.ts";
77

8-
export function formatTokens(params: ParameterRecord, tokens: Token[]): string {
8+
export function formatTokens(params: ParameterRecord, tokens: Token[], allowEscape = true): string {
99
let result = "";
1010

1111
for (const token of tokens) {
@@ -14,7 +14,9 @@ export function formatTokens(params: ParameterRecord, tokens: Token[]): string {
1414
continue;
1515
}
1616

17-
const escaped = !(token.wrapper === FormattingWrapper.InlineCodeblock || token.wrapper === FormattingWrapper.MultilineCodeblock);
17+
const escaped = allowEscape
18+
&& token.wrapper !== FormattingWrapper.InlineCodeblock
19+
&& token.wrapper !== FormattingWrapper.MultilineCodeblock;
1820

1921
let output: string;
2022

backend/src/common/template/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { parseTemplateTokens, TokenType } from "#common/template/parsing.ts";
1616
* @returns A function to call to format data with the template,
1717
* or an error denoting something that went wrong with parsing.
1818
*/
19-
export function parseTemplate<S extends TemplateSchema>(template: string, schema: S): ((params: ParameterRecord<S>) => string) | string {
19+
export function parseTemplate<S extends TemplateSchema>(template: string, schema: S, allowEscape = true): ((params: ParameterRecord<S>) => string) | string {
2020
// just a typed wrapper
2121
const tokens = parseTemplateTokens(template, schema);
2222

@@ -31,7 +31,7 @@ export function parseTemplate<S extends TemplateSchema>(template: string, schema
3131
return () => value;
3232
}
3333

34-
return params => formatTokens(params, tokens);
34+
return params => formatTokens(params, tokens, allowEscape);
3535
}
3636

3737
export type TemplateSchema = Record<string, ParameterType>;

0 commit comments

Comments
 (0)