Vergelijk commits

7 Commits

Auteur SHA1 Bericht Datum
8d685dc581 Makefile: Remove -lseccomp as we don't need it anymore 2021-09-29 18:33:45 +02:00
ed43f5f700 submodules: update cpp-httplib 2021-09-29 18:28:18 +02:00
10f00aeb45 main: Pass absolute path of config file
As sandboxing code chroots and chdirs away,
2021-09-23 17:13:08 +02:00
67eb8b6428 sandbox: adjust to latest qssb.h 2021-09-23 17:13:08 +02:00
f26fd19fb4 submodules: sync with latest upstream 2021-09-23 17:13:08 +02:00
204a72da1f setup: Fix broken FTS DELETE op
Thie previous DELETE statement lead to strange
behaviours. It was pure luck this did not blow up
before all these years. It appears it may leave the index
in an undefined state, and the database recently started
to display strange behaviour in connection with newer sqlite
version.

Now, just remove the previous revision from the FTS index,
as for now, search only cares about the most recent revisions.

Also, remove redundant UPDATE trigger on revision table
We never update revisions, thus such trigger is simply
redundant.

Relevant: https://gitlab.gnome.org/GNOME/tracker/-/merge_requests/353
2021-09-23 17:13:08 +02:00
88816a4015 utils: html_xss(): Add ' and &
They REALLY should have been there from the beginning...
2021-06-15 18:37:52 +02:00
21 gewijzigde bestanden met toevoegingen van 115 en 229 verwijderingen

3
.gitmodules vendored
Bestand weergeven

@ -7,6 +7,3 @@
[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

Bestand weergeven

@ -2,8 +2,8 @@
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 -I submodules/qsmaddy/include/
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h
CXX=g++

Bestand weergeven

@ -71,7 +71,6 @@ Config::Config(const std::map<std::string, std::string> &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");

Bestand weergeven

@ -86,7 +86,6 @@ class Config
std::string templateprefix;
std::string logfile;
std::string connectionstring;
std::string parser;
int session_max_lifetime;
int threadscount;

Bestand weergeven

@ -9,13 +9,10 @@
#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;
@ -28,7 +25,7 @@ class Handler
public:
Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider,
ICache &cache, IParser &parser)
ICache &cache)
{
this->handlersConfig = &handlersConfig;
this->templ = &templ;
@ -36,7 +33,6 @@ class Handler
this->userSession = &userSession;
this->urlProvider = &provider;
this->cache = &cache;
this->parser = &parser;
}
virtual Response handle(const Request &r);

Bestand weergeven

@ -10,16 +10,15 @@ class HandlerFactory
Database &db;
UrlProvider &urlProvider;
ICache &cache;
IParser &parser;
template <class T> inline std::unique_ptr<T> produce(Session &userSession)
{
return std::make_unique<T>(handlerConfig, templ, db, userSession, urlProvider, cache, parser);
return std::make_unique<T>(handlerConfig, templ, db, userSession, urlProvider, cache);
}
public:
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)
HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache)
: handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache)
{
}
std::unique_ptr<Handler> createHandler(const std::string &action, Session &userSession);

Bestand weergeven

@ -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
ParserLegacy parser;
Parser parser;
std::vector<std::string> 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");
ParserLegacy parser;
Parser 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));

Bestand weergeven

@ -21,7 +21,7 @@ SOFTWARE.
#include "handlerpageview.h"
#include "../database/exceptions.h"
#include "../logger.h"
#include "../parserlegacy.h"
#include "../parser.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);
}
}

Bestand weergeven

@ -5,8 +5,6 @@
#include "headline.h"
#include "database/pagedao.h"
#include "urlprovider.h"
class IParser
{
public:
@ -18,6 +16,4 @@ class IParser
virtual ~IParser(){};
};
#endif // PARSER_H

Bestand weergeven

