From 91dc5b876039fabd58f57e91dfc2545b44510efe Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:03:26 +0400 Subject: [PATCH 1/7] HTML escape in code blocks --- include/maddy/codeblockparser.h | 2 ++ include/maddy/inlinecodeparser.h | 10 ++++++++-- include/maddy/parser.h | 29 +++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/maddy/codeblockparser.h b/include/maddy/codeblockparser.h index f0fbb29..162f762 100644 --- a/include/maddy/codeblockparser.h +++ b/include/maddy/codeblockparser.h @@ -119,6 +119,8 @@ class CodeBlockParser : public BlockParser return; } + line = escapeHTML(line); + line += "\n"; } diff --git a/include/maddy/inlinecodeparser.h b/include/maddy/inlinecodeparser.h index c393e3f..0b60f5c 100644 --- a/include/maddy/inlinecodeparser.h +++ b/include/maddy/inlinecodeparser.h @@ -39,9 +39,15 @@ class InlineCodeParser : public LineParser void Parse(std::string& line) override { static std::regex re("`([^`]*)`"); - static std::string replacement = "$1"; + std::smatch match; - line = std::regex_replace(line, re, replacement); + if (std::regex_search(line, match, re)) + { + std::string replacement = + "" + escapeHTML(match[1].str()) + ""; + + line = match.prefix().str() + replacement + match.suffix().str(); + } } }; // class InlineCodeParser diff --git a/include/maddy/parser.h b/include/maddy/parser.h index 660752f..675893f 100644 --- a/include/maddy/parser.h +++ b/include/maddy/parser.h @@ -394,6 +394,35 @@ class Parser } ); } + + static std::string escapeHTML(const std::string& input) + { + std::string result; + for (char c : input) + { + switch (c) + { + case '&': + result += "&"; + break; + case '<': + result += "<"; + break; + case '>': + result += ">"; + break; + case '"': + result += """; + break; + case '\'': + result += "'"; + break; + default: + result += c; + } + } + return result; + } }; // class Parser // ----------------------------------------------------------------------------- From 28e04206e851b8edb61dab3fef43338c3d0dfc78 Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:39:22 +0400 Subject: [PATCH 2/7] Moved escapeHTML to common --- include/maddy/codeblockparser.h | 3 ++- include/maddy/common.h | 42 ++++++++++++++++++++++++++++++++ include/maddy/inlinecodeparser.h | 3 ++- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 include/maddy/common.h diff --git a/include/maddy/codeblockparser.h b/include/maddy/codeblockparser.h index 162f762..62405fd 100644 --- a/include/maddy/codeblockparser.h +++ b/include/maddy/codeblockparser.h @@ -11,6 +11,7 @@ #include #include "maddy/blockparser.h" +#include "maddy/common.h" // ----------------------------------------------------------------------------- @@ -119,7 +120,7 @@ class CodeBlockParser : public BlockParser return; } - line = escapeHTML(line); + line = common::escapeHTML(line); line += "\n"; } diff --git a/include/maddy/common.h b/include/maddy/common.h new file mode 100644 index 0000000..129c583 --- /dev/null +++ b/include/maddy/common.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace maddy { + +namespace common { + +inline std::string escapeHTML(const std::string& input) +{ + std::string result; + + for (char c : input) + { + switch (c) + { + case '&': + result += "&"; + break; + case '<': + result += "<"; + break; + case '>': + result += ">"; + break; + case '"': + result += """; + break; + case '\'': + result += "'"; + break; + default: + result += c; + } + } + + return result; +} + +} // namespace common + +} // namespace maddy diff --git a/include/maddy/inlinecodeparser.h b/include/maddy/inlinecodeparser.h index 0b60f5c..92a22d2 100644 --- a/include/maddy/inlinecodeparser.h +++ b/include/maddy/inlinecodeparser.h @@ -9,6 +9,7 @@ #include #include +#include "maddy/common.h" #include "maddy/lineparser.h" // ----------------------------------------------------------------------------- @@ -44,7 +45,7 @@ class InlineCodeParser : public LineParser if (std::regex_search(line, match, re)) { std::string replacement = - "" + escapeHTML(match[1].str()) + ""; + "" + common::escapeHTML(match[1].str()) + ""; line = match.prefix().str() + replacement + match.suffix().str(); } From 1c3c94d71423691405ac513c0067a8e8ed9fc5c6 Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:50:31 +0400 Subject: [PATCH 3/7] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a16cc40..d4487bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ maddy uses [semver versioning](https://semver.org/). ## Upcoming -* ... +* ![**FIXED**](https://img.shields.io/badge/-FIXED-%23090) HTML escaping in code blocks and inline code ## version 1.6.0 2025-07-26 From 3e19c78ee8dc8b3a2f1edda4016c1bad55431763 Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:12:13 +0400 Subject: [PATCH 4/7] Added HTML escape tests --- AUTHORS | 1 + tests/maddy/test_maddy_codeblockparser.cpp | 22 +++++++++++++++++++++ tests/maddy/test_maddy_inlinecodeparser.cpp | 14 +++++++++++++ 3 files changed, 37 insertions(+) diff --git a/AUTHORS b/AUTHORS index 698a0d0..23d3aa7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,3 +12,4 @@ Evan Klitzke (evan@eklitzke.org) Albert Schwarzkopf (dev-maddy@quitesimple.org) Ivans Saponenko (ivans.saponenko+maddy@gmail.com) Lucian Smith (lpsmith@uw.edu) +Vardan Petrosyan (github.com/Vardan2009) diff --git a/tests/maddy/test_maddy_codeblockparser.cpp b/tests/maddy/test_maddy_codeblockparser.cpp index 5306658..dd04578 100644 --- a/tests/maddy/test_maddy_codeblockparser.cpp +++ b/tests/maddy/test_maddy_codeblockparser.cpp @@ -74,3 +74,25 @@ TEST_F(MADDY_CODEBLOCKPARSER, ItShouldUseAnythingBehindFirstBackticksAsClass) ASSERT_EQ(expected, outputString); } + +TEST_F(MADDY_CODEBLOCKPARSER, ItProperlyEscapesHTML) +{ + std::vector markdown = { + "```html", "

