[parsing/mfm] Limit inline node recursion to 100
This commit is contained in:
parent
9036eacd98
commit
aa593f78b8
2 changed files with 88 additions and 36 deletions
|
@ -110,9 +110,13 @@ module MfmNodeTypes =
|
||||||
|
|
||||||
type internal UserState =
|
type internal UserState =
|
||||||
{ ParenthesisStack: char list
|
{ ParenthesisStack: char list
|
||||||
LastLine: int64 }
|
LastLine: int64
|
||||||
|
Depth: int64 }
|
||||||
|
|
||||||
static member Default = { ParenthesisStack = []; LastLine = 0 }
|
static member Default =
|
||||||
|
{ ParenthesisStack = []
|
||||||
|
LastLine = 0
|
||||||
|
Depth = 0 }
|
||||||
|
|
||||||
open MfmNodeTypes
|
open MfmNodeTypes
|
||||||
|
|
||||||
|
@ -220,10 +224,10 @@ module private MfmParser =
|
||||||
|
|
||||||
let clearParen = updateUserState <| fun u -> { u with ParenthesisStack = [] }
|
let clearParen = updateUserState <| fun u -> { u with ParenthesisStack = [] }
|
||||||
|
|
||||||
|
let (|GreaterEqualThan|_|) k value = if value >= k then Some() else None
|
||||||
|
|
||||||
// References
|
// References
|
||||||
let node, nodeRef = createParserForwardedToRef ()
|
|
||||||
let inlineNode, inlineNodeRef = createParserForwardedToRef ()
|
let inlineNode, inlineNodeRef = createParserForwardedToRef ()
|
||||||
let simple, simpleRef = createParserForwardedToRef ()
|
|
||||||
|
|
||||||
let seqFlatten items =
|
let seqFlatten items =
|
||||||
seq {
|
seq {
|
||||||
|
@ -451,43 +455,49 @@ module private MfmParser =
|
||||||
|
|
||||||
let prefixedNode (m: ParseMode) : Parser<MfmNode, UserState> =
|
let prefixedNode (m: ParseMode) : Parser<MfmNode, UserState> =
|
||||||
fun (stream: CharStream<_>) ->
|
fun (stream: CharStream<_>) ->
|
||||||
match (stream.Peek(), m) with
|
match stream.UserState.Depth with
|
||||||
// Block nodes, ordered by expected frequency
|
| GreaterEqualThan 100L -> stream |> charNode
|
||||||
| '`', Full -> codeBlockNode <|> codeNode
|
| _ ->
|
||||||
| '\n', Full when stream.Match("\n```") -> codeBlockNode
|
match (stream.Peek(), m) with
|
||||||
| '\n', Full when stream.Match("\n\n```") -> codeBlockNode
|
// Block nodes, ordered by expected frequency
|
||||||
| '>', Full -> quoteNode
|
| '`', Full -> codeBlockNode <|> codeNode
|
||||||
| '<', Full when stream.Match "<center>" -> centerNode
|
| '\n', Full when stream.Match("\n```") -> codeBlockNode
|
||||||
| '\\', Full when stream.Match "\\[" -> mathBlockNode
|
| '\n', Full when stream.Match("\n\n```") -> codeBlockNode
|
||||||
// Inline nodes, ordered by expected frequency
|
| '>', Full -> quoteNode
|
||||||
| '*', (Full | Inline) -> italicAsteriskNode <|> boldAsteriskNode
|
| '<', Full when stream.Match "<center>" -> centerNode
|
||||||
| '_', (Full | Inline) -> italicUnderscoreNode <|> boldUnderscoreNode
|
| '\\', Full when stream.Match "\\[" -> mathBlockNode
|
||||||
| '@', (Full | Inline) -> mentionNode
|
// Inline nodes, ordered by expected frequency
|
||||||
| '#', (Full | Inline) -> hashtagNode
|
| '*', (Full | Inline) -> italicAsteriskNode <|> boldAsteriskNode
|
||||||
| '`', Inline -> codeNode
|
| '_', (Full | Inline) -> italicUnderscoreNode <|> boldUnderscoreNode
|
||||||
| 'h', (Full | Inline) when stream.Match "http" -> urlNode
|
| '@', (Full | Inline) -> mentionNode
|
||||||
| ':', (Full | Inline | Simple) -> emojiCodeNode
|
| '#', (Full | Inline) -> hashtagNode
|
||||||
| '~', (Full | Inline) when stream.Match "~~" -> strikeNode
|
| '`', Inline -> codeNode
|
||||||
| '[', (Full | Inline) -> linkNode
|
| 'h', (Full | Inline) when stream.Match "http" -> urlNode
|
||||||
| '<', (Full | Inline) -> choice inlineTagNodes
|
| ':', (Full | Inline | Simple) -> emojiCodeNode
|
||||||
| '<', Simple when stream.Match "<plain>" -> plainNode
|
| '~', (Full | Inline) when stream.Match "~~" -> strikeNode
|
||||||
| '\\', (Full | Inline) when stream.Match "\\(" -> mathNode
|
| '[', (Full | Inline) -> linkNode
|
||||||
| '$', (Full | Inline) when stream.Match "$[" -> fnNode
|
| '<', (Full | Inline) -> choice inlineTagNodes
|
||||||
| '?', (Full | Inline) when stream.Match "?[" -> linkNode
|
| '<', Simple when stream.Match "<plain>" -> plainNode
|
||||||
// Fallback to char node
|
| '\\', (Full | Inline) when stream.Match "\\(" -> mathNode
|
||||||
| _ -> charNode
|
| '$', (Full | Inline) when stream.Match "$[" -> fnNode
|
||||||
<| stream
|
| '?', (Full | Inline) when stream.Match "?[" -> linkNode
|
||||||
|
// Fallback to char node
|
||||||
|
| _ -> charNode
|
||||||
|
<| stream
|
||||||
|
|
||||||
attempt <| prefixedNode m <|> charNode
|
attempt <| prefixedNode m <|> charNode
|
||||||
|
|
||||||
// Populate references
|
// Populate references
|
||||||
do nodeRef.Value <- parseNode Full
|
let pushDepth = updateUserState (fun u -> { u with Depth = (u.Depth + 1L) })
|
||||||
do inlineNodeRef.Value <- parseNode Inline |>> fun v -> v :?> MfmInlineNode
|
let popDepth = updateUserState (fun u -> { u with Depth = (u.Depth - 1L) })
|
||||||
do simpleRef.Value <- parseNode Simple
|
do inlineNodeRef.Value <- pushDepth >>. (parseNode Inline |>> fun v -> v :?> MfmInlineNode) .>> popDepth
|
||||||
|
|
||||||
|
// Parser abstractions
|
||||||
|
let node = parseNode Full
|
||||||
|
let simple = parseNode Simple
|
||||||
|
|
||||||
// Final parse command
|
// Final parse command
|
||||||
let parse = spaces >>. manyTill node eof .>> spaces
|
let parse = spaces >>. manyTill node eof .>> spaces
|
||||||
|
|
||||||
let parseSimple = spaces >>. manyTill simple eof .>> spaces
|
let parseSimple = spaces >>. manyTill simple eof .>> spaces
|
||||||
|
|
||||||
open MfmParser
|
open MfmParser
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System.Text;
|
||||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Serialization;
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Serialization;
|
||||||
using Iceshrimp.Parsing;
|
using Iceshrimp.Parsing;
|
||||||
|
using Microsoft.FSharp.Collections;
|
||||||
using static Iceshrimp.Parsing.MfmNodeTypes;
|
using static Iceshrimp.Parsing.MfmNodeTypes;
|
||||||
using FSDict = System.Collections.Generic.Dictionary<string, Microsoft.FSharp.Core.FSharpOption<string>?>;
|
using FSDict = System.Collections.Generic.Dictionary<string, Microsoft.FSharp.Core.FSharpOption<string>?>;
|
||||||
|
|
||||||
|
@ -516,10 +518,50 @@ public class MfmTests
|
||||||
</center>
|
</center>
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
AssertionOptions.FormattingOptions.MaxDepth = 100;
|
||||||
var res = Mfm.parse(input);
|
var res = Mfm.parse(input);
|
||||||
MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
|
MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestFnRecursionLimit()
|
||||||
|
{
|
||||||
|
const int iterations = 150;
|
||||||
|
const int limit = 100;
|
||||||
|
|
||||||
|
var input = GetMfm(iterations);
|
||||||
|
var result = Mfm.parse(input);
|
||||||
|
|
||||||
|
// Closing brackets will be grouped at the end, since fn node parser isn't greedy
|
||||||
|
List<MfmNode> expected = [GetExpected(iterations), new MfmTextNode(new string(']', iterations - limit))];
|
||||||
|
|
||||||
|
AssertionOptions.FormattingOptions.MaxDepth = 300;
|
||||||
|
AssertionOptions.FormattingOptions.MaxLines = 1000;
|
||||||
|
result.ToList().Should().Equal(expected, MfmNodeEqual);
|
||||||
|
MfmSerializer.Serialize(result).Should().BeEquivalentTo(input);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
string GetMfm(int count)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
sb.Append("$[test ");
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
sb.Append(']');
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
MfmInlineNode GetExpected(int count, int remaining = limit)
|
||||||
|
{
|
||||||
|
if (remaining <= 0)
|
||||||
|
return new MfmTextNode(GetMfm(count).TrimEnd(']'));
|
||||||
|
|
||||||
|
return new MfmFnNode("test", null, [GetExpected(--count, --remaining)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool MfmNodeEqual(MfmNode a, MfmNode b)
|
private static bool MfmNodeEqual(MfmNode a, MfmNode b)
|
||||||
{
|
{
|
||||||
if (a.GetType() != b.GetType()) return false;
|
if (a.GetType() != b.GetType()) return false;
|
||||||
|
|
Loading…
Add table
Reference in a new issue