@ -24,22 +24,29 @@ SOFTWARE.
#include <vector>
#include <algorithm>
#include <iterator>
#include "parserlegacy.h"
#include "parser.h"
#include "utils.h"
#include "htmllink.h"
std::vector<Headline> ParserLegacy::extractHeadlines(std::string content) const
std::vector<Headline> Parser::extractHeadlines(std::string content) const
{
std::vector<Headline> result;
utils::regex_callback_extractor(std::regex(R"(\[h(1|2|3)\](.*?)\[/h\1\])"), content, [&](std::smatch &smatch) {
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;
Headline h;
h.level = utils::toUInt(smatch.str(1));
h.title = smatch.str(2);
result.push_back(h);
});
}
return result;
}
std::vector<std::string> ParserLegacy::extractCategories(std::string content) const
std::vector<std::string> Parser::extractCategories(std::string content) const
{
std::vector<std::string> result;
std::string reg = R"(\[category\](.*?)\[/category\])";
@ -55,7 +62,7 @@ std::vector<std::string> ParserLegacy::extractCategories(std::string content) co
return result;
}
std::string ParserLegacy::extractCommand(std::string cmdname, std::string content) const
std::string Parser::extractCommand(std::string cmdname, std::string content) const
{
std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]";
@ -74,7 +81,7 @@ std::string ParserLegacy::extractCommand(std::string cmdname, std::string conten
}
return "";
}
std::string ParserLegacy::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{
std::string linktag = match.str(1);
std::string inside = match.str(2);
@ -109,7 +116,7 @@ std::string ParserLegacy::processLink(const PageDao &pageDao, UrlProvider &urlPr
return htmllink.render();
}
std::string ParserLegacy::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const
std::string Parser::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

Bestand weergeven

@ -2,7 +2,7 @@
#define PARSER_H
#include "iparser.h"
class ParserLegacy : public IParser
class Parser : public IParser
{
private:
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
@ -13,7 +13,7 @@ class ParserLegacy : public IParser
std::vector<std::string> extractCategories(std::string content) const override;
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override;
using IParser::IParser;
~ParserLegacy(){};
~Parser(){};
};
#endif // PARSER_H

Bestand weergeven

@ -1,71 +0,0 @@
#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<Headline> ParserMarkdown::extractHeadlines(std::string content) const
{
std::vector<Headline> 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<maddy::ParserConfig> config = std::make_shared<maddy::ParserConfig>();
auto maddy = std::make_shared<maddy::Parser>(config);
auto linkParser = std::make_shared<maddy::LinkParser>();
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<std::string> ParserMarkdown::extractCategories(std::string content) const
{
return { };
}

Bestand weergeven

@ -1,21 +0,0 @@
#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<Headline> 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<std::string> extractCategories(std::string content) const;
};
#endif // PARSER_MARKDOWN_H

Bestand weergeven

@ -37,10 +37,6 @@ 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.
@ -67,17 +63,6 @@ std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
return std::make_unique<FsCache>(path);
}
std::unique_ptr<IParser> createParser(const ConfigVariableResolver &resolver)
{
std::string parser = resolver.getConfig("parser");
if(parser == "legacy")
{
return std::make_unique<ParserLegacy>();
}
return std::make_unique<ParserMarkdown>();
}
int main(int argc, char **argv)
{
if(geteuid() == 0)
@ -92,21 +77,21 @@ int main(int argc, char **argv)
Logger::error() << "Sandbox is not supported, exiting";
exit(EXIT_FAILURE);
}
if(argc < 2)
{
std::cerr << "no path to config file provided" << std::endl;
return 1;
}
std::string configpath = std::filesystem::absolute(argv[1]).string();
if(!sandbox->enableForInit())
{
Logger::error() << "Sandboxing for init mode could not be activated.";
exit(EXIT_FAILURE);
}
if(argc < 2)
{
std::cerr << "no path to config file provided" << std::endl;
return 1;
}
try
{
ConfigReader configreader(argv[1]);
ConfigReader configreader(configpath);
Config config = configreader.readConfig();
// TODO: config.connectiontring only works as long as we only support sqlite of course
@ -150,10 +135,7 @@ int main(int argc, char **argv)
auto cache = createCache(config.configVarResolver);
cache->clear();
auto parser = createParser(config.configVarResolver);
HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get(), *parser.get()};
HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get()};
RequestWorker requestWorker{handlerFactory, database->createSessionDao(), siteTemplate};
auto interface = createGateway(config);

