File tree Expand file tree Collapse file tree 5 files changed +49
-4
lines changed
parser/src/parser/rules/block/module/include Expand file tree Collapse file tree 5 files changed +49
-4
lines changed Original file line number Diff line number Diff 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 = / \[ \[ i n c l u d e \s ( [ ^ \] ] * (?: \] (? ! \] ) [ ^ \] ] * ) * ) \] \] / gi ;
98+ const INCLUDE_PATTERN = / ^ \[ \[ i n c l u d e \s ( [ ^ \] ] * (?: \] (? ! \] ) [ ^ \] ] * ) * ) \] \] / gim ;
9699
97100/**
98101 * Parse the inner content of an `[[include ...]]` directive into a page reference
Original file line number Diff line number Diff 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" ,
Original file line number Diff line number Diff 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 } ) => {
Original file line number Diff line number Diff 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 } ) ;
Original file line number Diff line number Diff 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 ( / s t y l e = " d i s p l a y : \s * n o n e " / ) ;
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" } ] ;
You can’t perform that action at this time.
0 commit comments