From c35f7fff178bddcb4d8165e9f67c97a37d0b912f Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Sat, 21 Dec 2024 03:29:31 +0300 Subject: [PATCH 1/7] Fixed errors in LinkHelper and LinkInlineParser. --- src/Markdig.Tests/TestLinkHelper.cs | 15 ++ src/Markdig.Tests/TestPlayParser.cs | 8 + src/Markdig/Helpers/LinkHelper.cs | 216 ++++++++---------- .../Parsers/Inlines/LinkInlineParser.cs | 15 +- 4 files changed, 124 insertions(+), 130 deletions(-) diff --git a/src/Markdig.Tests/TestLinkHelper.cs b/src/Markdig.Tests/TestLinkHelper.cs index 75acbb19..d46c50ab 100644 --- a/src/Markdig.Tests/TestLinkHelper.cs +++ b/src/Markdig.Tests/TestLinkHelper.cs @@ -89,6 +89,14 @@ public class TestLinkHelper Assert.AreEqual("this\ris\r\na\ntitle", title); } + [Test] + public void TestTitleMultilineWithSpaceAndBackslash() + { + var text = new StringSlice("'a\n\\ \\\ntitle'"); + Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _)); + Assert.AreEqual("a\n\\ \\\ntitle", title); + } + [Test] public void TestUrlAndTitle() { @@ -238,6 +246,13 @@ public class TestLinkHelper } + [Test] + public void TestlLinkReferenceDefinitionInvalid() + { + var text = new StringSlice("[foo]: /url (title) x\n"); + Assert.False(LinkHelper.TryParseLinkReferenceDefinition(ref text, out _, out _, out _, out _, out _, out _)); + } + [Test] public void TestAutoLinkUrlSimple() { diff --git a/src/Markdig.Tests/TestPlayParser.cs b/src/Markdig.Tests/TestPlayParser.cs index 53420b1d..54af3927 100644 --- a/src/Markdig.Tests/TestPlayParser.cs +++ b/src/Markdig.Tests/TestPlayParser.cs @@ -46,6 +46,14 @@ public class TestPlayParser Assert.AreEqual("/yoyo", link?.Url); } + [Test] + public void TestLinkWithMultipleBackslashesInTitle() + { + var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')"); + var link = doc.Descendants().SelectMany(x => x.Inline.Descendants()).FirstOrDefault(); + Assert.AreEqual(@"\\127.0.0.1", link?.Title); + } + [Test] public void TestListBug2() { diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 48581e1e..0356a275 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -545,88 +545,70 @@ public static class LinkHelper enclosingCharacter = c; var closingQuote = c == '(' ? ')' : c; bool hasEscape = false; - // -1: undefined - // 0: has only spaces - // 1: has other characters - int hasOnlyWhiteSpacesSinceLastLine = -1; - while (true) + bool isLineBlank = false; // the first line is never blank + while ((c = text.NextChar()) != '\0') { - c = text.NextChar(); - if (c == '\r' || c == '\n') { - if (hasOnlyWhiteSpacesSinceLastLine >= 0) + if (isLineBlank) { - if (hasOnlyWhiteSpacesSinceLastLine == 1) - { - break; - } - hasOnlyWhiteSpacesSinceLastLine = -1; + break; } + + if (hasEscape) + { + hasEscape = false; + buffer.Append('\\'); + } + buffer.Append(c); + if (c == '\r' && text.PeekChar() == '\n') { buffer.Append('\n'); text.SkipChar(); } - continue; - } - if (c == '\0') - { - break; + isLineBlank = true; } - - if (c == closingQuote) + else if (hasEscape) { - if (hasEscape) + hasEscape = false; + + if (!c.IsAsciiPunctuation()) { - buffer.Append(closingQuote); - hasEscape = false; - continue; + buffer.Append('\\'); } + buffer.Append(c); + } + else if (c == closingQuote) + { // Skip last quote text.SkipChar(); - goto ReturnValid; + title = buffer.ToString(); + return true; } - - if (hasEscape && !c.IsAsciiPunctuation()) - { - buffer.Append('\\'); - } - - if (c == '\\') + else if (c == '\\') { hasEscape = true; - continue; + isLineBlank = false; } - - hasEscape = false; - - if (c.IsSpaceOrTab()) + else { - if (hasOnlyWhiteSpacesSinceLastLine < 0) + if (isLineBlank && !c.IsSpaceOrTab()) { - hasOnlyWhiteSpacesSinceLastLine = 1; + isLineBlank = false; } - } - else if (c != '\n' && c != '\r' && text.PeekChar() != '\n') - { - hasOnlyWhiteSpacesSinceLastLine = 0; - } - buffer.Append(c); + buffer.Append(c); + } } } buffer.Dispose(); title = null; return false; - - ReturnValid: - title = buffer.ToString(); - return true; } public static bool TryParseTitleTrivia(ref T text, out string? title, out char enclosingCharacter) where T : ICharIterator @@ -642,88 +624,70 @@ public static class LinkHelper enclosingCharacter = c; var closingQuote = c == '(' ? ')' : c; bool hasEscape = false; - // -1: undefined - // 0: has only spaces - // 1: has other characters - int hasOnlyWhiteSpacesSinceLastLine = -1; - while (true) + bool isLineBlank = false; // the first line is never blank + while ((c = text.NextChar()) != '\0') { - c = text.NextChar(); - if (c == '\r' || c == '\n') { - if (hasOnlyWhiteSpacesSinceLastLine >= 0) + if (isLineBlank) { - if (hasOnlyWhiteSpacesSinceLastLine == 1) - { - break; - } - hasOnlyWhiteSpacesSinceLastLine = -1; + break; } + + if (hasEscape) + { + hasEscape = false; + buffer.Append('\\'); + } + buffer.Append(c); + if (c == '\r' && text.PeekChar() == '\n') { buffer.Append('\n'); text.SkipChar(); } - continue; - } - if (c == '\0') - { - break; + isLineBlank = true; } - - if (c == closingQuote) + else if (hasEscape) { - if (hasEscape) + hasEscape = false; + + if (!c.IsAsciiPunctuation()) { - buffer.Append(closingQuote); - hasEscape = false; - continue; + buffer.Append('\\'); } + buffer.Append(c); + } + else if (c == closingQuote) + { // Skip last quote text.SkipChar(); - goto ReturnValid; + title = buffer.ToString(); + return true; } - - if (hasEscape && !c.IsAsciiPunctuation()) - { - buffer.Append('\\'); - } - - if (c == '\\') + else if (c == '\\') { hasEscape = true; - continue; + isLineBlank = false; } - - hasEscape = false; - - if (c.IsSpaceOrTab()) + else { - if (hasOnlyWhiteSpacesSinceLastLine < 0) + if (isLineBlank && !c.IsSpaceOrTab()) { - hasOnlyWhiteSpacesSinceLastLine = 1; + isLineBlank = false; } - } - else if (c != '\n' && c != '\r' && text.PeekChar() != '\n') - { - hasOnlyWhiteSpacesSinceLastLine = 0; - } - buffer.Append(c); + buffer.Append(c); + } } } buffer.Dispose(); title = null; return false; - - ReturnValid: - title = buffer.ToString(); - return true; } public static bool TryParseUrl(T text, [NotNullWhen(true)] out string? link) where T : ICharIterator @@ -760,12 +724,15 @@ public static class LinkHelper break; } - if (hasEscape && !c.IsAsciiPunctuation()) + if (hasEscape) { - buffer.Append('\\'); + hasEscape = false; + if (!c.IsAsciiPunctuation()) + { + buffer.Append('\\'); + } } - - if (c == '\\') + else if (c == '\\') { hasEscape = true; continue; @@ -776,8 +743,6 @@ public static class LinkHelper break; } - hasEscape = false; - buffer.Append(c); } while (c != '\0'); @@ -816,20 +781,21 @@ public static class LinkHelper if (!isAutoLink) { - if (hasEscape && !c.IsAsciiPunctuation()) + if (hasEscape) { - buffer.Append('\\'); + hasEscape = false; + if (!c.IsAsciiPunctuation()) + { + buffer.Append('\\'); + } } - // If we have an escape - if (c == '\\') + else if (c == '\\') { hasEscape = true; c = text.NextChar(); continue; } - - hasEscape = false; } if (IsEndOfUri(c, isAutoLink)) @@ -907,12 +873,15 @@ public static class LinkHelper break; } - if (hasEscape && !c.IsAsciiPunctuation()) + if (hasEscape) { - buffer.Append('\\'); + hasEscape = false; + if (!c.IsAsciiPunctuation()) + { + buffer.Append('\\'); + } } - - if (c == '\\') + else if (c == '\\') { hasEscape = true; continue; @@ -923,8 +892,6 @@ public static class LinkHelper break; } - hasEscape = false; - buffer.Append(c); } while (c != '\0'); @@ -963,20 +930,21 @@ public static class LinkHelper if (!isAutoLink) { - if (hasEscape && !c.IsAsciiPunctuation()) + if (hasEscape) { - buffer.Append('\\'); + hasEscape = false; + if (!c.IsAsciiPunctuation()) + { + buffer.Append('\\'); + } } - // If we have an escape - if (c == '\\') + else if (c == '\\') { hasEscape = true; c = text.NextChar(); continue; } - - hasEscape = false; } if (IsEndOfUri(c, isAutoLink)) @@ -1161,7 +1129,7 @@ public static class LinkHelper c = text.NextChar(); } - if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n') + if (c != '\0' && c != '\n' && (c != '\r' || text.PeekChar() != '\n')) { // If we were able to parse the url but the title doesn't end with space, // we are still returning a valid definition @@ -1301,7 +1269,7 @@ public static class LinkHelper c = text.NextChar(); } - if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n') + if (c != '\0' && c != '\n' && (c != '\r' || text.PeekChar() != '\n')) { // If we were able to parse the url but the title doesn't end with space, // we are still returning a valid definition diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 0caa1ef4..97431d5a 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -137,6 +137,9 @@ public class LinkInlineParser : InlineParser if (linkRef.CreateLinkInline != null) { link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild); + link.Span = new SourceSpan(parent.Span.Start, endPosition); + link.Line = parent.Line; + link.Column = parent.Column; } // Create a default link if the callback was not found @@ -145,8 +148,8 @@ public class LinkInlineParser : InlineParser // Inline Link var linkInline = new LinkInline() { - Url = HtmlHelper.Unescape(linkRef.Url), - Title = HtmlHelper.Unescape(linkRef.Title), + Url = HtmlHelper.Unescape(linkRef.Url, false), + Title = HtmlHelper.Unescape(linkRef.Title, false), Label = label, LabelSpan = labelSpan, UrlSpan = linkRef.UrlSpan, @@ -256,8 +259,8 @@ public class LinkInlineParser : InlineParser // Inline Link link = new LinkInline() { - Url = HtmlHelper.Unescape(url), - Title = HtmlHelper.Unescape(title), + Url = HtmlHelper.Unescape(url, false), + Title = HtmlHelper.Unescape(title, false), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), @@ -382,11 +385,11 @@ public class LinkInlineParser : InlineParser return new LinkInline() { TriviaBeforeUrl = wsBeforeLink, - Url = HtmlHelper.Unescape(url), + Url = HtmlHelper.Unescape(url, false), UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, - Title = HtmlHelper.Unescape(title), + Title = HtmlHelper.Unescape(title, false), UnescapedTitle = unescapedTitle, TitleEnclosingCharacter = titleEnclosingCharacter, TriviaAfterTitle = wsAfterTitle, From 90365bfeee0e9051992354aacfe35f4350a76c16 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:13:09 +0300 Subject: [PATCH 2/7] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 97431d5a..d7428147 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -148,8 +148,8 @@ public class LinkInlineParser : InlineParser // Inline Link var linkInline = new LinkInline() { - Url = HtmlHelper.Unescape(linkRef.Url, false), - Title = HtmlHelper.Unescape(linkRef.Title, false), + Url = HtmlHelper.Unescape(linkRef.Url, removeBackSlash: false), + Title = HtmlHelper.Unescape(linkRef.Title, removeBackSlash: false), Label = label, LabelSpan = labelSpan, UrlSpan = linkRef.UrlSpan, From ad0770a59406f432d3de402a93289c6d983d5428 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:13:22 +0300 Subject: [PATCH 3/7] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index d7428147..cade0e3f 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -259,8 +259,8 @@ public class LinkInlineParser : InlineParser // Inline Link link = new LinkInline() { - Url = HtmlHelper.Unescape(url, false), - Title = HtmlHelper.Unescape(title, false), + Url = HtmlHelper.Unescape(url, removeBackSlash: false), + Title = HtmlHelper.Unescape(title, removeBackSlash: false), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), From 54783b8f65db62a56f6d7b8982566d8922117cd6 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:13:56 +0300 Subject: [PATCH 4/7] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index cade0e3f..85b6c9bf 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -385,7 +385,7 @@ public class LinkInlineParser : InlineParser return new LinkInline() { TriviaBeforeUrl = wsBeforeLink, - Url = HtmlHelper.Unescape(url, false), + Url = HtmlHelper.Unescape(url, removeBackSlash: false), UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, From 7f604bef30746ee35d74d2e65498dcaf2cac384f Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:14:07 +0300 Subject: [PATCH 5/7] Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs Co-authored-by: Miha Zupan --- src/Markdig/Parsers/Inlines/LinkInlineParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs index 85b6c9bf..eee94ec7 100644 --- a/src/Markdig/Parsers/Inlines/LinkInlineParser.cs +++ b/src/Markdig/Parsers/Inlines/LinkInlineParser.cs @@ -389,7 +389,7 @@ public class LinkInlineParser : InlineParser UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, - Title = HtmlHelper.Unescape(title, false), + Title = HtmlHelper.Unescape(title, removeBackSlash: false), UnescapedTitle = unescapedTitle, TitleEnclosingCharacter = titleEnclosingCharacter, TriviaAfterTitle = wsAfterTitle, From 90bc15c016a84c3758374337516c83e5733435b9 Mon Sep 17 00:00:00 2001 From: snnz Date: Sat, 21 Dec 2024 06:14:16 +0300 Subject: [PATCH 6/7] Update src/Markdig.Tests/TestPlayParser.cs Co-authored-by: Miha Zupan --- src/Markdig.Tests/TestPlayParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markdig.Tests/TestPlayParser.cs b/src/Markdig.Tests/TestPlayParser.cs index 54af3927..18942411 100644 --- a/src/Markdig.Tests/TestPlayParser.cs +++ b/src/Markdig.Tests/TestPlayParser.cs @@ -50,7 +50,7 @@ public class TestPlayParser public void TestLinkWithMultipleBackslashesInTitle() { var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')"); - var link = doc.Descendants().SelectMany(x => x.Inline.Descendants()).FirstOrDefault(); + var link = doc.Descendants().FirstOrDefault(); Assert.AreEqual(@"\\127.0.0.1", link?.Title); } From ab8e85b06e87e87c30a7530d0c37c0561921bd95 Mon Sep 17 00:00:00 2001 From: Sergey Nozhenko Date: Sat, 21 Dec 2024 06:56:23 +0300 Subject: [PATCH 7/7] Remove additional condition, since a carriage return constitute a line ending regardless of whether it is followed by a line feed or not. --- src/Markdig/Helpers/LinkHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markdig/Helpers/LinkHelper.cs b/src/Markdig/Helpers/LinkHelper.cs index 0356a275..ac47b294 100644 --- a/src/Markdig/Helpers/LinkHelper.cs +++ b/src/Markdig/Helpers/LinkHelper.cs @@ -1129,7 +1129,7 @@ public static class LinkHelper c = text.NextChar(); } - if (c != '\0' && c != '\n' && (c != '\r' || text.PeekChar() != '\n')) + if (c != '\0' && c != '\n' && c != '\r') { // If we were able to parse the url but the title doesn't end with space, // we are still returning a valid definition @@ -1269,7 +1269,7 @@ public static class LinkHelper c = text.NextChar(); } - if (c != '\0' && c != '\n' && (c != '\r' || text.PeekChar() != '\n')) + if (c != '\0' && c != '\n' && c != '\r') { // If we were able to parse the url but the title doesn't end with space, // we are still returning a valid definition