Bestand weergeven

@ -26,16 +26,26 @@
bool SandboxLinux::enableForInit()
{
umask(0027);
struct qssb_policy policy = {0};
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
policy.blacklisted_syscalls = blacklisted_syscalls;
policy.no_new_privs = 1;
int result = qssb_enable_policy(&policy);
if(result != 0)
struct qssb_policy *policy = qssb_init_policy();
if(policy == NULL)
{
Logger::error() << "Failed to install sandboxing policy (init): " << result;
Logger::error() << "Failed to init sandboxing policy (init)";
return false;
}
policy->namespace_options = QSSB_UNSHARE_USER;
policy->drop_caps = 0;
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(execveat));
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(execve));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
int result = qssb_enable_policy(policy);
if(result != 0)
{
Logger::error() << "Failed to enable sandboxing policy (init): " << result;
qssb_free_policy(policy);
return false;
}
qssb_free_policy(policy);
return true;
}
@ -44,28 +54,34 @@ bool SandboxLinux::enablePreWorker(std::vector<std::string> fsPaths)
std::sort(fsPaths.begin(), fsPaths.end(),
[](const std::string &a, const std::string &b) { return a.length() < b.length(); });
struct qssb_path_policy *policies = new qssb_path_policy[fsPaths.size()];
struct qssb_policy *policy = qssb_init_policy();
if(policy == NULL)
{
Logger::error() << "Failed to init sandboxing policy (pre)";
return false;
}
for(unsigned int i = 0; i < fsPaths.size(); i++)
{
policies[i].next = policies + (i + 1);
policies[i].mountpoint = fsPaths[i].c_str();
policies[i].policy = QSSB_MOUNT_ALLOW_READ | QSSB_MOUNT_ALLOW_WRITE;
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ | QSSB_FS_ALLOW_WRITE, fsPaths[i].c_str());
}
policies[fsPaths.size() - 1].next = NULL;
struct qssb_policy policy = {0};
policy.path_policies = policies;
policy.namespace_options |= QSSB_UNSHARE_MOUNT;
policy.namespace_options |= QSSB_UNSHARE_USER;
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
policy.blacklisted_syscalls = blacklisted_syscalls;
int result = qssb_enable_policy(&policy);
policy->namespace_options = QSSB_UNSHARE_MOUNT;
policy->drop_caps = 0;
policy->mount_path_policies_to_chroot = 1;
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(execveat));
qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(execve));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
int result = qssb_enable_policy(policy);
if(result != 0)
{
Logger::error() << "Failed to install sandboxing policy (preworker): %i" << result;
qssb_free_policy(policy);
return false;
}
delete[] policies;
qssb_free_policy(policy);
return true;
}
@ -88,30 +104,35 @@ bool SandboxLinux::supported()
}
bool SandboxLinux::enableForWorker()
{
struct qssb_policy policy = {0};
policy.drop_caps = 1;
policy.not_dumpable = 1;
policy.no_new_privs = 1;
struct qssb_policy *policy = qssb_init_policy();
if(policy == NULL)
{
Logger::error() << "Failed to init sandboxing policy (worker) ";
return false;
}
policy->drop_caps = 1;
policy->not_dumpable = 1;
policy->no_new_privs = 1;
policy->namespace_options = 0;
/* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the
* sense that more could be listed here and some critical ones are probably missing */
int blacklisted_syscalls[] = {QSSB_SYS(setuid),
QSSB_SYS(connect),
QSSB_SYS(chroot),
QSSB_SYS(pivot_root),
QSSB_SYS(mount),
QSSB_SYS(setns),
QSSB_SYS(unshare),
QSSB_SYS(ptrace),
QSSB_SYS(personality),
QSSB_SYS(prctl),
-1};
policy.blacklisted_syscalls = blacklisted_syscalls;
if(qssb_enable_policy(&policy) != 0)
/* TODO: use qssb groups */
long blacklisted_syscalls[] = {QSSB_SYS(setuid), QSSB_SYS(connect), QSSB_SYS(chroot), QSSB_SYS(pivot_root),
QSSB_SYS(mount), QSSB_SYS(setns), QSSB_SYS(unshare), QSSB_SYS(ptrace),
QSSB_SYS(personality), QSSB_SYS(prctl)};
qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, blacklisted_syscalls,
sizeof(blacklisted_syscalls) / sizeof(blacklisted_syscalls[0]));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
if(qssb_enable_policy(policy) != 0)
{
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
qssb_free_policy(policy);
return false;
}
qssb_free_policy(policy);
return true;
}

