Skip to content

Commit 6eb525d

Browse files
authored
fix: includeの行頭限定とembed iframeのstyle/class属性保持 (#29)
- [[include]]の正規表現に^アンカーとmフラグを追加し、行頭以外のincludeを展開しないように修正 - embed-blockのsanitize-html設定にstyleとclass属性を追加
1 parent 9a220a1 commit 6eb525d

File tree

5 files changed

+49
-4
lines changed

5 files changed

+49
-4
lines changed

packages/parser/src/parser/rules/block/module/include/resolve.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ export function resolveIncludes(
9090
/**
9191
* Regex to match [[include ...]] directives.
9292
* Captures the content between [[include and ]] (may span multiple lines).
93+
*
94+
* The `m` flag makes `^` match at line boundaries, enforcing the Wikidot
95+
* rule that `[[include]]` must appear at the start of a line.
9396
*/
9497
// \s (single char, no quantifier) avoids overlap with [^\]]* that causes polynomial backtracking
95-
const INCLUDE_PATTERN = /\[\[include\s([^\]]*(?:\](?!\])[^\]]*)*)\]\]/gi;
98+
const INCLUDE_PATTERN = /^\[\[include\s([^\]]*(?:\](?!\])[^\]]*)*)\]\]/gim;
9699

97100
/**
98101
* Parse the inner content of an `[[include ...]]` directive into a page reference

packages/render/src/elements/embed-block.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ const SANITIZE_CONFIG: sanitizeHtml.IOptions = {
9696
allowedTags: ["iframe"],
9797
allowedAttributes: {
9898
iframe: [
99+
"class",
99100
"src",
101+
"style",
100102
"allow",
101103
"allowfullscreen",
102104
"frameborder",

tests/unit/module/include/resolve.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,22 @@ describe("resolveIncludes", () => {
210210
expect(expanded).toBe("Before\nIncluded\nAfter");
211211
});
212212

213+
test("does not resolve include that is not at line start", () => {
214+
const source = "abc [[include my-page]]";
215+
const fetcher = () => "Should not appear";
216+
217+
const expanded = resolveIncludes(source, fetcher);
218+
expect(expanded).toBe("abc [[include my-page]]");
219+
});
220+
221+
test("does not resolve include preceded by @@", () => {
222+
const source = "@@[[include my-page]]@@";
223+
const fetcher = () => "Should not appear";
224+
225+
const expanded = resolveIncludes(source, fetcher);
226+
expect(expanded).toBe("@@[[include my-page]]@@");
227+
});
228+
213229
test("div blocks spanning across includes are correctly parsed", () => {
214230
const source = "[[include credit:start]]\naaa\n[[include credit:end]]";
215231
const fetcher = (pageRef: { site: string | null; page: string }) => {

tests/unit/parser/settings.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,19 @@ describe("WikitextSettings - Parser", () => {
129129
const fetcher = (ref: { page: string }) => `Content of ${ref.page}`;
130130

131131
it("skips expansion when enablePageSyntax = false", () => {
132-
const source = "Before [[include test-page]] After";
132+
const source = "Before\n[[include test-page]]\nAfter";
133133
const result = resolveIncludes(source, fetcher, { settings: forumSettings });
134134
expect(result).toBe(source);
135135
});
136136

137137
it("expands when enablePageSyntax = true", () => {
138-
const source = "Before [[include test-page]] After";
138+
const source = "Before\n[[include test-page]]\nAfter";
139139
const result = resolveIncludes(source, fetcher, { settings: pageSettings });
140140
expect(result).toContain("Content of test-page");
141141
});
142142

143143
it("expands when settings not specified (backward compat)", () => {
144-
const source = "Before [[include test-page]] After";
144+
const source = "Before\n[[include test-page]]\nAfter";
145145
const result = resolveIncludes(source, fetcher);
146146
expect(result).toContain("Content of test-page");
147147
});

tests/unit/render/embed-block.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,30 @@ describe("embed-block security", () => {
338338
});
339339
});
340340

341+
describe("iframe attributes preservation", () => {
342+
test("style attribute is preserved on iframe", () => {
343+
const ctx = createMockContext({ embedAllowlist: null });
344+
const data = {
345+
contents: '<iframe src="https://example.com/frame.html" style="display: none"></iframe>',
346+
};
347+
renderEmbedBlock(ctx, data);
348+
const output = ctx.getOutput();
349+
expect(output).not.toContain("error-block");
350+
expect(output).toMatch(/style="display:\s*none"/);
351+
});
352+
353+
test("class attribute is preserved on iframe", () => {
354+
const ctx = createMockContext({ embedAllowlist: null });
355+
const data = {
356+
contents: '<iframe src="https://example.com/frame.html" class="my-iframe"></iframe>',
357+
};
358+
renderEmbedBlock(ctx, data);
359+
const output = ctx.getOutput();
360+
expect(output).not.toContain("error-block");
361+
expect(output).toContain('class="my-iframe"');
362+
});
363+
});
364+
341365
describe("custom allowlist", () => {
342366
test("Custom allowlist with host only", () => {
343367
const allowlist: EmbedAllowlistEntry[] = [{ host: "example.com" }];

0 commit comments

Comments
 (0)