diff --git a/.gitmodules b/.gitmodules index a3c7880..c5b36cf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "submodules/qssb.h"] path = submodules/qssb.h url = https://gitea.quitesimple.org/crtxcr/qssb.h.git +[submodule "submodules/qsmaddy"] + path = submodules/qsmaddy + url = https://gitea.quitesimple.org/crtxcr/qsmaddy diff --git a/Makefile b/Makefile index 8828037..a139184 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CXXFLAGS=-std=c++17 -O0 -g -no-pie -pipe -MMD -Wall -Wextra RELEASE_CXXFLAGS=-std=c++17 -O3 -pipe -MMD -Wall -Wextra LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs -lseccomp -INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h +INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h -I submodules/qsmaddy/include/ CXX=g++ diff --git a/config.cpp b/config.cpp index 6bf8c3e..7d74f89 100644 --- a/config.cpp +++ b/config.cpp @@ -71,6 +71,7 @@ Config::Config(const std::map &map) this->configmap = map; this->wikipath = optional("wikipath", "/"); + this->parser = optional("parser", "markdown"); this->handlersConfig.anon_username = optional("anon_username", "anonymouse"); this->handlersConfig.wikiname = required("wikiname"); this->logfile = required("logfile"); diff --git a/config.h b/config.h index 6780d83..c682340 100644 --- a/config.h +++ b/config.h @@ -86,6 +86,7 @@ class Config std::string templateprefix; std::string logfile; std::string connectionstring; + std::string parser; int session_max_lifetime; int threadscount; diff --git a/handlers/handler.h b/handlers/handler.h index 8569c0e..7d22f4b 100644 --- a/handlers/handler.h +++ b/handlers/handler.h @@ -9,10 +9,13 @@ #include "../database/queryoption.h" #include "../logger.h" #include "../cache/icache.h" +#include "../iparser.h" + class Handler { protected: ICache *cache; + IParser *parser; Template *templ; Database *database; Session *userSession; @@ -25,7 +28,7 @@ class Handler public: Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider, - ICache &cache) + ICache &cache, IParser &parser) { this->handlersConfig = &handlersConfig; this->templ = &templ; @@ -33,6 +36,7 @@ class Handler this->userSession = &userSession; this->urlProvider = &provider; this->cache = &cache; + this->parser = &parser; } virtual Response handle(const Request &r); diff --git a/handlers/handlerfactory.h b/handlers/handlerfactory.h index 7120b89..e08649f 100644 --- a/handlers/handlerfactory.h +++ b/handlers/handlerfactory.h @@ -10,15 +10,16 @@ class HandlerFactory Database &db; UrlProvider &urlProvider; ICache &cache; + IParser &parser; template inline std::unique_ptr produce(Session &userSession) { - return std::make_unique(handlerConfig, templ, db, userSession, urlProvider, cache); + return std::make_unique(handlerConfig, templ, db, userSession, urlProvider, cache, parser); } public: - HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache) - : handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache) + HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache, IParser &parser) + : handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache), parser(parser) { } std::unique_ptr createHandler(const std::string &action, Session &userSession); diff --git a/handlers/handlerpageedit.cpp b/handlers/handlerpageedit.cpp index 492e36f..3704e66 100644 --- a/handlers/handlerpageedit.cpp +++ b/handlers/handlerpageedit.cpp @@ -21,8 +21,8 @@ SOFTWARE. #include "handlerpageedit.h" #include "../database/exceptions.h" #include "../request.h" +#include "../parserlegacy.h" -#include "../parser.h" bool HandlerPageEdit::canAccess(std::string page) { return this->userSession->user.permissions.canEdit(); @@ -59,7 +59,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, // TODO: must check, whether categories differ, and perhaps don't allow every user // to set categories - Parser parser; + ParserLegacy parser; std::vector cats = parser.extractCategories(newContent); try { @@ -107,7 +107,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, if(r.post("do") == "preview") { std::string newContent = r.post("content"); - Parser parser; + ParserLegacy parser; TemplatePage templatePage = this->templ->getPage("page_creation_preview"); templatePage.setVar("actionurl", urlProvider->editPage(pagename)); templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent)); diff --git a/handlers/handlerpageview.cpp b/handlers/handlerpageview.cpp index 1ad121a..fc97fdc 100644 --- a/handlers/handlerpageview.cpp +++ b/handlers/handlerpageview.cpp @@ -21,7 +21,7 @@ SOFTWARE. #include "handlerpageview.h" #include "../database/exceptions.h" #include "../logger.h" -#include "../parser.h" +#include "../parserlegacy.h" #include "../htmllink.h" bool HandlerPageView::canAccess(std::string page) @@ -130,7 +130,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, TemplatePage &page = this->templ->getPage(templatepartname); - Parser parser; + Response result; result.setStatus(200); std::string indexcontent; @@ -138,8 +138,8 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, if(revisionid > 0) { - indexcontent = createIndexContent(parser, revision->content); - parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content); + indexcontent = createIndexContent(*parser, revision->content); + parsedcontent = parser->parse(pageDao, *this->urlProvider, revision->content); } else { @@ -153,7 +153,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, } else { - indexcontent = createIndexContent(parser, revision->content); + indexcontent = createIndexContent(*parser, revision->content); this->cache->put(cachekeyindexcontent, indexcontent); } if(cachedparsedcontent) @@ -162,7 +162,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, } else { - parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content); + parsedcontent = parser->parse(pageDao, *this->urlProvider, revision->content); this->cache->put(cachekeyparsedcontent, parsedcontent); } } diff --git a/iparser.h b/iparser.h index 3835e86..973d516 100644 --- a/iparser.h +++ b/iparser.h @@ -5,6 +5,8 @@ #include "headline.h" #include "database/pagedao.h" #include "urlprovider.h" + + class IParser { public: @@ -16,4 +18,6 @@ class IParser virtual ~IParser(){}; }; + + #endif // PARSER_H diff --git a/parser.cpp b/parserlegacy.cpp similarity index 83% rename from parser.cpp rename to parserlegacy.cpp index 8ee460c..0c7c117 100644 --- a/parser.cpp +++ b/parserlegacy.cpp @@ -24,29 +24,22 @@ SOFTWARE. #include #include #include -#include "parser.h" +#include "parserlegacy.h" #include "utils.h" #include "htmllink.h" -std::vector Parser::extractHeadlines(std::string content) const +std::vector ParserLegacy::extractHeadlines(std::string content) const { std::vector result; - std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])"; - std::regex headerfinder(reg); - auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder); - auto end = std::sregex_iterator(); - - for(auto it = begin; it != end; it++) - { - auto smatch = *it; + utils::regex_callback_extractor(std::regex(R"(\[h(1|2|3)\](.*?)\[/h\1\])"), content, [&](std::smatch &smatch) { Headline h; h.level = utils::toUInt(smatch.str(1)); h.title = smatch.str(2); result.push_back(h); - } + }); return result; } -std::vector Parser::extractCategories(std::string content) const +std::vector ParserLegacy::extractCategories(std::string content) const { std::vector result; std::string reg = R"(\[category\](.*?)\[/category\])"; @@ -62,7 +55,7 @@ std::vector Parser::extractCategories(std::string content) const return result; } -std::string Parser::extractCommand(std::string cmdname, std::string content) const +std::string ParserLegacy::extractCommand(std::string cmdname, std::string content) const { std::string cmd = "[cmd:" + cmdname + "]"; std::string cmdend = "[/cmd:" + cmdname + "]"; @@ -81,7 +74,7 @@ std::string Parser::extractCommand(std::string cmdname, std::string content) con } return ""; } -std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const +std::string ParserLegacy::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const { std::string linktag = match.str(1); std::string inside = match.str(2); @@ -116,7 +109,7 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider return htmllink.render(); } -std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const +std::string ParserLegacy::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const { std::string result; // we don't care about commands, but we nevertheless replace them with empty strings diff --git a/parser.h b/parserlegacy.h similarity index 91% rename from parser.h rename to parserlegacy.h index f34e12c..b59fd79 100644 --- a/parser.h +++ b/parserlegacy.h @@ -2,7 +2,7 @@ #define PARSER_H #include "iparser.h" -class Parser : public IParser +class ParserLegacy : public IParser { private: std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; @@ -13,7 +13,7 @@ class Parser : public IParser std::vector extractCategories(std::string content) const override; std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override; using IParser::IParser; - ~Parser(){}; + ~ParserLegacy(){}; }; #endif // PARSER_H diff --git a/parsermarkdown.cpp b/parsermarkdown.cpp new file mode 100644 index 0000000..760167a --- /dev/null +++ b/parsermarkdown.cpp @@ -0,0 +1,71 @@ +#include "parsermarkdown.h" +#include "logger.h" +#include "htmllink.h" + +std::string ParserMarkdown::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const +{ + std::string inner = match.str(1); + std::string link = match.str(2); + HtmlLink htmllink; + htmllink.href = link; + htmllink.innervalue = inner; + + if(link.find("http://") == 0 || link.find("https://") == 0) + { + return htmllink.render(); + } + + if(pageDao.exists(link)) + { + htmllink.cssclass = "exists"; + } + else + { + htmllink.cssclass = "notexists"; + } + + htmllink.href = urlProvider.page(htmllink.href); + + return htmllink.render(); +} + +ParserMarkdown::ParserMarkdown() +{ +} + +std::vector ParserMarkdown::extractHeadlines(std::string content) const +{ + std::vector result; + utils::regex_callback_extractor(std::regex(R"((#{1,6}) (.*))"), content, [&](std::smatch &smatch) { + Headline h; + h.level = smatch.str(1).length(); + h.title = smatch.str(2); + result.push_back(h); + }); + return result; +} + +std::string ParserMarkdown::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const +{ + std::shared_ptr config = std::make_shared(); + auto maddy = std::make_shared(config); + + auto linkParser = std::make_shared(); + linkParser->setCallback([&](std::smatch &match) { return processLink(pagedao, provider, match); }); + maddy->setLinkParser(linkParser); + // TODO: hack because the parser breaks if there is an \r + content = utils::strreplace(content, "\r", ""); + std::stringstream s{content}; + std::string result = maddy->Parse(s); + return result; +} + +std::string ParserMarkdown::extractCommand(std::string cmdname, std::string content) const +{ + return ""; +} + +std::vector ParserMarkdown::extractCategories(std::string content) const +{ + return { }; +} diff --git a/parsermarkdown.h b/parsermarkdown.h new file mode 100644 index 0000000..3b1a2a5 --- /dev/null +++ b/parsermarkdown.h @@ -0,0 +1,21 @@ +#ifndef PARSER_MARKDOWN_H +#define PARSER_MARKDOWN_H + +#include "iparser.h" +#include "maddy/parser.h" + +class ParserMarkdown : public IParser +{ +private: + std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; + +public: + ParserMarkdown(); + std::vector extractHeadlines(std::string content) const; + std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const; + + std::string extractCommand(std::string cmdname, std::string content) const; + std::vector extractCategories(std::string content) const; +}; + +#endif // PARSER_MARKDOWN_H diff --git a/qswiki.cpp b/qswiki.cpp index 46143cb..395b52c 100644 --- a/qswiki.cpp +++ b/qswiki.cpp @@ -37,6 +37,10 @@ SOFTWARE. #include "requestworker.h" #include "cache/fscache.h" #include "sandbox/sandboxfactory.h" +#include "iparser.h" +#include "parserlegacy.h" +#include "parsermarkdown.h" + void sigterm_handler(int arg) { // TODO: proper shutdown. @@ -63,6 +67,17 @@ std::unique_ptr createCache(const ConfigVariableResolver &resolver) return std::make_unique(path); } + +std::unique_ptr createParser(const ConfigVariableResolver &resolver) +{ + std::string parser = resolver.getConfig("parser"); + if(parser == "legacy") + { + return std::make_unique(); + } + return std::make_unique(); +} + int main(int argc, char **argv) { if(geteuid() == 0) @@ -135,7 +150,10 @@ int main(int argc, char **argv) auto cache = createCache(config.configVarResolver); cache->clear(); - HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get()}; + auto parser = createParser(config.configVarResolver); + + + HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get(), *parser.get()}; RequestWorker requestWorker{handlerFactory, database->createSessionDao(), siteTemplate}; auto interface = createGateway(config); diff --git a/submodules/qsmaddy b/submodules/qsmaddy new file mode 160000 index 0000000..167ce39 --- /dev/null +++ b/submodules/qsmaddy @@ -0,0 +1 @@ +Subproject commit 167ce3943d6e27ddc862e6ce52cf5a79c5a794de diff --git a/utils.cpp b/utils.cpp index 3df6306..1510217 100644 --- a/utils.cpp +++ b/utils.cpp @@ -175,3 +175,14 @@ std::string utils::toISODate(time_t t) } return std::string{result}; } + +void utils::regex_callback_extractor(std::regex regex, const std::string &input, std::function callback) +{ + auto begin = std::sregex_iterator(input.begin(), input.end(), regex); + auto end = std::sregex_iterator(); + for(auto it = begin; it != end; it++) + { + std::smatch smatch = *it; + callback(smatch); + } +} diff --git a/utils.h b/utils.h index d19be1b..de5a900 100644 --- a/utils.h +++ b/utils.h @@ -60,6 +60,7 @@ template std::vector getAll(std::multimap map, T key std::string regex_callback_replacer(std::regex regex, const std::string &input, std::function callback); +void regex_callback_extractor(std::regex regex, const std::string &input, std::function callback); std::string readCompleteFile(std::string_view filepath); inline std::string nz(const char *s)