#include "markdown_renderer.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <macrodown.h>
#include <mw/url.hpp>
#include <mw/utils.hpp>
#include <nodes.h>
namespace
{
std::optional<std::string> literalText(const macrodown::Node& node)
{
if(const auto* text = std::get_if<macrodown::Text>(&node.data))
{
return text->content;
}
const auto* group = std::get_if<macrodown::Group>(&node.data);
if(group == nullptr)
{
return std::nullopt;
}
std::string result;
for(const std::unique_ptr<macrodown::Node>& child : group->children)
{
auto child_text = literalText(*child);
if(!child_text)
{
return std::nullopt;
}
result += *child_text;
}
return result;
}
mw::E<void> validateUrls(const macrodown::Node& node)
{
if(const auto* macro = std::get_if<macrodown::Macro>(&node.data))
{
if(macro->name == "link" || macro->name == "img")
{
if(macro->arguments.empty())
{
return std::unexpected(mw::httpError(
422, "Markdown links require an absolute URL"));
}
auto text = literalText(*macro->arguments.front());
auto url = text ? mw::URL::fromStr(*text)
: mw::E<mw::URL>(std::unexpected(
mw::runtimeError("Computed URL")));
if(!text || !url ||
(url->scheme() != "http" && url->scheme() != "https") ||
url->host().empty())
{
return std::unexpected(mw::httpError(
422,
"Markdown links and images require literal HTTP or "
"HTTPS URLs"));
}
}
for(const std::unique_ptr<macrodown::Node>& argument :
macro->arguments)
{
auto valid = validateUrls(*argument);
if(!valid)
{
return valid;
}
}
}
else if(const auto* group = std::get_if<macrodown::Group>(&node.data))
{
for(const std::unique_ptr<macrodown::Node>& child : group->children)
{
auto valid = validateUrls(*child);
if(!valid)
{
return valid;
}
}
}
return {};
}
void escapeText(macrodown::Node& node)
{
if(auto* text = std::get_if<macrodown::Text>(&node.data))
{
text->content = mw::escapeHTML(text->content);
}
else if(auto* macro = std::get_if<macrodown::Macro>(&node.data))
{
for(const std::unique_ptr<macrodown::Node>& argument :
macro->arguments)
{
escapeText(*argument);
}
}
else if(auto* group = std::get_if<macrodown::Group>(&node.data))
{
for(const std::unique_ptr<macrodown::Node>& child : group->children)
{
escapeText(*child);
}
}
}
} // namespace
RenderedHtml::RenderedHtml(std::string value)
: value_(std::move(value))
{}
const std::string& RenderedHtml::value() const
{
return value_;
}
mw::E<RenderedHtml> MarkdownRenderer::render(
const std::string& source) const
{
try
{
macrodown::MacroDown renderer;
std::unique_ptr<macrodown::Node> tree = renderer.parse(source);
auto valid = validateUrls(*tree);
if(!valid)
{
return std::unexpected(std::move(valid.error()));
}
escapeText(*tree);
return RenderedHtml(renderer.render(*tree));
}
catch(const std::exception& error)
{
return std::unexpected(mw::httpError(
422, "Markdown could not be rendered: " +
std::string(error.what())));
}
}