diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 90e0f54b3d..2359223491 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -282,7 +282,7 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, result.cache = cache result.outDir = conf.outDir.string result.isPureRst = isPureRst - var options= {roSupportRawDirective, roSupportMarkdown, roPreferMarkdown} + var options= {roSupportRawDirective, roSupportMarkdown, roPreferMarkdown, roSandboxDisabled} if not isPureRst: options.incl roNimFile result.sharedState = newRstSharedState( options, filename.string, diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 7919537673..93c2231dc6 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -235,6 +235,12 @@ type ## to Markdown) -- implies `roSupportMarkdown` roNimFile ## set for Nim files where default interpreted ## text role should be :nim: + roSandboxDisabled ## this option enables certain options + ## (e.g. raw, include) + ## which are disabled by default as they can + ## enable users to read arbitrary data and + ## perform XSS if the parser is used in a web + ## app. RstParseOptions* = set[RstParseOption] @@ -260,7 +266,8 @@ type mwBrokenLink = "broken link '$1'", mwUnsupportedLanguage = "language '$1' not supported", mwUnsupportedField = "field '$1' not supported", - mwRstStyle = "RST style: $1" + mwRstStyle = "RST style: $1", + meSandboxedDirective = "disabled directive: '$1'", MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind, arg: string) {.closure, gcsafe.} ## what to do in case of an error @@ -315,6 +322,7 @@ const ":geek:": "icon_e_geek", ":ugeek:": "icon_e_ugeek" } + SandboxDirAllowlist = ["image", "code", "code-block"] type TokType = enum @@ -2987,10 +2995,14 @@ proc dirCodeBlock(p: var RstParser, nimExtension = false): PRstNode = ## ## As an extension this proc will process the ``file`` extension field and if ## present will replace the code block with the contents of the referenced - ## file. + ## file. This behaviour is disabled in sandboxed mode and can be re-enabled + ## with the `roSandboxDisabled` flag. result = parseDirective(p, rnCodeBlock, {hasArg, hasOptions}, parseLiteralBlock) var filename = strip(getFieldValue(result, "file")) if filename != "": + if roSandboxDisabled notin p.s.options: + let tok = p.tok[p.idx-2] + rstMessage(p, meSandboxedDirective, "file", tok.line, tok.col) var path = p.findRelativeFile(filename) if path == "": rstMessage(p, meCannotOpenFile, filename) var n = newRstNode(rnLiteralBlock) @@ -3086,6 +3098,11 @@ proc dirRaw(p: var RstParser): PRstNode = proc selectDir(p: var RstParser, d: string): PRstNode = result = nil + let tok = p.tok[p.idx-2] # report on directive in ".. directive::" + if roSandboxDisabled notin p.s.options: + if d notin SandboxDirAllowlist: + rstMessage(p, meSandboxedDirective, d, tok.line, tok.col) + case d of "admonition", "attention", "caution": result = dirAdmonition(p, d) of "code": result = dirCodeBlock(p) @@ -3112,7 +3129,6 @@ proc selectDir(p: var RstParser, d: string): PRstNode = of "title": result = dirTitle(p) of "warning": result = dirAdmonition(p, d) else: - let tok = p.tok[p.idx-2] # report on directive in ".. directive::" rstMessage(p, meInvalidDirective, d, tok.line, tok.col) proc prefix(ftnType: FootnoteType): string = diff --git a/tests/stdlib/trstgen.nim b/tests/stdlib/trstgen.nim index 2be65b35b3..995c0a1519 100644 --- a/tests/stdlib/trstgen.nim +++ b/tests/stdlib/trstgen.nim @@ -47,6 +47,10 @@ proc optionListLabel(opt: string): string = opt & "" +const + NoSandboxOpts = {roPreferMarkdown, roSupportMarkdown, roNimFile, roSandboxDisabled} + + suite "YAML syntax highlighting": test "Basics": let input = """.. code-block:: yaml @@ -631,7 +635,9 @@ let x = 1 let p3 = """
Par3 """ & id"value3" & ".
" let p4 = """Par4 value4.
""" let expected = p1 & p2 & "\n" & p3 & "\n" & p4 - check(input.toHtml == expected) + check( + input.toHtml(NoSandboxOpts) == expected + ) test "role directive": let input = dedent""" @@ -642,7 +648,10 @@ let x = 1 :language: brainhelp """ var warnings = new seq[string] - let output = input.toHtml(warnings=warnings) + let output = input.toHtml( + NoSandboxOpts, + warnings=warnings + ) check(warnings[].len == 1 and "language 'brainhelp' not supported" in warnings[0]) test "RST comments": @@ -1187,7 +1196,9 @@ Test1 .. tip:: endOf tip .. warning:: endOf warning """ - let output0 = input0.toHtml + let output0 = input0.toHtml( + NoSandboxOpts + ) for a in ["admonition", "attention", "caution", "danger", "error", "hint", "important", "note", "tip", "warning" ]: doAssert "endOf " & a & "" in output0 @@ -1198,7 +1209,9 @@ Test1 Test paragraph. """ - let output1 = input1.toHtml + let output1 = input1.toHtml( + NoSandboxOpts + ) doAssert "endOfError" in output1 doAssert "Test paragraph.
" in output1 doAssert "class=\"admonition admonition-error\"" in output1 @@ -1210,7 +1223,9 @@ Test1 Test paragraph. """ - let output2 = input2.toHtml + let output2 = input2.toHtml( + NoSandboxOpts + ) doAssert "endOfError Test2p." in output2 doAssert "Test paragraph.
" in output2 doAssert "class=\"admonition admonition-error\"" in output2 @@ -1218,7 +1233,9 @@ Test1 let input3 = dedent """ .. note:: endOfNote """ - let output3 = input3.toHtml + let output3 = input3.toHtml( + NoSandboxOpts + ) doAssert "endOfNote" in output3 doAssert "class=\"admonition admonition-info\"" in output3 @@ -1303,7 +1320,9 @@ Test1 That was a transition. """ - let output1 = input1.toHtml + let output1 = input1.toHtml( + NoSandboxOpts + ) doAssert "Nim))""") check("(([Nim](javascript://nim-lang.org/)))".toHtml == """((Nim))""") + +suite "local file inclusion": + test "cannot include files in sandboxed mode": + var error = new string + discard ".. include:: ./readme.md".toHtml(error=error) + check(error[] == "input(1, 11) Error: disabled directive: 'include'") + + test "code-block file directive is disabled": + var error = new string + discard ".. code-block:: nim\n :file: ./readme.md".toHtml(error=error) + check(error[] == "input(2, 20) Error: disabled directive: 'file'") +