Hello, World!

", "```" + }; + + std::string expected = + "
\n<h1>Hello, "
+    "World!</h1>\n
"; + + for (std::string md : markdown) + { + cbParser->AddLine(md); + } + ASSERT_TRUE(cbParser->IsFinished()); + + std::stringstream& output(cbParser->GetResult()); + const std::string& outputString = output.str(); + + ASSERT_EQ(expected, outputString); +} diff --git a/tests/maddy/test_maddy_inlinecodeparser.cpp b/tests/maddy/test_maddy_inlinecodeparser.cpp index 14bfa56..cd2a058 100644 --- a/tests/maddy/test_maddy_inlinecodeparser.cpp +++ b/tests/maddy/test_maddy_inlinecodeparser.cpp @@ -21,3 +21,17 @@ TEST(MADDY_INLINECODEPARSER, ItReplacesMarkdownWithCodeHTML) ASSERT_EQ(expected, text); } + +TEST(MADDY_INLINECODEPARSER, ItProperlyEscapesHTML) +{ + std::string text = + "some text `

Test

` text testing `it` out"; + std::string expected = + "some text <h1>Test</h1> text testing " + "<span>it</span> out"; + auto emphasizedParser = std::make_shared(); + + emphasizedParser->Parse(text); + + ASSERT_EQ(expected, text); +} From a015e06c25bfe9201e38fc9de6e8656ca769fe91 Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:21:24 +0400 Subject: [PATCH 5/7] InlineCodeParser replaces all matches --- include/maddy/inlinecodeparser.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/maddy/inlinecodeparser.h b/include/maddy/inlinecodeparser.h index 92a22d2..5ea0f7a 100644 --- a/include/maddy/inlinecodeparser.h +++ b/include/maddy/inlinecodeparser.h @@ -41,14 +41,22 @@ class InlineCodeParser : public LineParser { static std::regex re("`([^`]*)`"); std::smatch match; + std::string result; - if (std::regex_search(line, match, re)) + auto searchStart = line.cbegin(); + + while (std::regex_search(searchStart, line.cend(), match, re)) { - std::string replacement = - "" + common::escapeHTML(match[1].str()) + ""; + result.append(match.prefix()); + + result += "" + common::escapeHTML(match[1].str()) + ""; - line = match.prefix().str() + replacement + match.suffix().str(); + searchStart = match.suffix().first; } + + result.append(searchStart, line.cend()); + + line = std::move(result); } }; // class InlineCodeParser From 10f52fca1621c4e23639f2e6ab24dd154982bc96 Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:25:42 +0400 Subject: [PATCH 6/7] escapeHTML only replaces angle brackets --- include/maddy/common.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/include/maddy/common.h b/include/maddy/common.h index 129c583..1b10c3d 100644 --- a/include/maddy/common.h +++ b/include/maddy/common.h @@ -14,21 +14,12 @@ inline std::string escapeHTML(const std::string& input) { switch (c) { - case '&': - result += "&"; - break; case '<': result += "<"; break; case '>': result += ">"; break; - case '"': - result += """; - break; - case '\'': - result += "'"; - break; default: result += c; } From b2e04d95ac333b16868b05f6e760e80da6c6d052 Mon Sep 17 00:00:00 2001 From: Vardan2009 <70532109+Vardan2009@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:39:37 +0400 Subject: [PATCH 7/7] More escape cases in escapeHTML --- include/maddy/common.h | 9 +++++++++ tests/maddy/test_maddy_parser.h | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/maddy/common.h b/include/maddy/common.h index 1b10c3d..129c583 100644 --- a/include/maddy/common.h +++ b/include/maddy/common.h @@ -14,12 +14,21 @@ inline std::string escapeHTML(const std::string& input) { switch (c) { + case '&': + result += "&"; + break; case '<': result += "<"; break; case '>': result += ">"; break; + case '"': + result += """; + break; + case '\'': + result += "'"; + break; default: result += c; } diff --git a/tests/maddy/test_maddy_parser.h b/tests/maddy/test_maddy_parser.h index 576f30a..387703c 100644 --- a/tests/maddy/test_maddy_parser.h +++ b/tests/maddy/test_maddy_parser.h @@ -76,7 +76,8 @@ const std::string testHtml = "hierarchy
  1. and an " "ordered
  2. list
  3. directly
  • inside
  • \nvar c = "
    -  "'blub';\n

    A Quote

    With some text " + "'blub';\n

    A Quote

    With some " + "text " "blocks inside

    • even a list
    • should be
    • possible " "

    And well inline code should also " "work.

    Another Headline

    And not to forget hierarchy

    1. and an " "_ordered_
    2. list
    3. directly
  • inside
  • \nvar c = 'blub';\n

    A Quote " + "li>

    \nvar c = "
    +  "'blub';\n

    A Quote " "

    With some text blocks inside

    And well " "inline code should also work.

    Another "