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/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 diff --git a/include/maddy/codeblockparser.h b/include/maddy/codeblockparser.h index f0fbb29..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,6 +120,8 @@ class CodeBlockParser : public BlockParser return; } + 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 c393e3f..5ea0f7a 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" // ----------------------------------------------------------------------------- @@ -39,9 +40,23 @@ class InlineCodeParser : public LineParser void Parse(std::string& line) override { static std::regex re("`([^`]*)`"); - static std::string replacement = "$1"; + std::smatch match; + std::string result; - line = std::regex_replace(line, re, replacement); + auto searchStart = line.cbegin(); + + while (std::regex_search(searchStart, line.cend(), match, re)) + { + result.append(match.prefix()); + + result += "" + common::escapeHTML(match[1].str()) + ""; + + searchStart = match.suffix().first; + } + + result.append(searchStart, line.cend()); + + line = std::move(result); } }; // 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 // ----------------------------------------------------------------------------- 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); +} 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 "