Bestand weergeven

@ -25,24 +25,13 @@ count integer
CREATE TABLE category(id INTEGER PRIMARY KEY, name varchar(255));
CREATE TABLE categorymember(id INTEGER PRIMARY KEY, category REFERENCES category(id), page REFERENCES page (id));
CREATE INDEX revisionid ON revision (revisionid DESC);
CREATE INDEX pagename ON page (name)
;
CREATE INDEX token ON session (token)
;
CREATE TRIGGER search_ai AFTER INSERT ON revision BEGIN
DELETE FROM search WHERE page = new.page;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;
CREATE TRIGGER search_au AFTER UPDATE ON revision BEGIN
DELETE FROM search WHERE page = old.page;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;
CREATE VIRTUAL TABLE search USING fts5(content, page UNINDEXED, content=revision,content_rowid=id)
/* search(content,page) */;
CREATE TABLE IF NOT EXISTS 'search_data'(id INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'search_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS 'search_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
CREATE TABLE IF NOT EXISTS 'search_config'(k PRIMARY KEY, v) WITHOUT ROWID;
CREATE INDEX pagename ON page (name);
CREATE INDEX token ON session (token);
CREATE VIRTUAL TABLE search USING fts5(content, page UNINDEXED, content=revision,content_rowid=id);
CREATE TRIGGER search_ad AFTER DELETE ON revision BEGIN
INSERT INTO search(search, rowid, content, page) VALUES('delete', old.id, old.content, old.page);
END;
CREATE TRIGGER search_ai AFTER INSERT ON revision BEGIN
INSERT INTO search(search, rowid, content, page) SELECT 'delete', id, content, page FROM revision WHERE page = new.page AND revisionid = new.revisionid - 1;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;

Bestand weergeven

@ -46,6 +46,12 @@ std::string utils::html_xss(std::string_view str)
case '%':
result += "&#37;";
break;
case '\'':
result += "&#x27;";
break;
case '&':
result += "&amp;";
break;
default:
result += c;
}
@ -93,7 +99,7 @@ std::vector<std::string> utils::split(const std::string &str, char delim)
// TODO: can easily break if we pass a regex here
std::vector<std::string> utils::split(const std::string &str, const std::string &delim)
{
std::regex regex { delim + "+" };
std::regex regex{delim + "+"};
return split(str, regex);
}
@ -175,14 +181,3 @@ 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<void (std::smatch &)> 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);
}
}

Bestand weergeven

@ -60,7 +60,6 @@ template <class T, class U> std::vector<U> getAll(std::multimap<T, U> map, T key
std::string regex_callback_replacer(std::regex regex, const std::string &input,
std::function<std::string(std::smatch &)> callback);
void regex_callback_extractor(std::regex regex, const std::string &input, std::function<void(std::smatch &)> callback);
std::string readCompleteFile(std::string_view filepath);
inline std::string nz(const char *s)